From b04aa3ce4ef90beac375390565c51162050b2b1e Mon Sep 17 00:00:00 2001 From: TC1995 Date: Mon, 21 Nov 2022 17:08:50 +0100 Subject: [PATCH] Ported the 3c500/3c501 nic from VirtualBox. Added IRQ's 10 and 11 to the AMD PCnet ISA/VLB nics. --- src/include/86box/net_3c501.h | 44 ++ src/network/CMakeLists.txt | 4 +- src/network/net_3c501.c | 1223 +++++++++++++++++++++++++++++++++ src/network/net_pcnet.c | 4 + src/network/network.c | 2 + src/win/Makefile.mingw | 2 +- 6 files changed, 1276 insertions(+), 3 deletions(-) create mode 100644 src/include/86box/net_3c501.h create mode 100644 src/network/net_3c501.c diff --git a/src/include/86box/net_3c501.h b/src/include/86box/net_3c501.h new file mode 100644 index 000000000..e45f9a82a --- /dev/null +++ b/src/include/86box/net_3c501.h @@ -0,0 +1,44 @@ +/* + * 86Box An emulator of (mostly) x86-based PC systems and devices, + * using the ISA, EISA, VLB, MCA, and PCI system buses, + * roughly spanning the era between 1981 and 1995. + * + * This file is part of the 86Box Project. + * + * 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. + */ +#ifndef NET_3C501_H +#define NET_3C501_H + +extern const device_t threec501_device; + +#endif /*NET_3C501_H*/ diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 16708d6c4..98252d49e 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -13,8 +13,8 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(net OBJECT network.c net_pcap.c net_slirp.c net_dp8390.c net_3c503.c - net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c) +add_library(net OBJECT network.c net_pcap.c net_slirp.c net_dp8390.c net_3c501.c + net_3c503.c net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c) option(SLIRP_EXTERNAL "Link against the system-provided libslirp library" OFF) mark_as_advanced(SLIRP_EXTERNAL) diff --git a/src/network/net_3c501.c b/src/network/net_3c501.c new file mode 100644 index 000000000..19ad587c2 --- /dev/null +++ b/src/network/net_3c501.c @@ -0,0 +1,1223 @@ +/* + * 86Box An emulator of (mostly) x86-based PC systems and devices, + * using the ISA, EISA, VLB, MCA, and PCI system buses, + * roughly spanning the era between 1981 and 1995. + * + * This file is part of the 86Box Project. + * + * 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> + +/* 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 { + 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) +{ + 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 }; + 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, is_bcast = 0, 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; + } + + 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; + } + +#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(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 +}; diff --git a/src/network/net_pcnet.c b/src/network/net_pcnet.c index d3c5e29b5..e1f62c874 100644 --- a/src/network/net_pcnet.c +++ b/src/network/net_pcnet.c @@ -3065,6 +3065,8 @@ static const device_config_t pcnet_isa_config[] = { { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 9", .value = 9 }, + { .description = "IRQ 10", .value = 10 }, + { .description = "IRQ 11", .value = 11 }, { .description = "" } }, }, @@ -3124,6 +3126,8 @@ static const device_config_t pcnet_vlb_config[] = { { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 9", .value = 9 }, + { .description = "IRQ 10", .value = 10 }, + { .description = "IRQ 11", .value = 11 }, { .description = "" } }, }, diff --git a/src/network/network.c b/src/network/network.c index 0fc137092..f6e9eb565 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -67,6 +67,7 @@ #include <86box/ui.h> #include <86box/timer.h> #include <86box/network.h> +#include <86box/net_3c501.h> #include <86box/net_3c503.h> #include <86box/net_ne2000.h> #include <86box/net_pcnet.h> @@ -95,6 +96,7 @@ static const device_t net_none_device = { static const device_t *net_cards[] = { &net_none_device, + &threec501_device, &threec503_device, &pcnet_am79c960_device, &pcnet_am79c961_device, diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 7d5621a8e..61ed25f9b 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -647,7 +647,7 @@ NETOBJ := network.o \ arp_table.o bootp.o cksum.o dnssearch.o if.o ip_icmp.o ip_input.o \ ip_output.o mbuf.o misc.o sbuf.o slirp.o socket.o tcp_input.o \ tcp_output.o tcp_subr.o tcp_timer.o udp.o util.o version.o \ - net_dp8390.o \ + net_dp8390.o net_3c501.o \ net_3c503.o net_ne2000.o \ net_pcnet.o net_wd8003.o \ net_plip.o net_event.o