From 2de9a3f37aa9f8f2e18a36c653e43a340da0fb5c Mon Sep 17 00:00:00 2001 From: TC1995 Date: Mon, 20 Apr 2020 19:02:13 +0200 Subject: [PATCH] Set Link State from VBox to 86Box implemented on the PCnet card (any other nics don't have it and will simply return NULL). The PCnet-based Racal nic now has its own OID to make its drivers happy (according to VBox). --- src/include/86box/network.h | 4 +- src/network/net_3c503.c | 2 +- src/network/net_ne2000.c | 2 +- src/network/net_pcap.c | 2 +- src/network/net_pcnet.c | 258 +++++++++++++++++++++++++++--------- src/network/net_slirp.c | 2 +- src/network/net_wd8003.c | 2 +- src/network/network.c | 3 +- 8 files changed, 203 insertions(+), 72 deletions(-) diff --git a/src/include/86box/network.h b/src/include/86box/network.h index 39dbe49bc..9a0916382 100644 --- a/src/include/86box/network.h +++ b/src/include/86box/network.h @@ -66,6 +66,7 @@ enum { typedef void (*NETRXCB)(void *, uint8_t *, int); typedef int (*NETWAITCB)(void *); +typedef int (*NETSETLINKSTATE)(void *); typedef struct { @@ -76,6 +77,7 @@ typedef struct { int (*poll)(void *); NETRXCB rx; NETWAITCB wait; + NETSETLINKSTATE set_link_state; } netcard_t; typedef struct { @@ -101,7 +103,7 @@ extern void network_busy(uint8_t set); extern void network_end(void); extern void network_init(void); -extern void network_attach(void *, uint8_t *, NETRXCB, NETWAITCB); +extern void network_attach(void *, uint8_t *, NETRXCB, NETWAITCB, NETSETLINKSTATE); extern void network_close(void); extern void network_reset(void); extern int network_available(void); diff --git a/src/network/net_3c503.c b/src/network/net_3c503.c index b983ca3a3..c75c07353 100644 --- a/src/network/net_3c503.c +++ b/src/network/net_3c503.c @@ -617,7 +617,7 @@ threec503_nic_init(const device_t *info) dev->regs.gacfr = 0x09; /* Start with RAM mapping enabled. */ /* Attach ourselves to the network module. */ - network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL); + network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL, NULL); return(dev); } diff --git a/src/network/net_ne2000.c b/src/network/net_ne2000.c index 6649db62c..5158673c1 100644 --- a/src/network/net_ne2000.c +++ b/src/network/net_ne2000.c @@ -1469,7 +1469,7 @@ nic_init(const device_t *info) nic_reset(dev); /* Attach ourselves to the network module. */ - network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL); + network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL, NULL); nelog(1, "%s: %s attached IO=0x%X IRQ=%d\n", dev->name, dev->is_pci?"PCI":"ISA", dev->base_address, dev->base_irq); diff --git a/src/network/net_pcap.c b/src/network/net_pcap.c index 21a43190b..beba36ca9 100644 --- a/src/network/net_pcap.c +++ b/src/network/net_pcap.c @@ -184,7 +184,7 @@ poll_thread(void *arg) if (pcap == NULL) break; /* Wait for the next packet to arrive. */ - if (network_get_wait() || (poll_card->wait && poll_card->wait(poll_card->priv))) + if (network_get_wait() || (poll_card->set_link_state && poll_card->set_link_state(poll_card->priv)) || (poll_card->wait && poll_card->wait(poll_card->priv))) data = NULL; else data = (uint8_t *)f_pcap_next((void *)pcap, &h); diff --git a/src/network/net_pcnet.c b/src/network/net_pcnet.c index f7cf2a5dc..777858624 100644 --- a/src/network/net_pcnet.c +++ b/src/network/net_pcnet.c @@ -64,10 +64,12 @@ typedef struct RTNETETHERHDR #define MII_MAX_REG 32 #define CSR_MAX_REG 128 +/** Maximum number of times we report a link down to the guest (failure to send frame) */ +#define PCNET_MAX_LINKDOWN_REPORTED 3 + /** Maximum frame size we handle */ #define MAX_FRAME 1536 - /** @name Bus configuration registers * @{ */ #define BCR_MSRDA 0 @@ -230,9 +232,16 @@ typedef struct { /** Error counter for bad receive descriptors. */ uint32_t uCntBadRMD; uint16_t u16CSR0LastSeenByGuest; - uint64_t last_poll; + /** If set the link is currently up. */ + int fLinkUp; + /** If set the link is temporarily down because of a saved state load. */ + int fLinkTempDown; + /** Number of times we've reported the link down. */ + uint32_t cLinkDownReported; + /** MS to wait before we enable the link. */ + uint32_t cMsLinkUpDelay; uint8_t maclocal[6]; /* configured MAC (local) address */ - pc_timer_t poll_timer, timer_soft_int; + pc_timer_t timer_soft_int, timer_restore; } nic_t; /** @todo All structs: big endian? */ @@ -403,6 +412,17 @@ pcnet_do_irq(nic_t *dev, int issue) } } +/** + * Checks if the link is up. + * @returns true if the link is up. + * @returns false if the link is down. + */ +static __inline int +pcnetIsLinkUp(nic_t *dev) +{ + return !dev->fLinkTempDown && dev->fLinkUp; +} + /** * Load transmit message descriptor * Make sure we read the own flag first. @@ -1214,6 +1234,12 @@ pcnetReceiveNoSync(void *priv, uint8_t *buf, int size) buf = buf1; size = 60; } + + /* + * Drop packets if the cable is not connected + */ + if (!pcnetIsLinkUp(dev)) + return; pcnetlog(1, "%s: pcnetReceiveNoSync: RX %x:%x:%x:%x:%x:%x > %x:%x:%x:%x:%x:%x len %d\n", dev->name, buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], @@ -1408,6 +1434,28 @@ pcnetReceiveNoSync(void *priv, uint8_t *buf, int size) pcnetUpdateIrq(dev); } +/** + * Fails a TMD with a link down error. + */ +static void +pcnetXmitFailTMDLinkDown(nic_t *dev, TMD *pTmd) +{ + /* make carrier error - hope this is correct. */ + dev->cLinkDownReported++; + pTmd->tmd2.lcar = pTmd->tmd1.err = 1; + dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR */ +} + +/** + * Fails a TMD with a generic error. + */ +static void +pcnetXmitFailTMDGeneric(nic_t *dev, TMD *pTmd) +{ + /* make carrier error - hope this is correct. */ + pTmd->tmd2.lcar = pTmd->tmd1.err = 1; + dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR */ +} /** * Actually try transmit frames. @@ -1435,6 +1483,12 @@ pcnetAsyncTransmit(nic_t *dev) if (!pcnetTdtePoll(dev, &tmd)) break; + /* Don't continue sending packets when the link is down. */ + if ((!pcnetIsLinkUp(dev) + && dev->cLinkDownReported > PCNET_MAX_LINKDOWN_REPORTED) + ) + break; + pcnetlog(3, "%s: TMDLOAD %#010x\n", dev->name, PHYSADDR(dev, CSR_CXDA(dev))); int fLoopback = CSR_LOOP(dev); @@ -1446,52 +1500,59 @@ pcnetAsyncTransmit(nic_t *dev) const int cb = 4096 - tmd.tmd1.bcnt; pcnetlog("%s: pcnetAsyncTransmit: stp&enp: cb=%d xmtrc=%#x\n", dev->name, cb, CSR_XMTRC(dev)); - /* From the manual: ``A zero length buffer is acceptable as - * long as it is not the last buffer in a chain (STP = 0 and - * ENP = 1).'' That means that the first buffer might have a - * zero length if it is not the last one in the chain. */ - if (cb <= MAX_FRAME) { - dev->xmit_pos = cb; - DMAPageRead(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb); + if ((pcnetIsLinkUp(dev) || fLoopback)) { + + /* From the manual: ``A zero length buffer is acceptable as + * long as it is not the last buffer in a chain (STP = 0 and + * ENP = 1).'' That means that the first buffer might have a + * zero length if it is not the last one in the chain. */ + if (cb <= MAX_FRAME) { + dev->xmit_pos = cb; + DMAPageRead(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb); - if (fLoopback) { - if (HOST_IS_OWNER(CSR_CRST(dev))) - pcnetRdtePoll(dev); + if (fLoopback) { + if (HOST_IS_OWNER(CSR_CRST(dev))) + pcnetRdtePoll(dev); - pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos); + pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos); + } else { + pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf stp and enp, xmit pos = %d\n", dev->name, dev->xmit_pos); + network_tx(dev->abLoopBuf, dev->xmit_pos); + } + } else if (cb == 4096) { + /* The Windows NT4 pcnet driver sometimes marks the first + * unused descriptor as owned by us. Ignore that (by + * passing it back). Do not update the ring counter in this + * case (otherwise that driver becomes even more confused, + * which causes transmit to stall for about 10 seconds). + * This is just a workaround, not a final solution. + */ + /* r=frank: IMHO this is the correct implementation. The + * manual says: ``If the OWN bit is set and the buffer + * length is 0, the OWN bit will be cleared. In the C-LANCE + * the buffer length of 0 is interpreted as a 4096-byte + * buffer.'' + */ + /* r=michaln: Perhaps not quite right. The C-LANCE (Am79C90) + * datasheet explains that the old LANCE (Am7990) ignored + * the top four bits next to BCNT and a count of 0 was + * interpreted as 4096. In the C-LANCE, that is still the + * case if the top bits are all ones. If all 16 bits are + * zero, the C-LANCE interprets it as zero-length transmit + * buffer. It's not entirely clear if the later models + * (PCnet-ISA, PCnet-PCI) behave like the C-LANCE or not. + * It is possible that the actual behavior of the C-LANCE + * and later hardware is that the buffer lengths are *16-bit* + * two's complement numbers between 0 and 4096. AMD's drivers + * in fact generally treat the length as a 16-bit quantity. */ + pcnetlog(1, "%s: pcnetAsyncTransmit: illegal 4kb frame -> ignoring\n", dev->name); + pcnetTmdStorePassHost(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev))); + break; } else { - pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf stp and enp, xmit pos = %d\n", dev->name, dev->xmit_pos); - network_tx(dev->abLoopBuf, dev->xmit_pos); + pcnetXmitFailTMDGeneric(dev, &tmd); } - } else if (cb == 4096) { - /* The Windows NT4 pcnet driver sometimes marks the first - * unused descriptor as owned by us. Ignore that (by - * passing it back). Do not update the ring counter in this - * case (otherwise that driver becomes even more confused, - * which causes transmit to stall for about 10 seconds). - * This is just a workaround, not a final solution. - */ - /* r=frank: IMHO this is the correct implementation. The - * manual says: ``If the OWN bit is set and the buffer - * length is 0, the OWN bit will be cleared. In the C-LANCE - * the buffer length of 0 is interpreted as a 4096-byte - * buffer.'' - */ - /* r=michaln: Perhaps not quite right. The C-LANCE (Am79C90) - * datasheet explains that the old LANCE (Am7990) ignored - * the top four bits next to BCNT and a count of 0 was - * interpreted as 4096. In the C-LANCE, that is still the - * case if the top bits are all ones. If all 16 bits are - * zero, the C-LANCE interprets it as zero-length transmit - * buffer. It's not entirely clear if the later models - * (PCnet-ISA, PCnet-PCI) behave like the C-LANCE or not. - * It is possible that the actual behavior of the C-LANCE - * and later hardware is that the buffer lengths are *16-bit* - * two's complement numbers between 0 and 4096. AMD's drivers - * in fact generally treat the length as a 16-bit quantity. */ - pcnetlog(1, "%s: pcnetAsyncTransmit: illegal 4kb frame -> ignoring\n", dev->name); - pcnetTmdStorePassHost(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev))); - break; + } else { + pcnetXmitFailTMDLinkDown(dev, &tmd); } /* Write back the TMD and pass it to the host (clear own bit). */ @@ -2021,8 +2082,9 @@ pcnet_mii_readw(nic_t *dev, uint16_t miiaddr) | 0x0008 /* Able to do auto-negotiation. */ | 0x0004 /* Link up. */ | 0x0001; /* Extended Capability, i.e. registers 4+ valid. */ - if (isolate) { + if (!dev->fLinkUp || dev->fLinkTempDown || isolate) { val &= ~(0x0020 | 0x0004); + dev->cLinkDownReported++; } if (!autoneg) { /* Auto-negotiation disabled. */ @@ -2056,7 +2118,7 @@ pcnet_mii_readw(nic_t *dev, uint16_t miiaddr) case 5: /* Link partner ability register. */ - if (!isolate) { + if (dev->fLinkUp && !dev->fLinkTempDown && !isolate) { val = 0x8000 /* Next page bit. */ | 0x4000 /* Link partner acked us. */ | 0x0400 /* Can do flow control. */ @@ -2064,23 +2126,25 @@ pcnet_mii_readw(nic_t *dev, uint16_t miiaddr) | 0x0001; /* Use CSMA selector. */ } else { val = 0; + dev->cLinkDownReported++; } break; case 6: /* Auto negotiation expansion register. */ - if (!isolate) { + if (dev->fLinkUp && !dev->fLinkTempDown && !isolate) { val = 0x0008 /* Link partner supports npage. */ | 0x0004 /* Enable npage words. */ | 0x0001; /* Can do N-way auto-negotiation. */ } else { val = 0; + dev->cLinkDownReported++; } break; case 18: /* Diagnostic Register (FreeBSD pcn/ac101 driver reads this). */ - if (!isolate) { + if (dev->fLinkUp && !dev->fLinkTempDown && !isolate) { val = 0x1000 /* Receive PLL locked. */ | 0x0200; /* Signal detected. */ @@ -2095,6 +2159,7 @@ pcnet_mii_readw(nic_t *dev, uint16_t miiaddr) } } else { val = 0; + dev->cLinkDownReported++; } break; @@ -2117,6 +2182,11 @@ pcnet_bcr_readw(nic_t *dev, uint16_t rap) case BCR_LED2: case BCR_LED3: val = dev->aBCR[rap] & ~0x8000; + if (dev->fLinkTempDown || !dev->fLinkUp) { + if (rap == 4) + dev->cLinkDownReported++; + val &= ~0x40; + } val |= (val & 0x017f & dev->u32Lnkst) ? 0x8000 : 0; break; @@ -2623,6 +2693,27 @@ pcnet_pci_read(int func, int addr, void *p) return(0); } +/** + * Takes down the link temporarily if it's current status is up. + * + * This is used during restore and when replumbing the network link. + * + * The temporary link outage is supposed to indicate to the OS that all network + * connections have been lost and that it for instance is appropriate to + * renegotiate any DHCP lease. + * + * @param pThis The PCnet shared instance data. + */ +static void +pcnetTempLinkDown(nic_t *dev) +{ + if (dev->fLinkUp) { + dev->fLinkTempDown = 1; + dev->cLinkDownReported = 0; + dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR (this is probably wrong) */ + timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); + } +} /** * Check if the device/driver can receive data now. @@ -2664,6 +2755,34 @@ pcnetWaitReceiveAvail(void *priv) return dev->fMaybeOutOfSpace; } +static int +pcnetSetLinkState(void *priv) +{ + nic_t *dev = (nic_t *) priv; + int fLinkUp; + + if (dev->fLinkTempDown) { + pcnetTempLinkDown(dev); + return 1; + } + + fLinkUp = (dev->fLinkUp && !dev->fLinkTempDown); + if (dev->fLinkUp != fLinkUp) { + dev->fLinkUp = fLinkUp; + if (fLinkUp) { + dev->fLinkTempDown = 1; + dev->cLinkDownReported = 0; + dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR (this is probably wrong) */ + timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); + } else { + dev->cLinkDownReported = 0; + dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR (this is probably wrong) */ + } + } + + return 0; +} + static void pcnetTimerSoftInt(void *priv) { @@ -2675,16 +2794,18 @@ pcnetTimerSoftInt(void *priv) } static void -pcnetTimerCallback(void *priv) +pcnetTimerRestore(void *priv) { nic_t *dev = (nic_t *) priv; -#ifdef ENABLE_PCNET_LOG - pcnetlog(3, "Timer Callback to RX\n"); -#endif - pcnetPollRxTx(dev); - - timer_disable(&dev->poll_timer); + if (dev->cLinkDownReported <= PCNET_MAX_LINKDOWN_REPORTED) { + timer_advance_u64(&dev->timer_restore, 1500000 * TIMER_USEC); + } else { + dev->fLinkTempDown = 0; + if (dev->fLinkUp) { + dev->aCSR[0] &= ~(0x8000 | 0x2000); /* ERR | CERR - probably not 100% correct either... */ + } + } } static void * @@ -2717,10 +2838,19 @@ pcnet_init(const device_t *info) pcnet_mem_init(dev, 0x0fffff00); pcnet_mem_disable(dev); } - - dev->maclocal[0] = 0x00; /* 00:0C:87 (AMD OID) */ - dev->maclocal[1] = 0x0C; - dev->maclocal[2] = 0x87; + + dev->fLinkUp = 1; + dev->cMsLinkUpDelay = 5000; + + if (dev->board == DEV_AM79C960_EB) { + dev->maclocal[0] = 0x02; /* 02:07:01 (Racal OID) */ + dev->maclocal[1] = 0x07; + dev->maclocal[2] = 0x01; + } else { + dev->maclocal[0] = 0x00; /* 00:0C:87 (AMD OID) */ + dev->maclocal[1] = 0x0C; + dev->maclocal[2] = 0x87; + } /* See if we have a local MAC address configured. */ mac = device_get_config_mac("mac", -1); @@ -2816,12 +2946,12 @@ pcnet_init(const device_t *info) pcnetHardReset(dev); /* Attach ourselves to the network module. */ - network_attach(dev, dev->aPROM, pcnetReceiveNoSync, pcnetWaitReceiveAvail); + network_attach(dev, dev->aPROM, pcnetReceiveNoSync, pcnetWaitReceiveAvail, pcnetSetLinkState); if (dev->board == DEV_AM79C973) timer_add(&dev->timer_soft_int, pcnetTimerSoftInt, dev, 0); - - timer_add(&dev->poll_timer, pcnetTimerCallback, dev, 0); + + timer_add(&dev->timer_restore, pcnetTimerRestore, dev, 0); return(dev); } @@ -2838,8 +2968,6 @@ pcnet_close(void *priv) network_close(); if (dev) { - timer_disable(&dev->poll_timer); - free(dev); dev = NULL; diff --git a/src/network/net_slirp.c b/src/network/net_slirp.c index 3adc8d6f7..c1af9fc65 100644 --- a/src/network/net_slirp.c +++ b/src/network/net_slirp.c @@ -147,7 +147,7 @@ poll_thread(void *arg) /* Wait for the next packet to arrive. */ data_valid = 0; - if ((!network_get_wait() && !(poll_card->wait && poll_card->wait(poll_card->priv))) && (QueuePeek(slirpq) != 0)) { + if ((!network_get_wait() && !(poll_card->set_link_state && poll_card->set_link_state(poll_card->priv)) && !(poll_card->wait && poll_card->wait(poll_card->priv))) && (QueuePeek(slirpq) != 0)) { /* Grab a packet from the queue. */ // ui_sb_update_icon(SB_NETWORK, 1); diff --git a/src/network/net_wd8003.c b/src/network/net_wd8003.c index 7f0b5aeeb..953212a0b 100644 --- a/src/network/net_wd8003.c +++ b/src/network/net_wd8003.c @@ -771,7 +771,7 @@ wd_init(const device_t *info) mem_mapping_disable(&dev->ram_mapping); /* Attach ourselves to the network module. */ - network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL); + network_attach(dev->dp8390, dev->dp8390->physaddr, dp8390_rx, NULL, NULL); if (!(dev->board_chip & WE_ID_BUS_MCA)) { wdlog("%s: attached IO=0x%X IRQ=%d, RAM addr=0x%06x\n", dev->name, diff --git a/src/network/network.c b/src/network/network.c index db69227f3..4273cf6fb 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -223,7 +223,7 @@ network_init(void) * modules. */ void -network_attach(void *dev, uint8_t *mac, NETRXCB rx, NETWAITCB wait) +network_attach(void *dev, uint8_t *mac, NETRXCB rx, NETWAITCB wait, NETSETLINKSTATE set_link_state) { if (network_card == 0) return; @@ -231,6 +231,7 @@ network_attach(void *dev, uint8_t *mac, NETRXCB rx, NETWAITCB wait) net_cards[network_card].priv = dev; net_cards[network_card].rx = rx; net_cards[network_card].wait = wait; + net_cards[network_card].set_link_state = set_link_state; network_mac = mac; network_set_wait(0);