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).
This commit is contained in:
TC1995
2020-04-20 19:02:13 +02:00
parent 9a287c31e5
commit 2de9a3f37a
8 changed files with 203 additions and 72 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);