/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of the following network controller: * - 3Com Etherlink 3c500/3c501 (ISA 8-bit). * * * * Based on @(#)Dev3C501.cpp Oracle (VirtualBox) * * Authors: TheCollector1995, * Oracle * * Copyright 2022 TheCollector1995. * Portions Copyright (C) 2022 Oracle and/or its affilitates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the: * * Free Software Foundation, Inc. * 59 Temple Place - Suite 330 * Boston, MA 02111-1307 * USA. */ #include #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/io.h> #include <86box/dma.h> #include <86box/pic.h> #include <86box/mem.h> #include <86box/random.h> #include <86box/device.h> #include <86box/thread.h> #include <86box/timer.h> #include <86box/network.h> #include <86box/net_3c501.h> #include <86box/bswap.h> #include <86box/plat_unused.h> /* Maximum number of times we report a link down to the guest (failure to send frame) */ #define ELNK_MAX_LINKDOWN_REPORTED 3 /* Maximum number of times we postpone restoring a link that is temporarily down. */ #define ELNK_MAX_LINKRST_POSTPONED 3 /* Maximum frame size we handle */ #define MAX_FRAME 1536 /* Size of the packet buffer. */ #define ELNK_BUF_SIZE 2048 /* The packet buffer address mask. */ #define ELNK_BUF_ADR_MASK (ELNK_BUF_SIZE - 1) /* The GP buffer pointer address within the buffer. */ #define ELNK_GP(dev) (dev->uGPBufPtr & ELNK_BUF_ADR_MASK) /* The GP buffer pointer mask. * NB: The GP buffer pointer is internally a 12-bit counter. When addressing into the * packet buffer, bit 11 is ignored. Required to pass 3C501 diagnostics. */ #define ELNK_GP_MASK 0xfff /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * EtherLink Transmit Command Register. */ typedef struct ELNK_XMIT_CMD { uint8_t det_ufl : 1; /* Detect underflow. */ uint8_t det_coll : 1; /* Detect collision. */ uint8_t det_16col : 1; /* Detect collision 16. */ uint8_t det_succ : 1; /* Detect successful xmit. */ uint8_t unused : 4; } EL_XMT_CMD; /** * EtherLink Transmit Status Register. * * We will never see any real collisions, although collisions (including 16 * successive collisions) may be useful to report when the link is down * (something the 3C501 does not have a concept of). */ typedef struct ELNK_XMIT_STAT { uint8_t uflow : 1; /* Underflow on transmit. */ uint8_t coll : 1; /* Collision on transmit. */ uint8_t coll16 : 1; /* 16 collisions on transmit. */ uint8_t ready : 1; /* Ready for a new frame. */ uint8_t undef : 4; } EL_XMT_STAT; /** Address match (adr_match) modes. */ typedef enum { EL_ADRM_DISABLED = 0, /* Receiver disabled. */ EL_ADRM_PROMISC = 1, /* Receive all addresses. */ EL_ADRM_BCAST = 2, /* Receive station + broadcast. */ EL_ADRM_MCAST = 3 /* Receive station + multicast. */ } EL_ADDR_MATCH; /** * EtherLink Receive Command Register. */ typedef struct ELNK_RECV_CMD { uint8_t det_ofl : 1; /* Detect overflow errors. */ uint8_t det_fcs : 1; /* Detect FCS errors. */ uint8_t det_drbl : 1; /* Detect dribble error. */ uint8_t det_runt : 1; /* Detect short frames. */ uint8_t det_eof : 1; /* Detect EOF (frames without overflow). */ uint8_t acpt_good : 1; /* Accept good frames. */ uint8_t adr_match : 2; /* Address match mode. */ } EL_RCV_CMD; /** * EtherLink Receive Status Register. */ typedef struct ELNK_RECV_STAT { uint8_t oflow : 1; /* Overflow on receive. */ uint8_t fcs : 1; /* FCS error. */ uint8_t dribble : 1; /* Dribble error. */ uint8_t runt : 1; /* Short frame. */ uint8_t no_ovf : 1; /* Received packet w/o overflow. */ uint8_t good : 1; /* Received good packet. */ uint8_t undef : 1; uint8_t stale : 1; /* Stale receive status. */ } EL_RCV_STAT; /** Buffer control (buf_ctl) modes. */ typedef enum { EL_BCTL_SYSTEM = 0, /* Host has buffer access. */ EL_BCTL_XMT_RCV = 1, /* Transmit, then receive. */ EL_BCTL_RECEIVE = 2, /* Receive. */ EL_BCTL_LOOPBACK = 3 /* Loopback. */ } EL_BUFFER_CONTROL; /** * EtherLink Auxiliary Status Register. */ typedef struct ELNK_AUX_CMD { uint8_t ire : 1; /* Interrupt Request Enable. */ uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */ uint8_t buf_ctl : 2; /* Packet buffer control. */ uint8_t unused : 1; uint8_t dma_req : 1; /* DMA request. */ uint8_t ride : 1; /* Request Interrupt and DMA Enable. */ uint8_t reset : 1; /* Card in reset while set. */ } EL_AUX_CMD; /** * EtherLink Auxiliary Status Register. */ typedef struct ELNK_AUX_STAT { uint8_t recv_bsy : 1; /* Receive busy. */ uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */ uint8_t buf_ctl : 2; /* Packet buffer control. */ uint8_t dma_done : 1; /* DMA done. */ uint8_t dma_req : 1; /* DMA request. */ uint8_t ride : 1; /* Request Interrupt and DMA Enable. */ uint8_t xmit_bsy : 1; /* Transmit busy. */ } EL_AUX_STAT; /** * Internal interrupt status. */ typedef struct ELNK_INTR_STAT { uint8_t recv_intr : 1; /* Receive interrupt status. */ uint8_t xmit_intr : 1; /* Transmit interrupt status. */ uint8_t dma_intr : 1; /* DMA interrupt status. */ uint8_t unused : 5; } EL_INTR_STAT; typedef struct threec501_t { uint32_t base_address; int base_irq; uint32_t bios_addr; uint8_t maclocal[6]; /* configured MAC (local) address. */ bool fISR; /* Internal interrupt flag. */ int fDMA; /* Internal DMA active flag. */ int fInReset; /* Internal in-reset flag. */ uint8_t aPROM[8]; /* The PROM contents. Only 8 bytes addressable, R/O. */ uint8_t aStationAddr[6]; /* The station address programmed by the guest, W/O. */ uint16_t uGPBufPtr; /* General Purpose (GP) Buffer Pointer, R/W. */ uint16_t uRCVBufPtr; /* Receive (RCV) Buffer Pointer, R/W. */ /** Transmit Command Register, W/O. */ union { uint8_t XmitCmdReg; EL_XMT_CMD XmitCmd; }; /** Transmit Status Register, R/O. */ union { uint8_t XmitStatReg; EL_XMT_STAT XmitStat; }; /** Receive Command Register, W/O. */ union { uint8_t RcvCmdReg; EL_RCV_CMD RcvCmd; }; /** Receive Status Register, R/O. */ union { uint8_t RcvStatReg; EL_RCV_STAT RcvStat; }; /** Auxiliary Command Register, W/O. */ union { uint8_t AuxCmdReg; EL_AUX_CMD AuxCmd; }; /** Auxiliary Status Register, R/O. */ union { uint8_t AuxStatReg; EL_AUX_STAT AuxStat; }; int fLinkUp; /* If set the link is currently up. */ int fLinkTempDown; /* If set the link is temporarily down because of a saved state load. */ uint16_t cLinkDownReported; /* Number of times we've reported the link down. */ uint16_t cLinkRestorePostponed; /* Number of times we've postponed the link restore. */ /* Internal interrupt state. */ union { uint8_t IntrStateReg; EL_INTR_STAT IntrState; }; uint32_t cMsLinkUpDelay; /* MS to wait before we enable the link. */ int dma_channel; uint8_t abLoopBuf[ELNK_BUF_SIZE]; /* The loopback transmit buffer (avoid stack allocations). */ uint8_t abRuntBuf[64]; /* The runt pad buffer (only really needs 60 bytes). */ uint8_t abPacketBuf[ELNK_BUF_SIZE]; /* The packet buffer. */ int dma_pos; pc_timer_t timer_restore; netcard_t *netcard; } threec501_t; #ifdef ENABLE_3COM501_LOG int threec501_do_log = ENABLE_3COM501_LOG; static void threec501_log(const char *fmt, ...) { va_list ap; if (threec501_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define threec501_log(fmt, ...) #endif static void elnkSoftReset(threec501_t *dev); static void elnkR3HardReset(threec501_t *dev); #ifndef ETHER_IS_MULTICAST /* Net/Open BSD macro it seems */ # define ETHER_IS_MULTICAST(a) ((*(uint8_t *) (a)) & 1) #endif #define ETHER_ADDR_LEN ETH_ALEN #define ETH_ALEN 6 #pragma pack(1) struct ether_header /** @todo Use RTNETETHERHDR? */ { uint8_t ether_dhost[ETH_ALEN]; /**< destination ethernet address */ uint8_t ether_shost[ETH_ALEN]; /**< source ethernet address */ uint16_t ether_type; /**< packet type ID field */ }; #pragma pack() static void elnk_do_irq(threec501_t *dev, int set) { if (set) picint(1 << dev->base_irq); else picintc(1 << dev->base_irq); } /** * Checks if the link is up. * @returns true if the link is up. * @returns false if the link is down. */ static __inline int elnkIsLinkUp(threec501_t *dev) { return !dev->fLinkTempDown && dev->fLinkUp; } /** * 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 shared instance data. */ static void elnkTempLinkDown(threec501_t *dev) { if (dev->fLinkUp) { dev->fLinkTempDown = 1; dev->cLinkDownReported = 0; dev->cLinkRestorePostponed = 0; timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); } } /** * @interface_method_impl{PDMDEVREG,pfnReset} */ static void elnkR3Reset(void *priv) { threec501_t *dev = (threec501_t *) priv; if (dev->fLinkTempDown) { dev->cLinkDownReported = 0x1000; dev->cLinkRestorePostponed = 0x1000; timer_disable(&dev->timer_restore); } /** @todo How to flush the queues? */ elnkR3HardReset(dev); } static void elnkR3HardReset(threec501_t *dev) { dev->fISR = false; elnk_do_irq(dev, 0); /* Clear the packet buffer and station address. */ memset(dev->abPacketBuf, 0, sizeof(dev->abPacketBuf)); memset(dev->aStationAddr, 0, sizeof(dev->aStationAddr)); /* Reset the buffer pointers. */ dev->uGPBufPtr = 0; dev->uRCVBufPtr = 0; elnkSoftReset(dev); } /** * Check if incoming frame matches the station address. */ static __inline int padr_match(threec501_t *dev, const uint8_t *buf) { const struct ether_header *hdr = (struct ether_header *) buf; int result; /* Checks own + broadcast as well as own + multicast. */ result = (dev->RcvCmd.adr_match >= EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, dev->aStationAddr, 6); return result; } /** * Check if incoming frame is an accepted broadcast frame. */ static __inline int padr_bcast(threec501_t *dev, const uint8_t *buf) { static uint8_t aBCAST[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; const struct ether_header *hdr = (struct ether_header *) buf; int result = (dev->RcvCmd.adr_match == EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, aBCAST, 6); return result; } /** * Check if incoming frame is an accepted multicast frame. */ static __inline int padr_mcast(threec501_t *dev, const uint8_t *buf) { struct ether_header *hdr = (struct ether_header *) buf; int result = (dev->RcvCmd.adr_match == EL_ADRM_MCAST) && ETHER_IS_MULTICAST(hdr->ether_dhost); return result; } /** * Update the device IRQ line based on internal state. */ static void elnkUpdateIrq(threec501_t *dev) { bool fISR = false; /* IRQ is active if any interrupt source is active and interrupts * are enabled via RIDE or IRE. */ if (dev->IntrStateReg && (dev->AuxCmd.ride || dev->AuxCmd.ire)) fISR = true; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: elnkUpdateIrq: fISR=%d\n", fISR); #endif if (fISR != dev->fISR) { elnk_do_irq(dev, fISR); dev->fISR = fISR; } } /** * Perform a software reset of the NIC. */ static void elnkSoftReset(threec501_t *dev) { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: elnkSoftReset\n"); #endif /* Clear some of the user-visible register state. */ dev->XmitCmdReg = 0; dev->XmitStatReg = 0; dev->RcvCmdReg = 0; dev->RcvStatReg = 0; dev->AuxCmdReg = 0; dev->AuxStatReg = 0; /* The "stale receive status" is cleared by receiving an "interesting" packet. */ dev->RcvStat.stale = 1; /* By virtue of setting the buffer control to system, transmit is set to busy. */ dev->AuxStat.xmit_bsy = 1; /* Clear internal interrupt state. */ dev->IntrStateReg = 0; elnkUpdateIrq(dev); /* Note that a soft reset does not clear the packet buffer; software often * assumes that it survives soft reset. The programmed station address is * likewise not reset, and the buffer pointers are not reset either. * Verified on a real 3C501. */ /* No longer in reset state. */ dev->fInReset = 0; } /** * Write incoming data into the packet buffer. */ static int elnkReceiveLocked(void *priv, uint8_t *src, int size) { threec501_t *dev = (threec501_t *) priv; int is_padr = 0; int is_bcast = 0; int is_mcast = 0; bool fLoopback = dev->RcvCmd.adr_match == EL_BCTL_LOOPBACK; union { uint8_t RcvStatNewReg; EL_RCV_STAT RcvStatNew; } rcvstatnew; /* Drop everything if address matching is disabled. */ if (dev->RcvCmd.adr_match == EL_ADRM_DISABLED) return 0; /* Drop zero-length packets (how does that even happen?). */ if (!size) return 0; /* * Drop all packets if the cable is not connected (and not in loopback). */ if (!elnkIsLinkUp(dev) && !fLoopback) return 0; /* * Do not receive further packets until receive status was read. */ if (dev->RcvStat.stale == 0) return 0; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: size on wire=%d, RCV ptr=%u\n", size, dev->uRCVBufPtr); #endif /* * Perform address matching. Packets which do not pass the address * filter are always ignored. */ /// @todo cbToRecv must be 6 or more (complete address) if ((dev->RcvCmd.adr_match == EL_ADRM_PROMISC) /* promiscuous enabled */ || (is_padr = padr_match(dev, src)) || (is_bcast = padr_bcast(dev, src)) || (is_mcast = padr_mcast(dev, src))) { uint8_t *dst = dev->abPacketBuf + dev->uRCVBufPtr; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501 Packet passed address filter (is_padr=%d, is_bcast=%d, is_mcast=%d), size=%d\n", is_padr, is_bcast, is_mcast, size); #endif /* Receive status is evaluated from scratch. The stale bit must remain set until we know better. */ rcvstatnew.RcvStatNewReg = 0; rcvstatnew.RcvStatNew.stale = 1; dev->RcvStatReg = 0x80; /* Detect errors: Runts, overflow, and FCS errors. * NB: Dribble errors can not happen because we can only receive an * integral number of bytes. FCS errors are only possible in loopback * mode in case the FCS is deliberately corrupted. */ /* See if we need to pad, and how much. Have to be careful because the * Receive Buffer Pointer might be near the end of the buffer. */ if (size < 60) { /* In loopback mode only, short packets are flagged as errors because * diagnostic tools want to see the errors. Otherwise they're padded to * minimum length (if packet came over the wire, it should have been * properly padded). */ /// @todo This really is kind of wrong. We shouldn't be doing any /// padding here, it should be done by the sending side! if (!fLoopback) { memset(dev->abRuntBuf, 0, sizeof(dev->abRuntBuf)); memcpy(dev->abRuntBuf, src, size); size = 60; src = dev->abRuntBuf; } else { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501 runt, size=%d\n", size); #endif rcvstatnew.RcvStatNew.runt = 1; } } /* We don't care how big the frame is; if it fits into the buffer, all is * good. But conversely if the Receive Buffer Pointer is initially near the * end of the buffer, a small frame can trigger an overflow. */ if ((dev->uRCVBufPtr + size) <= ELNK_BUF_SIZE) rcvstatnew.RcvStatNew.no_ovf = 1; else { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501 overflow, size=%d\n", size); #endif rcvstatnew.RcvStatNew.oflow = 1; } if (fLoopback && dev->AuxCmd.xmit_bf) { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501 bad FCS\n"); #endif rcvstatnew.RcvStatNew.fcs = 1; } /* Error-free packets are considered good. */ if (rcvstatnew.RcvStatNew.no_ovf && !rcvstatnew.RcvStatNew.fcs && !rcvstatnew.RcvStatNew.runt) rcvstatnew.RcvStatNew.good = 1; uint16_t cbCopy = (uint16_t) MIN(ELNK_BUF_SIZE - dev->uRCVBufPtr, size); /* All packets that passed the address filter are copied to the buffer. */ /* Copy incoming data to the packet buffer. NB: Starts at the current * Receive Buffer Pointer position. */ memcpy(dst, src, cbCopy); /* Packet length is indicated via the receive buffer pointer. */ dev->uRCVBufPtr = (dev->uRCVBufPtr + cbCopy) & ELNK_GP_MASK; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501 Received packet, size=%d, RP=%u\n", cbCopy, dev->uRCVBufPtr); #endif /* * If one of the "interesting" conditions was hit, stop receiving until * the status register is read (mark it not stale). * NB: The precise receive logic is not very well described in the EtherLink * documentation. It was refined using the 3C501.EXE diagnostic utility. */ if ((rcvstatnew.RcvStatNew.good && dev->RcvCmd.acpt_good) || (rcvstatnew.RcvStatNew.no_ovf && dev->RcvCmd.det_eof) || (rcvstatnew.RcvStatNew.runt && dev->RcvCmd.det_runt) || (rcvstatnew.RcvStatNew.dribble && dev->RcvCmd.det_drbl) || (rcvstatnew.RcvStatNew.fcs && dev->RcvCmd.det_fcs) || (rcvstatnew.RcvStatNew.oflow && dev->RcvCmd.det_ofl)) { dev->AuxStat.recv_bsy = 0; dev->IntrState.recv_intr = 1; rcvstatnew.RcvStatNew.stale = 0; /* Prevents further receive until set again. */ } /* Finally update the receive status. */ dev->RcvStat = rcvstatnew.RcvStatNew; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: RcvCmd=%02X, RcvStat=%02X, RCVBufPtr=%u\n", dev->RcvCmdReg, dev->RcvStatReg, dev->uRCVBufPtr); #endif elnkUpdateIrq(dev); } return 1; } /** * Actually try transmit frames. * * @threads TX or EMT. */ static void elnkAsyncTransmit(threec501_t *dev) { /* * Just drop it if not transmitting. Can happen with delayed transmits * if transmit was disabled in the meantime. */ if (!dev->AuxStat.xmit_bsy) { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Nope, xmit disabled\n"); #endif return; } if ((dev->AuxCmd.buf_ctl != EL_BCTL_XMT_RCV) && (dev->AuxCmd.buf_ctl != EL_BCTL_LOOPBACK)) { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Nope, not in xmit-then-receive or loopback state\n"); #endif return; } /* * Blast out data from the packet buffer. */ do { /* Don't send anything when the link is down. */ if (!elnkIsLinkUp(dev) && dev->cLinkDownReported > ELNK_MAX_LINKDOWN_REPORTED) break; bool const fLoopback = dev->AuxCmd.buf_ctl == EL_BCTL_LOOPBACK; /* * Sending is easy peasy, there is by definition always * a complete packet on hand. */ const unsigned cb = ELNK_BUF_SIZE - ELNK_GP(dev); /* Packet size. */ #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: cb=%d, loopback=%d.\n", cb, fLoopback); #endif dev->XmitStatReg = 0; /* Clear transmit status before filling it out. */ if (elnkIsLinkUp(dev) || fLoopback) { if (cb <= MAX_FRAME) { if (fLoopback) { elnkReceiveLocked(dev, &dev->abPacketBuf[ELNK_GP(dev)], cb); } else { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: elnkAsyncTransmit: transmit loopbuf xmit pos = %d\n", cb); #endif network_tx(dev->netcard, &dev->abPacketBuf[ELNK_GP(dev)], cb); } dev->XmitStat.ready = 1; } else { /* Signal error, as this violates the Ethernet specs. */ /** @todo check if the correct error is generated. */ #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: illegal giant frame (%u bytes) -> signalling error\n", cb); #endif } } else { /* Signal a transmit error pretending there was a collision. */ dev->cLinkDownReported++; dev->XmitStat.coll = 1; } /* Transmit officially done, update register state. */ dev->AuxStat.xmit_bsy = 0; dev->IntrState.xmit_intr = !!(dev->XmitCmdReg & dev->XmitStatReg); #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: XmitCmd=%02X, XmitStat=%02X\n", dev->XmitCmdReg, dev->XmitStatReg); #endif /* NB: After a transmit, the GP Buffer Pointer points just past * the end of the packet buffer (3C501 diagnostics). */ dev->uGPBufPtr = ELNK_BUF_SIZE; /* NB: The buffer control does *not* change to Receive and stays the way it was. */ if (!fLoopback) { dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ } } while (0); /* No loop, because there isn't ever more than one packet to transmit. */ elnkUpdateIrq(dev); } static void elnkCsrWrite(threec501_t *dev, uint8_t data) { bool fTransmit = false; bool fReceive = false; bool fDMAR; int mode; union { uint8_t reg; EL_AUX_CMD val; } auxcmd; auxcmd.reg = data; /* Handle reset first. */ if (dev->AuxCmd.reset != auxcmd.val.reset) { if (auxcmd.val.reset) { /* Card is placed into reset. Just set the flag. NB: When in reset * state, we permit writes to other registers, but those have no * effect and will be overwritten when the card is taken out of reset. */ #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Card going into reset\n"); #endif dev->fInReset = true; /* Many EtherLink drivers like to reset the card a lot. That can lead to * packet loss if a packet was already received before the card was reset. */ } else { /* Card is being taken out of reset. */ #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Card going out of reset\n"); #endif elnkSoftReset(dev); } dev->AuxCmd.reset = auxcmd.val.reset; /* Update the reset bit, if nothing else. */ } /* If the card is in reset, stop right here. */ if (dev->fInReset) { #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Reset\n"); #endif return; } /* Evaluate DMA state. If it changed, we'll have to go back to R3. */ fDMAR = auxcmd.val.dma_req && auxcmd.val.ride; if (fDMAR != dev->fDMA) { /* Start/stop DMA as requested. */ dev->fDMA = fDMAR; if (fDMAR) { dma_set_drq(dev->dma_channel, fDMAR); mode = dma_mode(dev->dma_channel); #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: DMA Mode = %02x.\n", mode & 0x0c); #endif if ((mode & 0x0c) == 0x04) { while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) { dma_channel_write(dev->dma_channel, dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos]); dev->dma_pos++; } } else { while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) { int dma_data = dma_channel_read(dev->dma_channel); dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos] = dma_data & 0xff; dev->dma_pos++; } } dev->uGPBufPtr = (dev->uGPBufPtr + dev->dma_pos) & ELNK_GP_MASK; dma_set_drq(dev->dma_channel, 0); dev->dma_pos = 0; dev->IntrState.dma_intr = 1; dev->AuxStat.dma_done = 1; elnkUpdateIrq(dev); } #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: DMARQ for channel %u set to %u\n", dev->dma_channel, fDMAR); #endif } /* Interrupt enable changes. */ if ((dev->AuxCmd.ire != auxcmd.val.ire) || (dev->AuxCmd.ride != auxcmd.val.ride)) { dev->AuxStat.ride = dev->AuxCmd.ride = auxcmd.val.ride; dev->AuxCmd.ire = auxcmd.val.ire; /* NB: IRE is not visible in the aux status register. */ } /* DMA Request changes. */ if (dev->AuxCmd.dma_req != auxcmd.val.dma_req) { dev->AuxStat.dma_req = dev->AuxCmd.dma_req = auxcmd.val.dma_req; if (!auxcmd.val.dma_req) { /* Clearing the DMA Request bit also clears the DMA Done status bit and any DMA interrupt. */ dev->IntrState.dma_intr = 0; dev->AuxStat.dma_done = 0; } } /* Packet buffer control changes. */ if (dev->AuxCmd.buf_ctl != auxcmd.val.buf_ctl) { #ifdef ENABLE_3COM501_LOG static const char *apszBuffCntrl[4] = { "System", "Xmit then Recv", "Receive", "Loopback" }; threec501_log("3Com501: Packet buffer control `%s' -> `%s'\n", apszBuffCntrl[dev->AuxCmd.buf_ctl], apszBuffCntrl[auxcmd.val.buf_ctl]); #endif if (auxcmd.val.buf_ctl == EL_BCTL_XMT_RCV) { /* Transmit, then receive. */ fTransmit = true; dev->AuxStat.recv_bsy = 0; } else if (auxcmd.val.buf_ctl == EL_BCTL_SYSTEM) { dev->AuxStat.xmit_bsy = 1; /* Transmit Busy is set here and cleared once actual transmit completes. */ dev->AuxStat.recv_bsy = 0; } else if (auxcmd.val.buf_ctl == EL_BCTL_RECEIVE) { /* Special case: If going from xmit-then-receive mode to receive mode, and we received * a packet already (right after the receive), don't restart receive and lose the already * received packet. */ if (!dev->uRCVBufPtr) fReceive = true; } else { /* For loopback, we go through the regular transmit and receive path. That may be an * overkill but the receive path is too complex for a special loopback-only case. */ fTransmit = true; dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ } dev->AuxStat.buf_ctl = dev->AuxCmd.buf_ctl = auxcmd.val.buf_ctl; } /* NB: Bit 1 (xmit_bf, transmit packets with bad FCS) is a simple control * bit which does not require special handling here. Just copy it over. */ dev->AuxStat.xmit_bf = dev->AuxCmd.xmit_bf = auxcmd.val.xmit_bf; /* There are multiple bits that affect interrupt state. Handle them now. */ elnkUpdateIrq(dev); /* After fully updating register state, do a transmit (including loopback) or receive. */ if (fTransmit) elnkAsyncTransmit(dev); else if (fReceive) { dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ } } static uint8_t threec501_read(uint16_t addr, void *priv) { threec501_t *dev = (threec501_t *) priv; uint8_t retval = 0xff; switch (addr & 0x0f) { case 0x00: /* Receive status register aliases. The SEEQ 8001 */ case 0x02: /* EDLC clearly only decodes one bit for reads. */ case 0x04: case 0x06: /* Receive status register. */ retval = dev->RcvStatReg; dev->RcvStat.stale = 1; /* Allows further reception. */ dev->IntrState.recv_intr = 0; /* Reading clears receive interrupt. */ elnkUpdateIrq(dev); break; case 0x01: /* Transmit status register aliases. */ case 0x03: case 0x05: case 0x07: /* Transmit status register. */ retval = dev->XmitStatReg; dev->IntrState.xmit_intr = 0; /* Reading clears transmit interrupt. */ elnkUpdateIrq(dev); break; case 0x08: /* GP Buffer pointer LSB. */ retval = (dev->uGPBufPtr & 0xff); break; case 0x09: /* GP Buffer pointer MSB. */ retval = (dev->uGPBufPtr >> 8); break; case 0x0a: /* RCV Buffer pointer LSB. */ retval = (dev->uRCVBufPtr & 0xff); break; case 0x0b: /* RCV Buffer pointer MSB. */ retval = (dev->uRCVBufPtr >> 8); break; case 0x0c: /* Ethernet address PROM window. */ case 0x0d: /* Alias. */ /* Reads use low 3 bits of GP buffer pointer, no auto-increment. */ retval = dev->aPROM[dev->uGPBufPtr & 7]; break; case 0x0e: /* Auxiliary status register. */ retval = dev->AuxStatReg; break; case 0x0f: /* Buffer window. */ /* Reads use low 11 bits of GP buffer pointer, auto-increment. */ retval = dev->abPacketBuf[ELNK_GP(dev)]; dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK; break; default: break; } elnkUpdateIrq(dev); #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: read addr %x, value %x\n", addr & 0x0f, retval); #endif return retval; } static uint8_t threec501_nic_readb(uint16_t addr, void *priv) { return threec501_read(addr, priv); } static uint16_t threec501_nic_readw(uint16_t addr, void *priv) { return threec501_read(addr, priv) | (threec501_read(addr + 1, priv) << 8); } static void threec501_write(uint16_t addr, uint8_t value, void *priv) { threec501_t *dev = (threec501_t *) priv; int reg = (addr & 0x0f); switch (reg) { case 0x00: /* Six bytes of station address. */ case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: dev->aStationAddr[reg] = value; break; case 0x06: /* Receive command. */ dev->RcvCmdReg = value; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Receive Command register set to %02X\n", dev->RcvCmdReg); #endif break; case 0x07: /* Transmit command. */ dev->XmitCmdReg = value; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Transmit Command register set to %02X\n", dev->XmitCmdReg); #endif break; case 0x08: /* GP Buffer pointer LSB. */ dev->uGPBufPtr = (dev->uGPBufPtr & 0xff00) | value; break; case 0x09: /* GP Buffer pointer MSB. */ dev->uGPBufPtr = (dev->uGPBufPtr & 0x00ff) | (value << 8); break; case 0x0a: /* RCV Buffer pointer clear. */ dev->uRCVBufPtr = 0; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: RCV Buffer Pointer cleared (%02X)\n", value); #endif break; case 0x0b: /* RCV buffer pointer MSB. */ case 0x0c: /* Ethernet address PROM window. */ case 0x0d: /* Undocumented. */ #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: Writing read-only register %02X!\n", reg); #endif break; case 0x0e: /* Auxiliary Command (CSR). */ elnkCsrWrite(dev, value); break; case 0x0f: /* Buffer window. */ /* Writes use low 11 bits of GP buffer pointer, auto-increment. */ if (dev->AuxCmd.buf_ctl != EL_BCTL_SYSTEM) { /// @todo Does this still increment GPBufPtr? break; } dev->abPacketBuf[ELNK_GP(dev)] = value; dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK; break; default: break; } #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: write addr %x, value %x\n", reg, value); #endif } static void threec501_nic_writeb(uint16_t addr, uint8_t value, void *priv) { threec501_write(addr, value, priv); } static void threec501_nic_writew(uint16_t addr, uint16_t value, void *priv) { threec501_write(addr, value & 0xff, priv); threec501_write(addr + 1, value >> 8, priv); } static int elnkSetLinkState(void *priv, uint32_t link_state) { threec501_t *dev = (threec501_t *) priv; if (link_state & NET_LINK_TEMP_DOWN) { elnkTempLinkDown(dev); return 1; } bool link_up = !(link_state & NET_LINK_DOWN); if (dev->fLinkUp != link_up) { dev->fLinkUp = link_up; if (link_up) { dev->fLinkTempDown = 1; dev->cLinkDownReported = 0; dev->cLinkRestorePostponed = 0; timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); } else { dev->cLinkDownReported = 0; dev->cLinkRestorePostponed = 0; } } return 0; } static void elnkR3TimerRestore(void *priv) { threec501_t *dev = (threec501_t *) priv; if ((dev->cLinkDownReported <= ELNK_MAX_LINKDOWN_REPORTED) && (dev->cLinkRestorePostponed <= ELNK_MAX_LINKRST_POSTPONED)) { timer_advance_u64(&dev->timer_restore, 1500000 * TIMER_USEC); dev->cLinkRestorePostponed++; } else { dev->fLinkTempDown = 0; } } static void * threec501_nic_init(UNUSED(const device_t *info)) { uint32_t mac; threec501_t *dev; dev = malloc(sizeof(threec501_t)); memset(dev, 0x00, sizeof(threec501_t)); dev->maclocal[0] = 0x02; /* 02:60:8C (3Com OID) */ dev->maclocal[1] = 0x60; dev->maclocal[2] = 0x8C; dev->base_address = device_get_config_hex16("base"); dev->base_irq = device_get_config_int("irq"); dev->dma_channel = device_get_config_int("dma"); dev->fLinkUp = 1; dev->cMsLinkUpDelay = 5000; /* See if we have a local MAC address configured. */ mac = device_get_config_mac("mac", -1); /* * Make this device known to the I/O system. * PnP and PCI devices start with address spaces inactive. */ io_sethandler(dev->base_address, 0x10, threec501_nic_readb, threec501_nic_readw, NULL, threec501_nic_writeb, threec501_nic_writew, NULL, dev); /* Set up our BIA. */ if (mac & 0xff000000) { /* Generate new local MAC. */ dev->maclocal[3] = random_generate(); dev->maclocal[4] = random_generate(); dev->maclocal[5] = random_generate(); mac = (((int) dev->maclocal[3]) << 16); mac |= (((int) dev->maclocal[4]) << 8); mac |= ((int) dev->maclocal[5]); device_set_config_mac("mac", mac); } else { dev->maclocal[3] = (mac >> 16) & 0xff; dev->maclocal[4] = (mac >> 8) & 0xff; dev->maclocal[5] = (mac & 0xff); } /* Initialize the PROM */ memcpy(dev->aPROM, dev->maclocal, sizeof(dev->maclocal)); dev->aPROM[6] = dev->aPROM[7] = 0; /* The two padding bytes. */ #ifdef ENABLE_3COM501_LOG threec501_log("I/O=%04x, IRQ=%d, DMA=%d, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", dev->base_address, dev->base_irq, dev->dma_channel, dev->aPROM[0], dev->aPROM[1], dev->aPROM[2], dev->aPROM[3], dev->aPROM[4], dev->aPROM[5]); #endif /* Reset the board. */ elnkR3HardReset(dev); /* Attach ourselves to the network module. */ dev->netcard = network_attach(dev, dev->aPROM, elnkReceiveLocked, elnkSetLinkState); timer_add(&dev->timer_restore, elnkR3TimerRestore, dev, 0); return dev; } static void threec501_nic_close(void *priv) { threec501_t *dev = (threec501_t *) priv; #ifdef ENABLE_3COM501_LOG threec501_log("3Com501: closed\n"); #endif free(dev); } static const device_config_t threec501_config[] = { { .name = "base", .description = "Address", .type = CONFIG_HEX16, .default_string = "", .default_int = 0x300, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "0x280", .value = 0x280 }, { .description = "0x300", .value = 0x300 }, { .description = "0x310", .value = 0x310 }, { .description = "0x320", .value = 0x320 }, { .description = "" } }, }, { .name = "irq", .description = "IRQ", .type = CONFIG_SELECTION, .default_string = "", .default_int = 3, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "IRQ 2/9", .value = 9 }, { .description = "IRQ 3", .value = 3 }, { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 6", .value = 6 }, { .description = "IRQ 7", .value = 7 }, { .description = "" } }, }, { .name = "dma", .description = "DMA channel", .type = CONFIG_SELECTION, .default_string = "", .default_int = 3, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "DMA 1", .value = 1 }, { .description = "DMA 2", .value = 2 }, { .description = "DMA 3", .value = 3 }, { .description = "" } }, }, { .name = "mac", .description = "MAC Address", .type = CONFIG_MAC, .default_string = "", .default_int = -1 }, { .name = "", .description = "", .type = CONFIG_END } }; const device_t threec501_device = { .name = "3Com EtherLink (3c500/3c501)", .internal_name = "3c501", .flags = DEVICE_ISA, .local = 0, .init = threec501_nic_init, .close = threec501_nic_close, .reset = elnkR3Reset, { .available = NULL }, .speed_changed = NULL, .force_redraw = NULL, .config = threec501_config };