From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Devices/Network/Dev3C501.cpp | 2721 +++++++++++++++++++++++++++++++++ 1 file changed, 2721 insertions(+) create mode 100644 src/VBox/Devices/Network/Dev3C501.cpp (limited to 'src/VBox/Devices/Network/Dev3C501.cpp') diff --git a/src/VBox/Devices/Network/Dev3C501.cpp b/src/VBox/Devices/Network/Dev3C501.cpp new file mode 100644 index 00000000..e6e141f1 --- /dev/null +++ b/src/VBox/Devices/Network/Dev3C501.cpp @@ -0,0 +1,2721 @@ +/* $Id: Dev3C501.cpp $ */ +/** @file + * Dev3C501 - 3Com EtherLink (3C501) Ethernet Adapter Emulation. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * 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, in version 3 of the + * License. + * + * 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, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_3c501 3Com 3C501 Ethernet Controller Emulation. + * + * This software was written based on the following documents: + * + * - 3Com IBM Ethernet (IE) Controller/Transceiver + * External Reference Specification, March 15, 1983 + * - 3Com EtherLink (3C501) Adapter Technical Reference + * Manual Part No. 6405-00, November 26, 1988 + * - SEEQ 8001 EDLC Ethernet Data Link Controller + * Preliminary Data Sheet, December 1982 + * + * The emulation is compatible with 3Com 3C501 EtherLink aka IE4. It also + * appears to be compatible with the original 1982 3C500 IBM Ethernet aka + * IE; the IE and IE4 documentation is nearly identical. + * + * The EtherLink is a very early design. It has only a single 2K buffer for + * both send and receive, and was desgined long before full-duplex Ethernet + * was possible (it is capable of simultaneous send and receive, but only in + * loopback mode). If it has just received a packet, the EtherLink can't + * receive another packet until the first one has been processed by the + * host. + * + * The above problem is greatly alleviated in a VM because incoming packets + * can be buffered for a short while and don't have to be immediately + * dropped just because the adapter is currently sending or because the + * receive status register has not been read yet. + * + * The first 8 registers (station address, receive and transmit command and + * status) are implemented in the SEEQ 8001 EDLC chip. The remaining 8 + * registers are provided by the 3Com ASIC (0755-02) on the 3C501 or + * discrete chips on the 3C500. + * + * The '16 collisions' bit in the transmit command/status register is nearly + * useless. The SEEQ 8001 could retransmit automatically, but the IE/IE4 can + * not because the GP Buffer Pointer needs to be reinitialized by software + * prior to each transmit attempt. It is unclear if the 16-collision counter + * only rolls over modulo 16 or if it is cleared by something other than + * reset. + * + * The 3C501 supports DMA transfers to/from the packet buffer. Many drivers + * don't use DMA by default or at all. Due to the overhead of programming + * the DMA controller, direct I/O access (rep insb/outsb) is always faster + * in a VM. DMA would only be a win for very ancient drivers which don't use + * the rep insb/outsb instructions (those didn't exist on the 8086/8088). + * + * NB: The default DMA channel (channel 1) conflicts with the default Sound + * Blaster settings. If both 3C501 and SB16 are used, then one of them + * either needs to be reconfigured to use DMA channel other than 1 or the + * 3C501 must not use DMA. + * + * The 3Com documentation implies that writing the low byte of the Receive + * Buffer Pointer is enough to clear the pointer. Yet almost all drivers, + * including 3Com's sample code, write zeros to both the low and high bytes + * of the Receive Buffer Pointer when clearing it. BSD drivers (if_el.c) + * notably only write the low byte. It has been verified on a real 3C501 + * that the documentation is correct. Writing anything to the Receive Buffer + * Pointer LSB clears the pointer (writing to the MSB appears to have no + * effect whatsoever). + * + * If the Receive Buffer Pointer is not explicitly cleared prior to + * receiving a packet, it will simply keep incrementing from wherever it + * was. Once it hits the end of the buffer (wraps around to zero), a + * receive overflow will be triggered (because the EDLC's FIFO will no + * longer be serviced) but the buffer will contain however much data there + * was room for. Note that the SEEQ 8001 datasheet is not explicit, but the + * EDLC can probably receive frames with more than 1,500 octets of payload. + * + * The GP Buffer Pointer behavior is quite curious. It appears to be + * internally a 12-bit pointer, and its top bit (that is, bit 11) is ignored + * when addressing into the 2K buffer. When writing the MSB, the top 5 bits + * are masked (always written as zero), i.e. only a 11-bit value can be + * written. Through auto-increment, the GP Buffer Pointer can reach values + * that can be read but not written. + * + * The implementation was tested for correctness using 3Com's diagnostic + * utility (3C501.EXE, Version 2.4, 1986 and also DIAGNOSE.COM, Version 2.0, + * 1983) and "passes diagnose with flying colors". Note that the interrupt + * test does not pass in V2.3 diagnostics by default because it writes an + * EOI to port 0F820h instead of 20h, relying on the system board to decode + * only the low 10 bits of the address. PCI-based systems decode all address + * bits and writes to address 0F820h do not reach the interrupt controller. + * The 3C501.EXE utility can be run with the '-i' switch to skip interrupt + * tests; the older DIAGNOSE.COM does not have that problem. In both + * versions, the preliminary test fails if the MAC address OID is not + * 02:60:8C (the utility thinks the PROM is corrupted). + * + * 3Com's XNS driver (ETH.SYS) likewise requires the OID to be 02:60:8C, + * otherwise the driver uses 00:00:00:00:00:00 as its MAC address, which is + * not something that produces useful results. Most old drivers (NetWare, + * NDIS, XENIX) don't care about the OID, but some (BSDs, Linux, some SCO + * UNIX versions) want to see the 3Com OID. + * + * The MS Networks Client setup also requires the OID to match 3Com's when + * detecting the hardware, but the actual NDIS driver does not care. Note + * that the setup fails to detect the emulated 3C501 at the default 0x300 + * base address, but finds it at 0x310 and other addresses. + * + * Note that especially newer Linux/BSD OSes are a lost cause. Their 3C501 + * drivers are very hard to configure, broken in various ways, and likely + * untested. For example the Linux driver clears the receive buffer pointer + * at the end of the interrupt handler, which may easily happen after a + * packet was already received. In FreeBSD 6.4, the kernel crashes when the + * el0 driver is loaded. In FreeBSD 5.0, the el0 driver sends packets and + * reads packets from the card, but the OS never sees any incoming data + * (even though the receive packet counter keeps going up). + * + * The precise receive logic (when a packet is copied to the buffer, when an + * interrupt is signaled, when receive goes idle) is difficult to understand + * from the 3Com documentation, but is extensively tested by the diagnostic + * utility. The SEEQ 8001 datasheet may be easier to understand than the + * EtherLink documentation. + * + * Some drivers (e.g. NetWare DOS IPX shell and ODI drivers) like to reset + * the chip more or less after every packet is sent or received. That leads + * to a situation where the NIC is briefly unable to receive anything. If we + * drop packets in that case, we end up with well over 10% packet loss and + * terrible performance. We have to hold off and not drop packets just + * because the receiver is disabled for a moment. + * + * Note that the reset bit in the auxiliary command register does not nearly + * reset the entire chip as the documentation suggests. It may only truly + * reset the SEEQ 8001 EDLC chip. It is impossible to say how going out of + * reset affects the auxiliary command register itself, since it must be + * written to exit the reset state. The reset bit clears the EDLC transmit + * and command registers, but not the programmed station address. It also + * does not disturb the packet buffer, and it does not clear the GP Buffer + * Pointer. + * + * The default EtherLink configuration uses I/O base 300h, IRQ 3, DMA + * channel 1. Prior to May 1983, the default IRQ was 5. On old EtherLink + * cards, the I/O address was configurable from 200h-3F0h in increments of + * 16, DMA 1 or 3, and IRQ 3 or 5. Newer EtherLinks (starting circa in 1984) + * in addition allow DMA 2 and IRQ 2, 4, 6, and 7. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_ELNK +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef IN_RING3 +# include +# include +# include +#endif + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +#define ELNK_SAVEDSTATE_VERSION 1 + +/** 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 2048u + +/* 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(pThis) ((pThis)->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 + +/* The EtherLink is an 8-bit adapter, hence DMA channels up to 3 are available. */ +#define ELNK_MAX_VALID_DMA 3 + + +/********************************************************************************************************************************* +* 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; + + +/** + * EtherLink 3C501 state. + */ +typedef struct ELNKSTATE +{ + /** Restore timer. + * This is used to disconnect and reconnect the link after a restore. */ + TMTIMERHANDLE hTimerRestore; + + /** Transmit signaller. */ + PDMTASKHANDLE hXmitTask; + /** Receive ready signaller. */ + PDMTASKHANDLE hCanRxTask; + + /** Internal interrupt flag. */ + bool fISR; + /** Internal DMA active flag. */ + bool fDMA; + /** Internal in-reset flag. */ + bool fInReset; + + /** The PROM contents. Only 8 bytes addressable, R/O. */ + uint8_t aPROM[8]; + + /** The station address programmed by the guest, W/O. */ + uint8_t aStationAddr[6]; + /** General Purpose (GP) Buffer Pointer, R/W. */ + uint16_t uGPBufPtr; + + /** Receive (RCV) Buffer Pointer, R/WC. */ + uint16_t uRCVBufPtr; + /** 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; + }; + + /** Base port of the I/O space region. */ + RTIOPORT IOPortBase; + /** The configured ISA IRQ. */ + uint8_t uIsaIrq; + /** The configured ISA DMA channel. */ + uint8_t uIsaDma; + /** If set the link is currently up. */ + bool fLinkUp; + /** If set the link is temporarily down because of a saved state load. */ + bool fLinkTempDown; + /** Number of times we've reported the link down. */ + uint16_t cLinkDownReported; + /** Number of times we've postponed the link restore. */ + uint16_t cLinkRestorePostponed; + + /** The "hardware" MAC address. */ + RTMAC MacConfigured; + /** Internal interrupt state. */ + union { + uint8_t IntrStateReg; + EL_INTR_STAT IntrState; + }; + + /** Set if ELNKSTATER3::pDrv is not NULL. */ + bool fDriverAttached; + /** The LED. */ + PDMLED Led; + /** Status LUN: The LED ports. */ + PDMILEDPORTS ILeds; + /** Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + /** Access critical section. */ + PDMCRITSECT CritSect; + /** Event semaphore for blocking on receive. */ + RTSEMEVENT hEventOutOfRxSpace; + /** We are waiting/about to start waiting for more receive buffers. */ + bool volatile fMaybeOutOfSpace; + + /* MS to wait before we enable the link. */ + uint32_t cMsLinkUpDelay; + /** The device instance number (for logging). */ + uint32_t iInstance; + + STAMCOUNTER StatReceiveBytes; + STAMCOUNTER StatTransmitBytes; + STAMCOUNTER StatPktsLostReset; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILEADV StatIOReadRZ; + STAMPROFILEADV StatIOReadR3; + STAMPROFILEADV StatIOWriteRZ; + STAMPROFILEADV StatIOWriteR3; + STAMPROFILEADV StatReceive; + STAMPROFILEADV StatTransmitR3; + STAMPROFILEADV StatTransmitRZ; + STAMPROFILE StatTransmitSendR3; + STAMPROFILE StatTransmitSendRZ; + STAMPROFILE StatRxOverflow; + STAMCOUNTER StatRxOverflowWakeup; + STAMPROFILEADV StatInterrupt; + STAMCOUNTER StatResets; + STAMCOUNTER StatDropPktAdrmDis; + STAMCOUNTER StatDropPktZeroLen; + STAMCOUNTER StatDropPktVMNotRunning; + STAMCOUNTER StatDropPktNoLink; + STAMCOUNTER StatDropPktStaleRcv; +#endif /* VBOX_WITH_STATISTICS */ + + /** ISA I/O ports. */ + IOMIOPORTHANDLE hIoPortsIsa; + + /** The loopback transmit buffer (avoid stack allocations). */ + uint8_t abLoopBuf[ELNK_BUF_SIZE]; + + /** The runt pad buffer (only really needs 60 bytes). */ + uint8_t abRuntBuf[64]; + + /** The packet buffer. */ + uint8_t abPacketBuf[ELNK_BUF_SIZE]; +} ELNKSTATE, *PELNKSTATE; + + +/** + * EtherLink state for ring-3. + * + * @implements PDMIBASE + * @implements PDMINETWORKDOWN + * @implements PDMINETWORKCONFIG + * @implements PDMILEDPORTS + */ +typedef struct ELNKSTATER3 +{ + /** Pointer to the device instance. */ + PPDMDEVINSR3 pDevIns; + /** Pointer to the connector of the attached network driver. */ + PPDMINETWORKUPR3 pDrv; + /** Pointer to the attached network driver. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** LUN\#0 + status LUN: The base interface. */ + PDMIBASE IBase; + /** LUN\#0: The network port interface. */ + PDMINETWORKDOWN INetworkDown; + /** LUN\#0: The network config port interface. */ + PDMINETWORKCONFIG INetworkConfig; + + /** Status LUN: The LED ports. */ + PDMILEDPORTS ILeds; + /** Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; +} ELNKSTATER3; +/** Pointer to an EtherLink state structure for ring-3. */ +typedef ELNKSTATER3 *PELNKSTATER3; + + +/** + * EtherLink state for ring-0. + */ +typedef struct ELNKSTATER0 +{ + /** Pointer to the connector of the attached network driver. */ + PPDMINETWORKUPR0 pDrv; +} ELNKSTATER0; +/** Pointer to an EtherLink state structure for ring-0. */ +typedef ELNKSTATER0 *PELNKSTATER0; + + +/** + * EtherLink state for raw-mode. + */ +typedef struct ELNKSTATERC +{ + /** Pointer to the connector of the attached network driver. */ + PPDMINETWORKUPRC pDrv; +} ELNKSTATERC; +/** Pointer to an EtherLink state structure for raw-mode. */ +typedef ELNKSTATERC *PELNKSTATERC; + + +/** The EtherLink state structure for the current context. */ +typedef CTX_SUFF(ELNKSTATE) ELNKSTATECC; +/** Pointer to an EtherLink state structure for the current + * context. */ +typedef CTX_SUFF(PELNKSTATE) PELNKSTATECC; + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +static int elnkAsyncTransmit(PPDMDEVINS pDevIns, PELNKSTATE pThis, PELNKSTATECC pThisCC, bool fOnWorkerThread); + +/** + * Checks if the link is up. + * @returns true if the link is up. + * @returns false if the link is down. + */ +DECLINLINE(bool) elnkIsLinkUp(PELNKSTATE pThis) +{ + return pThis->fDriverAttached && !pThis->fLinkTempDown && pThis->fLinkUp; +} + + +#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() + + +/** + * Check if incoming frame matches the station address. + */ +DECLINLINE(int) padr_match(PELNKSTATE pThis, const uint8_t *buf) +{ + struct ether_header *hdr = (struct ether_header *)buf; + int result; + + /* Checks own + broadcast as well as own + multicast. */ + result = (pThis->RcvCmd.adr_match >= EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, pThis->aStationAddr, 6); + + return result; +} + + +/** + * Check if incoming frame is an accepted broadcast frame. + */ +DECLINLINE(int) padr_bcast(PELNKSTATE pThis, 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 = (pThis->RcvCmd.adr_match == EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, aBCAST, 6); + return result; +} + + +/** + * Check if incoming frame is an accepted multicast frame. + */ +DECLINLINE(int) padr_mcast(PELNKSTATE pThis, const uint8_t *buf) +{ + struct ether_header *hdr = (struct ether_header *)buf; + int result = (pThis->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(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + bool fISR = false; + + STAM_PROFILE_ADV_START(&pThis->StatInterrupt, a); + + /* IRQ is active if any interrupt source is active and interrupts + * are enabled via RIDE or IRE. + */ + if (pThis->IntrStateReg && (pThis->AuxCmd.ride || pThis->AuxCmd.ire)) + fISR = true; + + Log2(("#%d set irq fISR=%d\n", pThis->iInstance, fISR)); + + /* The IRQ line typically does not change. */ + if (RT_UNLIKELY(fISR != pThis->fISR)) + { + Log(("#%d IRQ=%d, state=%d\n", pThis->iInstance, pThis->uIsaIrq, fISR)); + PDMDevHlpISASetIrq(pDevIns, pThis->uIsaIrq, fISR); + pThis->fISR = fISR; + } + STAM_PROFILE_ADV_STOP(&pThis->StatInterrupt, a); +} + + +/** + * Perform a software reset of the NIC. + */ +static void elnkSoftReset(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + LogFlowFunc(("#%d:\n", pThis->iInstance)); + + /* Clear some of the user-visible register state. */ + pThis->XmitCmdReg = 0; + pThis->XmitStatReg = 0; + pThis->RcvCmdReg = 0; + pThis->RcvStatReg = 0; + pThis->AuxCmdReg = 0; + pThis->AuxStatReg = 0; + + /* The "stale receive status" is cleared by receiving an "interesting" packet. */ + pThis->RcvStat.stale = 1; + + /* By virtue of setting the buffer control to system, transmit is set to busy. */ + pThis->AuxStat.xmit_bsy = 1; + + /* Clear internal interrupt state. */ + pThis->IntrStateReg = 0; + elnkUpdateIrq(pDevIns, pThis); + + /* 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. */ + pThis->fInReset = false; +} + +#ifdef IN_RING3 + +static DECLCALLBACK(void) elnkR3WakeupReceive(PPDMDEVINS pDevIns) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + STAM_COUNTER_INC(&pThis->StatRxOverflowWakeup); + if (pThis->hEventOutOfRxSpace != NIL_RTSEMEVENT) + RTSemEventSignal(pThis->hEventOutOfRxSpace); +} + +/** + * @callback_method_impl{FNPDMTASKDEV, + * Signal to R3 that NIC is ready to receive a packet. + */ +static DECLCALLBACK(void) elnkR3CanRxTaskCallback(PPDMDEVINS pDevIns, void *pvUser) +{ + RT_NOREF(pvUser); + elnkR3WakeupReceive(pDevIns); +} + +#endif /* IN_RING3 */ + + +/** + * Write incoming data into the packet buffer. + */ +static void elnkReceiveLocked(PPDMDEVINS pDevIns, PELNKSTATE pThis, const uint8_t *src, size_t cbToRecv, bool fLoopback) +{ + int is_padr = 0, is_bcast = 0, is_mcast = 0; + union { + uint8_t RcvStatNewReg; + EL_RCV_STAT RcvStatNew; + }; + + /* + * Drop all packets if the VM is not running yet/anymore. + */ + VMSTATE enmVMState = PDMDevHlpVMState(pDevIns); + if ( enmVMState != VMSTATE_RUNNING + && enmVMState != VMSTATE_RUNNING_LS) + { + STAM_COUNTER_INC(&pThis->StatDropPktVMNotRunning); + return; + } + + /* Drop everything if address matching is disabled. */ + if (RT_UNLIKELY(pThis->RcvCmd.adr_match == EL_ADRM_DISABLED)) + { + STAM_COUNTER_INC(&pThis->StatDropPktAdrmDis); + return; + } + + /* Drop zero-length packets (how does that even happen?). */ + if (RT_UNLIKELY(!cbToRecv)) + { + STAM_COUNTER_INC(&pThis->StatDropPktZeroLen); + return; + } + + /* + * Drop all packets if the cable is not connected (and not in loopback). + */ + if (RT_UNLIKELY(!elnkIsLinkUp(pThis) && !fLoopback)) + { + STAM_COUNTER_INC(&pThis->StatDropPktNoLink); + return; + } + + /* + * Do not receive further packets until receive status was read. + */ + if (RT_UNLIKELY(pThis->RcvStat.stale == 0)) + { + STAM_COUNTER_INC(&pThis->StatDropPktStaleRcv); + return; + } + + LogFlowFunc(("#%d: size on wire=%d, RCV ptr=%u\n", pThis->iInstance, cbToRecv, pThis->uRCVBufPtr)); + + /* + * Perform address matching. Packets which do not pass the address + * filter are always ignored. + */ + /// @todo cbToRecv must be 6 or more (complete address) + if ( pThis->RcvCmd.adr_match == EL_ADRM_PROMISC /* promiscuous enabled */ + || (is_padr = padr_match(pThis, src)) + || (is_bcast = padr_bcast(pThis, src)) + || (is_mcast = padr_mcast(pThis, src))) + { + uint8_t *dst = pThis->abPacketBuf + pThis->uRCVBufPtr; + + Log2Func(("#%d Packet passed address filter (is_padr=%d, is_bcast=%d, is_mcast=%d), size=%d\n", pThis->iInstance, cbToRecv, is_padr, is_bcast, is_mcast)); + + /* Receive status is evaluated from scratch. The stale bit must remain set until we know better. */ + RcvStatNewReg = 0; + RcvStatNew.stale = 1; + pThis->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 (RT_UNLIKELY(cbToRecv < 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(pThis->abRuntBuf, 0, sizeof(pThis->abRuntBuf)); + memcpy(pThis->abRuntBuf, src, cbToRecv); + cbToRecv = 60; + src = pThis->abRuntBuf; + } + else + { + LogFunc(("#%d runt, size=%d\n", pThis->iInstance, cbToRecv)); + 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 (pThis->uRCVBufPtr + cbToRecv <= ELNK_BUF_SIZE) + { + RcvStatNew.no_ovf = 1; + } + else + { + LogFunc(("#%d overflow, size=%d\n", pThis->iInstance, cbToRecv)); + RcvStatNew.oflow = 1; + } + + if (fLoopback && pThis->AuxCmd.xmit_bf) + { + LogFunc(("#%d bad FCS\n", pThis->iInstance)); + RcvStatNew.fcs = 1; + } + + /* Error-free packets are considered good. */ + if (RcvStatNew.no_ovf && !RcvStatNew.fcs && !RcvStatNew.runt) + RcvStatNew.good = 1; + + uint16_t cbCopy = (uint16_t)RT_MIN(ELNK_BUF_SIZE - pThis->uRCVBufPtr, cbToRecv); + + /* All packets that passed the address filter are copied to the buffer. */ + + STAM_REL_COUNTER_ADD(&pThis->StatReceiveBytes, cbCopy); + + /* 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. */ + pThis->uRCVBufPtr = (pThis->uRCVBufPtr + cbCopy) & ELNK_GP_MASK; + + Log2Func(("Received packet, size=%d, RP=%u\n", cbCopy, pThis->uRCVBufPtr)); + + /* + * 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.good && pThis->RcvCmd.acpt_good) + || (RcvStatNew.no_ovf && pThis->RcvCmd.det_eof) + || (RcvStatNew.runt && pThis->RcvCmd.det_runt) + || (RcvStatNew.dribble && pThis->RcvCmd.det_drbl) + || (RcvStatNew.fcs && pThis->RcvCmd.det_fcs) + || (RcvStatNew.oflow && pThis->RcvCmd.det_ofl)) + { + pThis->AuxStat.recv_bsy = 0; + pThis->IntrState.recv_intr = 1; + RcvStatNew.stale = 0; /* Prevents further receive until set again. */ + } + /* Finally update the receive status. */ + pThis->RcvStat = RcvStatNew; + + LogFlowFunc(("#%d: RcvCmd=%02X, RcvStat=%02X, RCVBufPtr=%u\n", pThis->iInstance, pThis->RcvCmdReg, pThis->RcvStatReg, pThis->uRCVBufPtr)); + elnkUpdateIrq(pDevIns, pThis); + } +} + + +/** + * Transmit data from the packet buffer. + * + * @returns VBox status code. VERR_TRY_AGAIN is returned if we're busy. + * + * @param pDevIns The device instance. + * @param pThis The EtherLink shared instance + * data. + * @param pThisCC The EtherLink state data for the + * current context. + * @param fOnWorkerThread Whether we're on a worker thread or on an EMT. + */ +static int elnkXmitBuffer(PPDMDEVINS pDevIns, PELNKSTATE pThis, PELNKSTATECC pThisCC, bool fOnWorkerThread) +{ + RT_NOREF_PV(fOnWorkerThread); + int rc; + + /* + * Grab the xmit lock of the driver as well as the 3C501 device state. + */ + PPDMINETWORKUP pDrv = pThisCC->pDrv; + if (pDrv) + { + rc = pDrv->pfnBeginXmit(pDrv, false /*fOnWorkerThread*/); + if (RT_FAILURE(rc)) + return rc; + } + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + if (RT_SUCCESS(rc)) + { + /** @todo check if we're supposed to suspend now. */ + /* + * Do the transmitting. + */ + int rc2 = elnkAsyncTransmit(pDevIns, pThis, pThisCC, false /*fOnWorkerThread*/); + AssertReleaseRC(rc2); + + /* + * Release the locks. + */ + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + } + else + AssertLogRelRC(rc); + if (pDrv) + pDrv->pfnEndXmit(pDrv); + + return rc; +} + + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNPDMTASKDEV, + * This is just a very simple way of delaying sending to R3. + */ +static DECLCALLBACK(void) elnkR3XmitTaskCallback(PPDMDEVINS pDevIns, void *pvUser) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + NOREF(pvUser); + + /* + * Transmit if we can. + */ + elnkXmitBuffer(pDevIns, pThis, pThisCC, true /*fOnWorkerThread*/); +} +#endif /* IN_RING3 */ + + +/** + * Allocates a scatter/gather buffer for a transfer. + * + * @returns See PPDMINETWORKUP::pfnAllocBuf. + * @param pThis The shared state data. + * @param pThisCC The current context state data. + * @param cbMin The minimum buffer size. + * @param fLoopback Set if we're in loopback mode. + * @param pSgLoop Pointer to stack storage for the loopback SG. + * @param ppSgBuf Where to return the SG buffer descriptor on success. + * Always set. + */ +DECLINLINE(int) elnkXmitAllocBuf(PELNKSTATE pThis, PELNKSTATECC pThisCC, size_t cbMin, bool fLoopback, + PPDMSCATTERGATHER pSgLoop, PPPDMSCATTERGATHER ppSgBuf) +{ + int rc; + + if (!fLoopback) + { + PPDMINETWORKUP pDrv = pThisCC->pDrv; + if (RT_LIKELY(pDrv)) + { + rc = pDrv->pfnAllocBuf(pDrv, cbMin, NULL /*pGso*/, ppSgBuf); + AssertMsg(rc == VINF_SUCCESS || rc == VERR_TRY_AGAIN || rc == VERR_NET_DOWN || rc == VERR_NO_MEMORY, ("%Rrc\n", rc)); + if (RT_FAILURE(rc)) + *ppSgBuf = NULL; + } + else + { + rc = VERR_NET_DOWN; + *ppSgBuf = NULL; + } + } + else + { + /* Fake loopback allocator. */ + pSgLoop->fFlags = PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_1; + pSgLoop->cbUsed = 0; + pSgLoop->cbAvailable = sizeof(pThis->abLoopBuf); + pSgLoop->pvAllocator = pThis; + pSgLoop->pvUser = NULL; + pSgLoop->cSegs = 1; + pSgLoop->aSegs[0].cbSeg = sizeof(pThis->abLoopBuf); + pSgLoop->aSegs[0].pvSeg = pThis->abLoopBuf; + *ppSgBuf = pSgLoop; + rc = VINF_SUCCESS; + } + return rc; +} + + +/** + * Sends the scatter/gather buffer. + * + * Wrapper around PDMINETWORKUP::pfnSendBuf, so check it out for the fine print. + * + * @returns See PDMINETWORKUP::pfnSendBuf. + * @param pDevIns The device instance. + * @param pThis The shared EtherLink state data. + * @param pThisCC The current context state data. + * @param fLoopback Set if we're in loopback mode. + * @param pSgBuf The SG to send. + * @param fOnWorkerThread Set if we're being called on a work thread. Clear + * if an EMT. + */ +DECLINLINE(int) elnkXmitSendBuf(PPDMDEVINS pDevIns, PELNKSTATE pThis, PELNKSTATECC pThisCC, bool fLoopback, PPDMSCATTERGATHER pSgBuf, bool fOnWorkerThread) +{ + int rc; + STAM_REL_COUNTER_ADD(&pThis->StatTransmitBytes, pSgBuf->cbUsed); + if (!fLoopback) + { + STAM_PROFILE_START(&pThis->CTX_SUFF_Z(StatTransmitSend), a); + if (pSgBuf->cbUsed > 70) /* unqualified guess */ + pThis->Led.Asserted.s.fWriting = pThis->Led.Actual.s.fWriting = 1; + + PPDMINETWORKUP pDrv = pThisCC->pDrv; + if (RT_LIKELY(pDrv)) + { + rc = pDrv->pfnSendBuf(pDrv, pSgBuf, fOnWorkerThread); + AssertMsg(rc == VINF_SUCCESS || rc == VERR_NET_DOWN || rc == VERR_NET_NO_BUFFER_SPACE, ("%Rrc\n", rc)); + } + else + rc = VERR_NET_DOWN; + + pThis->Led.Actual.s.fWriting = 0; + STAM_PROFILE_STOP(&pThis->CTX_SUFF_Z(StatTransmitSend), a); + } + else + { + /* Loopback, immediately send buffer to the receive path. */ + Assert(pSgBuf->pvAllocator == (void *)pThis); + pThis->Led.Asserted.s.fReading = pThis->Led.Actual.s.fReading = 1; + + LogFlowFunc(("#%d: loopback (%u bytes)\n", pThis->iInstance, pSgBuf->cbUsed)); + elnkReceiveLocked(pDevIns, pThis, pThis->abLoopBuf, pSgBuf->cbUsed, fLoopback); + pThis->Led.Actual.s.fReading = 0; + rc = VINF_SUCCESS; + } + return rc; +} + + +/** + * Reads the entire frame into the scatter gather buffer. + */ +DECLINLINE(void) elnkXmitRead(PPDMDEVINS pDevIns, PELNKSTATE pThis, const unsigned cbFrame, PPDMSCATTERGATHER pSgBuf) +{ + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); RT_NOREF(pDevIns); + Assert(pSgBuf->cbAvailable >= cbFrame); + + pSgBuf->cbUsed = cbFrame; + memcpy(pSgBuf->aSegs[0].pvSeg, &pThis->abPacketBuf[ELNK_GP(pThis)], cbFrame); +} + +/** + * Try to transmit a frame. + */ +static void elnkTransmit(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + + /* + * Transmit the packet if possible, defer it if we cannot do it + * in the current context. + */ +#if defined(IN_RING0) || defined(IN_RC) + if (!pThisCC->pDrv) + { + int rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hXmitTask); + AssertRC(rc); + } + else +#endif + { + int rc = elnkXmitBuffer(pDevIns, pThis, pThisCC, false /*fOnWorkerThread*/); + if (rc == VERR_TRY_AGAIN) + rc = VINF_SUCCESS; + AssertRC(rc); + } +} + + +/** + * If a packet is waiting, poke the receiving machinery. + * + * @threads EMT. + */ +static void elnkKickReceive(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + /* Some drivers (e.g. NetWare IPX shell/ODI drivers) first go to receive mode through + * the aux command register and only then enable address matching. + */ + if ((pThis->AuxStat.recv_bsy == 1) && (pThis->RcvCmd.adr_match != EL_ADRM_DISABLED)) + { + if (pThis->fMaybeOutOfSpace) + { +#ifdef IN_RING3 + elnkR3WakeupReceive(pDevIns); +#else + int rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hCanRxTask); + AssertRC(rc); +#endif + } + } + +} + +/** + * Try transmitting a frame. + * + * @threads TX or EMT. + */ +static int elnkAsyncTransmit(PPDMDEVINS pDevIns, PELNKSTATE pThis, PELNKSTATECC pThisCC, bool fOnWorkerThread) +{ + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + + /* + * Just drop it if not transmitting. Can happen with delayed transmits + * if transmit was disabled in the meantime. + */ + if (RT_UNLIKELY(!pThis->AuxStat.xmit_bsy)) + { + LogFunc(("#%d: Nope, xmit disabled (fOnWorkerThread=%RTbool)\n", pThis->iInstance, fOnWorkerThread)); + return VINF_SUCCESS; + } + + if (RT_UNLIKELY((pThis->AuxCmd.buf_ctl != EL_BCTL_XMT_RCV) && (pThis->AuxCmd.buf_ctl != EL_BCTL_LOOPBACK))) + { + LogFunc(("#%d: Nope, not in xmit-then-receive or loopback state (fOnWorkerThread=%RTbool)\n", pThis->iInstance, fOnWorkerThread)); + return VINF_SUCCESS; + } + + /* + * Blast out data from the packet buffer. + */ + int rc; + STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatTransmit), a); + do + { + /* Don't send anything when the link is down. */ + if (RT_UNLIKELY( !elnkIsLinkUp(pThis) + && pThis->cLinkDownReported > ELNK_MAX_LINKDOWN_REPORTED) + ) + break; + + bool const fLoopback = pThis->AuxCmd.buf_ctl == EL_BCTL_LOOPBACK; + PDMSCATTERGATHER SgLoop; + PPDMSCATTERGATHER pSgBuf; + + /* + * Sending is easy peasy, there is by definition always + * a complete packet on hand. + */ + const unsigned cb = ELNK_BUF_SIZE - ELNK_GP(pThis); /* Packet size. */ + LogFunc(("#%d: cb=%d\n", pThis->iInstance, cb)); + + pThis->XmitStatReg = 0; /* Clear transmit status before filling it out. */ + + if (RT_LIKELY(elnkIsLinkUp(pThis) || fLoopback)) + { + if (RT_LIKELY(cb <= MAX_FRAME)) + { + rc = elnkXmitAllocBuf(pThis, pThisCC, cb, fLoopback, &SgLoop, &pSgBuf); + if (RT_SUCCESS(rc)) + { + elnkXmitRead(pDevIns, pThis, cb, pSgBuf); + rc = elnkXmitSendBuf(pDevIns, pThis, pThisCC, fLoopback, pSgBuf, fOnWorkerThread); + Log2Func(("#%d: rc=%Rrc\n", pThis->iInstance, rc)); + } + else if (rc == VERR_TRY_AGAIN) + { + STAM_PROFILE_ADV_STOP(&pThis->CTX_SUFF_Z(StatTransmit), a); + LogFunc(("#%d: rc=%Rrc\n", pThis->iInstance, rc)); + return VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + pThis->XmitStat.ready = 1; + else + pThis->XmitStat.coll = 1; /* Pretend there was a collision. */ + } + else + { + /* Signal error, as this violates the Ethernet specs. */ + /** @todo check if the correct error is generated. */ + LogRel(("3C501#%d: illegal giant frame (%u bytes) -> signalling error\n", pThis->iInstance, cb)); + } + } + else + { + /* Signal a transmit error pretending there was a collision. */ + pThis->cLinkDownReported++; + pThis->XmitStat.coll = 1; + } + /* Transmit officially done, update register state. */ + pThis->AuxStat.xmit_bsy = 0; + pThis->IntrState.xmit_intr = !!(pThis->XmitCmdReg & pThis->XmitStatReg); + LogFlowFunc(("#%d: XmitCmd=%02X, XmitStat=%02X\n", pThis->iInstance, pThis->XmitCmdReg, pThis->XmitStatReg)); + + /* NB: After a transmit, the GP Buffer Pointer points just past + * the end of the packet buffer (3C501 diagnostics). + */ + pThis->uGPBufPtr = ELNK_BUF_SIZE; + + /* NB: The buffer control does *not* change to Receive and stays the way it was. */ + if (RT_UNLIKELY(!fLoopback)) + { + pThis->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + elnkKickReceive(pDevIns, pThis); + } + } while (0); /* No loop, because there isn't ever more than one packet to transmit. */ + + elnkUpdateIrq(pDevIns, pThis); + + STAM_PROFILE_ADV_STOP(&pThis->CTX_SUFF_Z(StatTransmit), a); + + return VINF_SUCCESS; +} + +/* -=-=-=-=-=- I/O Port access -=-=-=-=-=- */ + + +static int elnkCsrWrite(PPDMDEVINS pDevIns, PELNKSTATE pThis, uint8_t data) +{ + int rc = VINF_SUCCESS; + bool fTransmit = false; + bool fReceive = false; + bool fDMAR; + union { + uint8_t reg; + EL_AUX_CMD val; + }; + + reg = data; + + /* Handle reset first. */ + if (pThis->AuxCmd.reset != val.reset) + { + if (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. + */ + LogFunc(("#%d: Card going into reset\n", pThis->iInstance)); + pThis->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. + */ + if (RT_UNLIKELY(!pThis->RcvStat.stale)) + STAM_REL_COUNTER_INC(&pThis->StatPktsLostReset); + } + else + { + /* Card is being taken out of reset. */ + LogFunc(("#%d: Card going out of reset\n", pThis->iInstance)); + STAM_COUNTER_INC(&pThis->StatResets); + elnkSoftReset(pDevIns, pThis); + } + pThis->AuxCmd.reset = val.reset; /* Update the reset bit, if nothing else. */ + } + + /* If the card is in reset, stop right here. */ + if (pThis->fInReset) + return rc; + + /* Evaluate DMA state. If it changed, we'll have to go back to R3. */ + fDMAR = val.dma_req && val.ride; + if (fDMAR != pThis->fDMA) +#ifdef IN_RING3 + { + /* Start/stop DMA as requested. */ + pThis->fDMA = fDMAR; + PDMDevHlpDMASetDREQ(pDevIns, pThis->uIsaDma, fDMAR); + if (fDMAR) + PDMDevHlpDMASchedule(pDevIns); + Log(("3C501#%d: DMARQ for channel %u set to %u\n", pThis->iInstance, pThis->uIsaDma, fDMAR)); + } +#else + return VINF_IOM_R3_IOPORT_WRITE; +#endif + + /* Interrupt enable changes. */ + if ((pThis->AuxCmd.ire != val.ire) || (pThis->AuxCmd.ride != val.ride)) + { + pThis->AuxStat.ride = pThis->AuxCmd.ride = val.ride; + pThis->AuxCmd.ire = val.ire; /* NB: IRE is not visible in the aux status register. */ + } + + /* DMA Request changes. */ + if (pThis->AuxCmd.dma_req != val.dma_req) + { + pThis->AuxStat.dma_req = pThis->AuxCmd.dma_req = val.dma_req; + if (!val.dma_req) + { + /* Clearing the DMA Request bit also clears the DMA Done status bit and any DMA interrupt. */ + pThis->IntrState.dma_intr = 0; + pThis->AuxStat.dma_done = 0; + } + } + + /* Packet buffer control changes. */ + if (pThis->AuxCmd.buf_ctl != val.buf_ctl) + { +#ifdef LOG_ENABLED + static const char *apszBuffCntrl[4] = { "System", "Xmit then Recv", "Receive", "Loopback" }; + Log(("3C501#%d: Packet buffer control `%s' -> `%s'\n", pThis->iInstance, apszBuffCntrl[pThis->AuxCmd.buf_ctl], apszBuffCntrl[val.buf_ctl])); +#endif + if (val.buf_ctl == EL_BCTL_XMT_RCV) + { + /* Transmit, then receive. */ + Log2(("3C501#%d: Transmit %u bytes\n%Rhxs\nxmit_bsy=%u\n", pThis->iInstance, ELNK_BUF_SIZE - pThis->uGPBufPtr, &pThis->abPacketBuf[pThis->uGPBufPtr], pThis->AuxStat.xmit_bsy)); + fTransmit = true; + pThis->AuxStat.recv_bsy = 0; + } + else if (val.buf_ctl == EL_BCTL_SYSTEM) + { + pThis->AuxStat.xmit_bsy = 1; /* Transmit Busy is set here and cleared once actual transmit completes. */ + pThis->AuxStat.recv_bsy = 0; + } + else if (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 (!pThis->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; + pThis->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + } + pThis->AuxStat.buf_ctl = pThis->AuxCmd.buf_ctl = 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. + */ + pThis->AuxStat.xmit_bf = pThis->AuxCmd.xmit_bf = val.xmit_bf; + + /* There are multiple bits that affect interrupt state. Handle them now. */ + elnkUpdateIrq(pDevIns, pThis); + + /* After fully updating register state, do a transmit (including loopback) or receive. */ + if (fTransmit) + elnkTransmit(pDevIns, pThis); + else if (fReceive) + { + pThis->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + elnkKickReceive(pDevIns, pThis); + } + + return rc; +} + +static int elIoWrite(PPDMDEVINS pDevIns, PELNKSTATE pThis, uint32_t addr, uint32_t val) +{ + int reg = addr & 0xf; + int rc = VINF_SUCCESS; + + Log2Func(("#%d: addr=%#06x val=%#04x\n", pThis->iInstance, addr, val & 0xff)); + + switch (reg) + { + case 0x00: /* Six bytes of station address. */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + pThis->aStationAddr[reg] = val; + break; + + case 0x06: /* Receive command. */ + { + EL_RCV_CMD OldRcvCmd = pThis->RcvCmd; + + pThis->RcvCmdReg = val; + /* If address filter just got enabled, receive may need a kick. */ + if (OldRcvCmd.adr_match == EL_ADRM_DISABLED && pThis->RcvCmd.adr_match != EL_ADRM_DISABLED) + elnkKickReceive(pDevIns, pThis); + Log2(("Receive Command register set to %02X\n", pThis->RcvCmdReg)); + break; + } + + case 0x07: /* Transmit command. */ + pThis->XmitCmdReg = val; + Log2(("Transmit Command register set to %02X\n", pThis->XmitCmdReg)); + break; + + case 0x08: /* GP Buffer pointer LSB. */ + pThis->uGPBufPtr = (pThis->uGPBufPtr & 0xff00) | (uint8_t)val; + Log2(("GP Buffer Pointer LSB write, now %u\n", pThis->uGPBufPtr)); + break; + + case 0x09: /* GP Buffer pointer MSB. */ + pThis->uGPBufPtr = ((uint8_t)val << 8) | RT_LOBYTE(pThis->uGPBufPtr); + Log2(("GP Buffer Pointer MSB write, now %u\n", pThis->uGPBufPtr)); + break; + + case 0x0a: /* RCV Buffer pointer clear. */ + pThis->uRCVBufPtr = 0; + Log2(("RCV Buffer Pointer cleared (%02X)\n", val)); + break; + + case 0x0b: /* RCV buffer pointer MSB. */ + case 0x0c: /* Ethernet address PROM window. */ + case 0x0d: /* Undocumented. */ + Log(("Writing read-only register %02X!\n", reg)); + break; + + case 0x0e: /* Auxiliary Command (CSR). */ + rc = elnkCsrWrite(pDevIns, pThis, val); + break; + + case 0x0f: /* Buffer window. */ + /* Writes use low 11 bits of GP buffer pointer, auto-increment. */ + if (pThis->AuxCmd.buf_ctl != EL_BCTL_SYSTEM) + { + Log(("Packet buffer write ignored, buf_ctl=%u!\n", pThis->AuxCmd.buf_ctl)); + /// @todo Does this still increment GPBufPtr? + break; + } + pThis->abPacketBuf[ELNK_GP(pThis)] = val; + pThis->uGPBufPtr = (pThis->uGPBufPtr + 1) & ELNK_GP_MASK; + break; + } + + return rc; +} + +static uint32_t elIoRead(PPDMDEVINS pDevIns, PELNKSTATE pThis, uint32_t addr, int *pRC) +{ + uint32_t val = UINT32_MAX; + + *pRC = VINF_SUCCESS; + + 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. */ + val = pThis->RcvStatReg; + pThis->RcvStat.stale = 1; /* Allows further reception. */ + pThis->IntrState.recv_intr = 0; /* Reading clears receive interrupt. */ + elnkUpdateIrq(pDevIns, pThis); + break; + + case 0x01: /* Transmit status register aliases. */ + case 0x03: + case 0x05: + case 0x07: /* Transmit status register. */ + val = pThis->XmitStatReg; + pThis->IntrState.xmit_intr = 0; /* Reading clears transmit interrupt. */ + elnkUpdateIrq(pDevIns, pThis); + break; + + case 0x08: /* GP Buffer pointer LSB. */ + val = RT_LOBYTE(pThis->uGPBufPtr); + break; + + case 0x09: /* GP Buffer pointer MSB. */ + val = RT_HIBYTE(pThis->uGPBufPtr); + break; + + case 0x0a: /* RCV Buffer pointer LSB. */ + val = RT_LOBYTE(pThis->uRCVBufPtr); + break; + + case 0x0b: /* RCV Buffer pointer MSB. */ + val = RT_HIBYTE(pThis->uRCVBufPtr); + break; + + case 0x0c: /* Ethernet address PROM window. */ + case 0x0d: /* Alias. */ + /* Reads use low 3 bits of GP buffer pointer, no auto-increment. */ + val = pThis->aPROM[pThis->uGPBufPtr & 7]; + break; + + case 0x0e: /* Auxiliary status register. */ + val = pThis->AuxStatReg; + break; + + case 0x0f: /* Buffer window. */ + /* Reads use low 11 bits of GP buffer pointer, auto-increment. */ + val = pThis->abPacketBuf[ELNK_GP(pThis)]; + pThis->uGPBufPtr = (pThis->uGPBufPtr + 1) & ELNK_GP_MASK; + break; + } + + elnkUpdateIrq(pDevIns, pThis); + + Log2Func(("#%d: addr=%#06x val=%#04x\n", pThis->iInstance, addr, val & 0xff)); + return val; +} + + +/** + * @callback_method_impl{FNIOMIOPORTIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) +elnkIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + int rc = VINF_SUCCESS; + uint8_t u8Lo, u8Hi; + STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatIORead), a); + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + RT_NOREF_PV(pvUser); + + switch (cb) + { + case 1: + *pu32 = elIoRead(pDevIns, pThis, Port, &rc); + break; + case 2: + /* Manually split word access. */ + u8Lo = elIoRead(pDevIns, pThis, Port + 0, &rc); + Assert(RT_SUCCESS(rc)); + u8Hi = elIoRead(pDevIns, pThis, Port + 1, &rc); + Assert(RT_SUCCESS(rc)); + *pu32 = RT_MAKE_U16(u8Lo, u8Hi); + break; + default: + rc = PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, + "elnkIOPortRead: unsupported operation size: offset=%#10x cb=%u\n", + Port, cb); + } + + Log2Func(("#%d: Port=%RTiop *pu32=%#RX32 cb=%d rc=%Rrc\n", pThis->iInstance, Port, *pu32, cb, rc)); + STAM_PROFILE_ADV_STOP(&pThis->CTX_SUFF_Z(StatIORead), a); + return rc; +} + + +/** + * @callback_method_impl{FNIOMIOPORTOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) +elnkIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + int rc = VINF_SUCCESS; + STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatIOWrite), a); + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + RT_NOREF_PV(pvUser); + + switch (cb) + { + case 1: + rc = elIoWrite(pDevIns, pThis, Port, RT_LOBYTE(u32)); + break; + case 2: + /* Manually split word access. */ + rc = elIoWrite(pDevIns, pThis, Port + 0, RT_LOBYTE(u32)); + if (!RT_SUCCESS(rc)) + break; + rc = elIoWrite(pDevIns, pThis, Port + 1, RT_HIBYTE(u32)); + break; + default: + rc = PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, + "elnkIOPortWrite: unsupported operation size: offset=%#10x cb=%u\n", + Port, cb); + } + + Log2Func(("#%d: Port=%RTiop u32=%#RX32 cb=%d rc=%Rrc\n", pThis->iInstance, Port, u32, cb, rc)); + STAM_PROFILE_ADV_STOP(&pThis->CTX_SUFF_Z(StatIOWrite), a); + return rc; +} + + +#ifdef IN_RING3 + +/* Shamelessly stolen from DevDMA.cpp */ + +/* Test the decrement bit of mode register. */ +#define IS_MODE_DEC(c) ((c) & 0x20) +/* Test the auto-init bit of mode register. */ +#define IS_MODE_AI(c) ((c) & 0x10) +/* Extract the transfer type bits of mode register. */ +#define GET_MODE_XTYP(c) (((c) & 0x0c) >> 2) + +/* DMA transfer modes. */ +enum { + DMODE_DEMAND, /* Demand transfer mode. */ + DMODE_SINGLE, /* Single transfer mode. */ + DMODE_BLOCK, /* Block transfer mode. */ + DMODE_CASCADE /* Cascade mode. */ +}; + +/* DMA transfer types. */ +enum { + DTYPE_VERIFY, /* Verify transfer type. */ + DTYPE_WRITE, /* Write transfer type. */ + DTYPE_READ, /* Read transfer type. */ + DTYPE_ILLEGAL /* Undefined. */ +}; + +static DECLCALLBACK(uint32_t) elnkR3DMAXferHandler(PPDMDEVINS pDevIns, void *opaque, + unsigned nchan, uint32_t dma_pos, uint32_t dma_len) +{ + RT_NOREF(pDevIns); + PELNKSTATE pThis = (PELNKSTATE)opaque; + int dma_mode; + int dma_type; + uint32_t cbToXfer; + uint32_t cbXferred; + uint16_t uLastPos; + int rc; + + /* + * The 3C501 EtherLink uses DMA as an alternative to accessing + * the buffer window register. The GP Buffer Pointer controls + * the address into the packet buffer for both writing to and + * reading from the buffer. + */ + dma_mode = PDMDevHlpDMAGetChannelMode(pDevIns, pThis->uIsaDma); + dma_type = GET_MODE_XTYP(dma_mode); + LogFlowFunc(("dma_mode=%d, dma_type=%d, dma_pos=%u, dma_len=%u, GPBP=%u\n", dma_mode, dma_type, dma_pos, dma_len, pThis->uGPBufPtr)); + + cbToXfer = dma_len; + + if (dma_type == DTYPE_WRITE) + { + /* Write transfer type. Reading from device, writing to memory. */ + rc = PDMDevHlpDMAWriteMemory(pDevIns, nchan, + &pThis->abPacketBuf[ELNK_GP(pThis)], + dma_pos, cbToXfer, &cbXferred); + AssertMsgRC(rc, ("DMAWriteMemory -> %Rrc\n", rc)); + uLastPos = pThis->uRCVBufPtr; + } + else + { + /* Read of Verify transfer type. Reading from memory, writing to device. */ + rc = PDMDevHlpDMAReadMemory(pDevIns, nchan, + &pThis->abPacketBuf[ELNK_GP(pThis)], + dma_pos, cbToXfer, &cbXferred); + AssertMsgRC(rc, ("DMAReadMemory -> %Rrc\n", rc)); + uLastPos = 0; /* Stop when buffer address wraps back to zero. */ + } + Log2Func(("After DMA transfer: GPBufPtr=%u, lastpos=%u, cbXferred=%u\n", pThis->uGPBufPtr, uLastPos, cbXferred)); + + /* Advance the GP buffer pointer and see if transfer completed (it almost certainly did). */ + pThis->uGPBufPtr = (pThis->uGPBufPtr + cbXferred) & ELNK_GP_MASK; + if (ELNK_GP(pThis) == uLastPos || 1) + { + Log2(("DMA completed\n")); + PDMDevHlpDMASetDREQ(pDevIns, pThis->uIsaDma, 0); + pThis->IntrState.dma_intr = 1; + pThis->AuxStat.dma_done = 1; + elnkUpdateIrq(pDevIns, pThis); + } + else + { + Log(("DMA continuing: GPBufPtr=%u, lastpos=%u, cbXferred=%u\n", pThis->uGPBufPtr, uLastPos, cbXferred)); + PDMDevHlpDMASchedule(pDevIns); + } + + /* Returns the updated transfer count. */ + return dma_pos + cbXferred; +} + + +/* -=-=-=-=-=- Timer Callbacks -=-=-=-=-=- */ + +/** + * @callback_method_impl{FNTMTIMERDEV, Restore timer callback} + * + * This is only called when we restore a saved state and temporarily + * disconnected the network link to inform the guest that network connections + * should be considered lost. + */ +static DECLCALLBACK(void) elnkR3TimerRestore(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + RT_NOREF(pvUser); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertReleaseRC(rc); + + rc = VERR_GENERAL_FAILURE; + + /* The EhterLink cards have no concept of a link state, and cables were assumed to be + * permanently attached (AUI or BNC). We can simulate a disconnected cable by reporting + * collisions on transmit, but a guest that waits to receive something will never know. + * For that reason, the link is temporarily down, we will only postpone restoring it + * a couple of times, and then reconnect regardless of whether the guest noticed + * anything or not. + */ + if ( (pThis->cLinkDownReported <= ELNK_MAX_LINKDOWN_REPORTED) + && (pThis->cLinkRestorePostponed <= ELNK_MAX_LINKRST_POSTPONED)) + rc = PDMDevHlpTimerSetMillies(pDevIns, hTimer, 1500); + if (RT_FAILURE(rc)) + { + pThis->fLinkTempDown = false; + if (pThis->fLinkUp) + { + LogRel(("3C501#%d: The link is back up again after the restore.\n", + pThis->iInstance)); + LogFunc(("#%d: cLinkDownReported=%d\n", + pThis->iInstance, pThis->cLinkDownReported)); + pThis->Led.Actual.s.fError = 0; + } + } + else + { + LogFunc(("#%d: cLinkDownReported=%d, cLinkRestorePostponed=%d, wait another 1500ms...\n", + pThis->iInstance, pThis->cLinkDownReported, pThis->cLinkRestorePostponed)); + pThis->cLinkRestorePostponed++; + } + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); +} + + +/* -=-=-=-=-=- Debug Info Handler -=-=-=-=-=- */ + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) elnkR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + bool fStationAddr = false; + bool fRecvBuffer = false; + bool fSendBuffer = false; + static const char *apszAddrMatch[4] = { "Disabled", "Promiscuous", "Broadcast", "Multicast" }; + static const char *apszBuffCntrl[4] = { "System", "Xmit then Recv", "Receive", "Loopback" }; + /* + * Parse args. + */ + if (pszArgs) + { + fStationAddr = strstr(pszArgs, "verbose") || strstr(pszArgs, "addr"); + fRecvBuffer = strstr(pszArgs, "verbose") || strstr(pszArgs, "recvbuf"); + fSendBuffer = strstr(pszArgs, "verbose") || strstr(pszArgs, "sendbuf"); + } + + /* + * Show info. + */ + pHlp->pfnPrintf(pHlp, + "3C501 #%d: port=%RTiop IRQ=%u DMA=%u mac-cfg=%RTmac%s%s %s\n", + pThis->iInstance, + pThis->IOPortBase, pThis->uIsaIrq, pThis->uIsaDma, &pThis->MacConfigured, + pDevIns->fRCEnabled ? " RC" : "", pDevIns->fR0Enabled ? " RZ" : "", + pThis->fDriverAttached ? "attached" : "unattached!"); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_INTERNAL_ERROR); /* Take it here so we know why we're hanging... */ + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CritSect, rcLock); + + pHlp->pfnPrintf(pHlp, " GP Buf Ptr : %u (masked %u)\n", pThis->uGPBufPtr, ELNK_GP(pThis)); + pHlp->pfnPrintf(pHlp, " RCV Buf Ptr: %u\n", pThis->uRCVBufPtr); + pHlp->pfnPrintf(pHlp, " Recv Command: %02X Recv Status: %02X\n", pThis->RcvCmdReg, pThis->RcvStatReg); + pHlp->pfnPrintf(pHlp, " Xmit Command: %02X Xmit Status: %02X\n", pThis->XmitCmdReg, pThis->XmitStatReg); + pHlp->pfnPrintf(pHlp, " Aux Command: %02X Aux Status: %02X\n", pThis->AuxCmdReg, pThis->AuxStatReg); + + pHlp->pfnPrintf(pHlp, " Address matching: %s\n", apszAddrMatch[pThis->RcvCmd.adr_match]); + pHlp->pfnPrintf(pHlp, " Buffer control : %s\n", apszBuffCntrl[pThis->AuxCmd.buf_ctl]); + pHlp->pfnPrintf(pHlp, " Interrupt state : xmit=%u recv=%u dma=%u\n", pThis->IntrState.xmit_intr, pThis->IntrState.recv_intr, pThis->IntrState.dma_intr); + if (pThis->fLinkTempDown) + { + pHlp->pfnPrintf(pHlp, " Link down count : %d\n", pThis->cLinkDownReported); + pHlp->pfnPrintf(pHlp, " Postpone count : %d\n", pThis->cLinkRestorePostponed); + } + + /* Dump the station address. */ + if (fStationAddr) + { + pHlp->pfnPrintf(pHlp, " Station address : %RTmac\n", &pThis->aStationAddr); + } + + /* Dump the beginning of the send buffer. */ + if (fSendBuffer) + { + pHlp->pfnPrintf(pHlp, "Send buffer (start at %u):\n", ELNK_GP(pThis)); + unsigned dump_end = RT_MIN((ELNK_GP(pThis)) + 64, sizeof(pThis->abPacketBuf) - 16); + for (unsigned ofs = ELNK_GP(pThis); ofs < dump_end; ofs += 16) + pHlp->pfnPrintf(pHlp, " %04X: %Rhxs\n", ofs, &pThis->abPacketBuf[ofs]); + pHlp->pfnPrintf(pHlp, "pktbuf at %p, end at %p\n", &pThis->abPacketBuf[ELNK_GP(pThis)], &pThis->abPacketBuf[ELNK_BUF_SIZE]); + } + + /* Dump the beginning of the receive buffer. */ + if (fRecvBuffer) + { + pHlp->pfnPrintf(pHlp, "Receive buffer (start at 0):\n"); + unsigned dump_end = RT_MIN(pThis->uRCVBufPtr, 64); + for (unsigned ofs = 0; ofs < dump_end; ofs += 16) + pHlp->pfnPrintf(pHlp, " %04X: %Rhxs\n", ofs, &pThis->abPacketBuf[ofs]); + pHlp->pfnPrintf(pHlp, "pktbuf at %p, end at %p\n", pThis->abPacketBuf, &pThis->abPacketBuf[pThis->uRCVBufPtr]); + } + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); +} + + +/* -=-=-=-=-=- Helper(s) -=-=-=-=-=- */ + + +static void elnkR3HardReset(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + LogFlowFunc(("#%d:\n", pThis->iInstance)); + + /* Initialize the PROM */ + Assert(sizeof(pThis->MacConfigured) == 6); + memcpy(pThis->aPROM, &pThis->MacConfigured, sizeof(pThis->MacConfigured)); + pThis->aPROM[6] = pThis->aPROM[7] = 0; /* The two padding bytes. */ + + /* Clear the packet buffer and station address. */ + memset(pThis->abPacketBuf, 0, sizeof(pThis->abPacketBuf)); + memset(pThis->aStationAddr, 0, sizeof(pThis->aStationAddr)); + + /* Reset the buffer pointers. */ + pThis->uGPBufPtr = 0; + pThis->uRCVBufPtr = 0; + + elnkSoftReset(pDevIns, pThis); +} + +/** + * 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 pDevIns The device instance. + * @param pThis The device instance data. + */ +static void elnkTempLinkDown(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + if (pThis->fLinkUp) + { + pThis->fLinkTempDown = true; + pThis->cLinkDownReported = 0; + pThis->cLinkRestorePostponed = 0; + pThis->Led.Asserted.s.fError = pThis->Led.Actual.s.fError = 1; + int rc = PDMDevHlpTimerSetMillies(pDevIns, pThis->hTimerRestore, pThis->cMsLinkUpDelay); + AssertRC(rc); + } +} + + +/* -=-=-=-=-=- Saved State -=-=-=-=-=- */ + +/** + * @callback_method_impl{FNSSMDEVLIVEEXEC, Pass 0 only.} + */ +static DECLCALLBACK(int) elnkLiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) +{ + RT_NOREF(uPass); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + pDevIns->pHlpR3->pfnSSMPutMem(pSSM, &pThis->MacConfigured, sizeof(pThis->MacConfigured)); + return VINF_SSM_DONT_CALL_AGAIN; +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEPREP, + * Serializes the receive thread, it may be working inside the critsect.} + */ +static DECLCALLBACK(int) elnkSavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pSSM); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertRC(rc); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) elnkSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + pHlp->pfnSSMPutU16(pSSM, pThis->uGPBufPtr); + pHlp->pfnSSMPutU16(pSSM, pThis->uRCVBufPtr); + pHlp->pfnSSMPutU8(pSSM, pThis->XmitCmdReg); + pHlp->pfnSSMPutU8(pSSM, pThis->XmitStatReg); + pHlp->pfnSSMPutU8(pSSM, pThis->RcvCmdReg); + pHlp->pfnSSMPutU8(pSSM, pThis->RcvStatReg); + pHlp->pfnSSMPutU8(pSSM, pThis->AuxCmdReg); + pHlp->pfnSSMPutU8(pSSM, pThis->AuxStatReg); + + pHlp->pfnSSMPutU8(pSSM, pThis->IntrStateReg); + pHlp->pfnSSMPutBool(pSSM, pThis->fInReset); + pHlp->pfnSSMPutBool(pSSM, pThis->fLinkUp); + pHlp->pfnSSMPutBool(pSSM, pThis->fISR); + pHlp->pfnSSMPutMem(pSSM, pThis->aStationAddr, sizeof(pThis->aStationAddr)); + + /* Save the configured MAC address. */ + pHlp->pfnSSMPutMem(pSSM, &pThis->MacConfigured, sizeof(pThis->MacConfigured)); + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADPREP}, + * Serializes the receive thread, it may be working inside the critsect.} + */ +static DECLCALLBACK(int) elnkLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + RT_NOREF(pSSM); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertRC(rc); + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + + return rc; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) elnkLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + if (SSM_VERSION_MAJOR_CHANGED(uVersion, ELNK_SAVEDSTATE_VERSION)) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + if (uPass == SSM_PASS_FINAL) + { + /* restore data */ + pHlp->pfnSSMGetU16(pSSM, &pThis->uGPBufPtr); + pHlp->pfnSSMGetU16(pSSM, &pThis->uRCVBufPtr); + pHlp->pfnSSMGetU8(pSSM, &pThis->XmitCmdReg); + pHlp->pfnSSMGetU8(pSSM, &pThis->XmitStatReg); + pHlp->pfnSSMGetU8(pSSM, &pThis->RcvCmdReg); + pHlp->pfnSSMGetU8(pSSM, &pThis->RcvStatReg); + pHlp->pfnSSMGetU8(pSSM, &pThis->AuxCmdReg); + pHlp->pfnSSMGetU8(pSSM, &pThis->AuxStatReg); + + pHlp->pfnSSMGetU8(pSSM, &pThis->IntrStateReg); + pHlp->pfnSSMGetBool(pSSM, &pThis->fInReset); + pHlp->pfnSSMGetBool(pSSM, &pThis->fLinkUp); + pHlp->pfnSSMGetBool(pSSM, &pThis->fISR); + pHlp->pfnSSMGetMem(pSSM, &pThis->aStationAddr, sizeof(pThis->aStationAddr)); + } + + /* check config */ + RTMAC Mac; + int rc = pHlp->pfnSSMGetMem(pSSM, &Mac, sizeof(Mac)); + AssertRCReturn(rc, rc); + if ( memcmp(&Mac, &pThis->MacConfigured, sizeof(Mac)) + && (uPass == 0 || !PDMDevHlpVMTeleportedAndNotFullyResumedYet(pDevIns)) ) + LogRel(("3C501#%u: The mac address differs: config=%RTmac saved=%RTmac\n", pThis->iInstance, &pThis->MacConfigured, &Mac)); + + if (uPass == SSM_PASS_FINAL) + { + /* update promiscuous mode. */ + if (pThisCC->pDrv) + pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, 0 /* promiscuous enabled */); + + /* Indicate link down to the guest OS that all network connections have + been lost, unless we've been teleported here. */ + if (!PDMDevHlpVMTeleportedAndNotFullyResumedYet(pDevIns)) + elnkTempLinkDown(pDevIns, pThis); + } + + return VINF_SUCCESS; +} + + +/* -=-=-=-=-=- ELNKSTATE::INetworkDown -=-=-=-=-=- */ + +/** + * Check if the device/driver can receive data now. + * + * Worker for elnkNet_WaitReceiveAvail(). This must be called before + * the pfnRecieve() method is called. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + * @param pThis The shared instance data. + */ +static int elnkCanReceive(PPDMDEVINS pDevIns, PELNKSTATE pThis) +{ + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertReleaseRC(rc); + + rc = VINF_SUCCESS; + + /* + * The real 3C501 is very limited in that the packet buffer can only hold one + * frame and and it is shared between transmit and receive, which means the card + * frequently drops packets on a busy network. We cheat a bit and try to hold + * off when it looks like receive is only temporarily unavailable. + * + * If the receiver is disabled, accept packet and drop it to avoid + * packet pile-ups. If it's enabled, take a closer look. + */ +#if 0 + if (pThis->RcvCmd.adr_match != EL_ADRM_DISABLED) { + /* The 3C501 is only prepared to accept a packet if the receiver is busy. + * When not busy, try to delay packets. + */ + if (!pThis->AuxStat.recv_bsy) + { + rc = VERR_NET_NO_BUFFER_SPACE; + } + } +#else + if (pThis->RcvCmd.adr_match == EL_ADRM_DISABLED || !pThis->AuxStat.recv_bsy) + { + rc = VERR_NET_NO_BUFFER_SPACE; + } +#endif + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + return rc; +} + + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnWaitReceiveAvail} + */ +static DECLCALLBACK(int) elnkNet_WaitReceiveAvail(PPDMINETWORKDOWN pInterface, RTMSINTERVAL cMillies) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + int rc = elnkCanReceive(pDevIns, pThis); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + if (RT_UNLIKELY(cMillies == 0)) + return VERR_NET_NO_BUFFER_SPACE; + + rc = VERR_INTERRUPTED; + ASMAtomicXchgBool(&pThis->fMaybeOutOfSpace, true); + STAM_PROFILE_START(&pThis->StatRxOverflow, a); + VMSTATE enmVMState; + while (RT_LIKELY( (enmVMState = PDMDevHlpVMState(pDevIns)) == VMSTATE_RUNNING + || enmVMState == VMSTATE_RUNNING_LS)) + { + int rc2 = elnkCanReceive(pDevIns, pThis); + if (RT_SUCCESS(rc2)) + { + rc = VINF_SUCCESS; + break; + } + LogFlowFunc(("waiting cMillies=%u...\n", cMillies)); + + /* Start the poll timer once which will remain active as long fMaybeOutOfSpace + * is true -- even if (transmit) polling is disabled. */ + rc2 = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertReleaseRC(rc2); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + RTSemEventWait(pThis->hEventOutOfRxSpace, cMillies); + } + STAM_PROFILE_STOP(&pThis->StatRxOverflow, a); + ASMAtomicXchgBool(&pThis->fMaybeOutOfSpace, false); + + return rc; +} + + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnReceive} + */ +static DECLCALLBACK(int) elnkNet_Receive(PPDMINETWORKDOWN pInterface, const void *pvBuf, size_t cb) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + int rc; + + STAM_PROFILE_ADV_START(&pThis->StatReceive, a); + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + AssertReleaseRC(rc); + + if (cb > 50) /* unqualified guess */ + pThis->Led.Asserted.s.fReading = pThis->Led.Actual.s.fReading = 1; + elnkReceiveLocked(pDevIns, pThis, (const uint8_t *)pvBuf, cb, false); + pThis->Led.Actual.s.fReading = 0; + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMINETWORKDOWN,pfnXmitPending} + */ +static DECLCALLBACK(void) elnkNet_XmitPending(PPDMINETWORKDOWN pInterface) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkDown); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + elnkXmitBuffer(pDevIns, pThis, pThisCC, true /*fOnWorkerThread*/); +} + + +/* -=-=-=-=-=- ELNKSTATE::INetworkConfig -=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnGetMac} + */ +static DECLCALLBACK(int) elnkGetMac(PPDMINETWORKCONFIG pInterface, PRTMAC pMac) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkConfig); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + LogFlowFunc(("#%d\n", pThis->iInstance)); + /// @todo This is broken!! We can't properly get the MAC address set by the guest +#if 0 + memcpy(pMac, pThis->aStationAddr, sizeof(*pMac)); +#else + memcpy(pMac, pThis->aPROM, sizeof(*pMac)); +#endif + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnGetLinkState} + */ +static DECLCALLBACK(PDMNETWORKLINKSTATE) elnkGetLinkState(PPDMINETWORKCONFIG pInterface) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkConfig); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + if (pThis->fLinkUp && !pThis->fLinkTempDown) + return PDMNETWORKLINKSTATE_UP; + if (!pThis->fLinkUp) + return PDMNETWORKLINKSTATE_DOWN; + if (pThis->fLinkTempDown) + return PDMNETWORKLINKSTATE_DOWN_RESUME; + AssertMsgFailed(("Invalid link state!\n")); + return PDMNETWORKLINKSTATE_INVALID; +} + + +/** + * @interface_method_impl{PDMINETWORKCONFIG,pfnSetLinkState} + */ +static DECLCALLBACK(int) elnkSetLinkState(PPDMINETWORKCONFIG pInterface, PDMNETWORKLINKSTATE enmState) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, INetworkConfig); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + bool fLinkUp; + + AssertMsgReturn(enmState > PDMNETWORKLINKSTATE_INVALID && enmState <= PDMNETWORKLINKSTATE_DOWN_RESUME, + ("Invalid link state: enmState=%d\n", enmState), VERR_INVALID_PARAMETER); + + if (enmState == PDMNETWORKLINKSTATE_DOWN_RESUME) + { + elnkTempLinkDown(pDevIns, pThis); + /* + * Note that we do not notify the driver about the link state change because + * the change is only temporary and can be disregarded from the driver's + * point of view (see @bugref{7057}). + */ + return VINF_SUCCESS; + } + /* has the state changed? */ + fLinkUp = enmState == PDMNETWORKLINKSTATE_UP; + if (pThis->fLinkUp != fLinkUp) + { + pThis->fLinkUp = fLinkUp; + if (fLinkUp) + { + /* Connect with a configured delay. */ + pThis->fLinkTempDown = true; + pThis->cLinkDownReported = 0; + pThis->cLinkRestorePostponed = 0; + pThis->Led.Asserted.s.fError = pThis->Led.Actual.s.fError = 1; + int rc = PDMDevHlpTimerSetMillies(pDevIns, pThis->hTimerRestore, pThis->cMsLinkUpDelay); + AssertRC(rc); + } + else + { + /* Disconnect. */ + pThis->cLinkDownReported = 0; + pThis->cLinkRestorePostponed = 0; + pThis->Led.Asserted.s.fError = pThis->Led.Actual.s.fError = 1; + } + Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + if (pThisCC->pDrv) + pThisCC->pDrv->pfnNotifyLinkChanged(pThisCC->pDrv, enmState); + } + return VINF_SUCCESS; +} + + +/* -=-=-=-=-=- ELNKSTATE::ILeds (LUN#0) -=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed} + */ +static DECLCALLBACK(int) elnkQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, ILeds); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + if (iLUN == 0) + { + *ppLed = &pThis->Led; + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/* -=-=-=-=-=- ELNKSTATE::IBase (LUN#0) -=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) elnkQueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PELNKSTATECC pThisCC = RT_FROM_MEMBER(pInterface, ELNKSTATECC, IBase); + Assert(&pThisCC->IBase == pInterface); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKDOWN, &pThisCC->INetworkDown); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKCONFIG, &pThisCC->INetworkConfig); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds); + return NULL; +} + + +/* -=-=-=-=-=- PDMDEVREG -=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMDEVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) elnkR3PowerOff(PPDMDEVINS pDevIns) +{ + /* Poke thread waiting for buffer space. */ + elnkR3WakeupReceive(pDevIns); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDetach} + * + * One port on the network card has been disconnected from the network. + */ +static DECLCALLBACK(void) elnkR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + RT_NOREF(fFlags); + LogFlowFunc(("#%d:\n", pThis->iInstance)); + + AssertLogRelReturnVoid(iLUN == 0); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CritSect, rcLock); + + /* + * Zero some important members. + */ + pThis->fDriverAttached = false; + pThisCC->pDrvBase = NULL; + pThisCC->pDrv = NULL; + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnAttach} + * One port on the network card has been connected to a network. + */ +static DECLCALLBACK(int) elnkR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + RT_NOREF(fFlags); + LogFlowFunc(("#%d:\n", pThis->iInstance)); + + AssertLogRelReturn(iLUN == 0, VERR_PDM_NO_SUCH_LUN); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CritSect, rcLock); + + /* + * Attach the driver. + */ + int rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->IBase, &pThisCC->pDrvBase, "Network Port"); + if (RT_SUCCESS(rc)) + { + pThisCC->pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMINETWORKUP); + AssertMsgStmt(pThisCC->pDrv, ("Failed to obtain the PDMINETWORKUP interface!\n"), + rc = VERR_PDM_MISSING_INTERFACE_BELOW); + pThis->fDriverAttached = true; + } + else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER + || rc == VERR_PDM_CFG_MISSING_DRIVER_NAME) + { + /* This should never happen because this function is not called + * if there is no driver to attach! */ + Log(("#%d: No attached driver!\n", pThis->iInstance)); + } + + /* + * Temporary set the link down if it was up so that the guest + * will know that we have change the configuration of the + * network card + */ + if (RT_SUCCESS(rc)) + elnkTempLinkDown(pDevIns, pThis); + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + return rc; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnSuspend} + */ +static DECLCALLBACK(void) elnkR3Suspend(PPDMDEVINS pDevIns) +{ + /* Poke thread waiting for buffer space. */ + elnkR3WakeupReceive(pDevIns); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) elnkR3Reset(PPDMDEVINS pDevIns) +{ + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + if (pThis->fLinkTempDown) + { + pThis->cLinkDownReported = 0x1000; + pThis->cLinkRestorePostponed = 0x1000; + PDMDevHlpTimerStop(pDevIns, pThis->hTimerRestore); + elnkR3TimerRestore(pDevIns, pThis->hTimerRestore, pThis); + } + + /** @todo How to flush the queues? */ + elnkR3HardReset(pDevIns, pThis); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnRelocate} + */ +static DECLCALLBACK(void) elnkR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta) +{ + PELNKSTATERC pThisRC = PDMINS_2_DATA_RC(pDevIns, PELNKSTATERC); + pThisRC->pDrv += offDelta; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) elnkR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->CritSect)) + { + RTSemEventSignal(pThis->hEventOutOfRxSpace); + RTSemEventDestroy(pThis->hEventOutOfRxSpace); + pThis->hEventOutOfRxSpace = NIL_RTSEMEVENT; + PDMDevHlpCritSectDelete(pDevIns, &pThis->CritSect); + } + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) elnkR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + PELNKSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PELNKSTATECC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PPDMIBASE pBase; + char szTmp[128]; + int rc; + + /* + * Init what's required to make the destructor safe. + */ + pThis->iInstance = iInstance; + pThis->hEventOutOfRxSpace = NIL_RTSEMEVENT; + pThis->hIoPortsIsa = NIL_IOMIOPORTHANDLE; + pThisCC->pDevIns = pDevIns; + + /* + * Validate configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "MAC|CableConnected|Port|IRQ|DMA|LinkUpDelay|LineSpeed", ""); + + /* + * Read the configuration. + */ + rc = pHlp->pfnCFGMQueryBytes(pCfg, "MAC", &pThis->MacConfigured, sizeof(pThis->MacConfigured)); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"MAC\" value")); + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "CableConnected", &pThis->fLinkUp, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"CableConnected\" value")); + + /* + * Process ISA configuration options. + */ + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pThis->IOPortBase, 0x300); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"Port\" value")); + + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pThis->uIsaIrq, 3); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"IRQ\" value")); + + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pThis->uIsaDma, 1); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"DMA\" value")); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "LinkUpDelay", (uint32_t*)&pThis->cMsLinkUpDelay, 5000); /* ms */ + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the value of 'LinkUpDelay'")); + Assert(pThis->cMsLinkUpDelay <= 300000); /* less than 5 minutes */ + if (pThis->cMsLinkUpDelay > 5000 || pThis->cMsLinkUpDelay < 100) + { + LogRel(("3C501#%d WARNING! Link up delay is set to %u seconds!\n", + iInstance, pThis->cMsLinkUpDelay / 1000)); + } + Log(("#%d Link up delay is set to %u seconds\n", + iInstance, pThis->cMsLinkUpDelay / 1000)); + + + /* + * Initialize data (most of it anyway). + */ + pThis->Led.u32Magic = PDMLED_MAGIC; + /* IBase */ + pThisCC->IBase.pfnQueryInterface = elnkQueryInterface; + /* INetworkPort */ + pThisCC->INetworkDown.pfnWaitReceiveAvail = elnkNet_WaitReceiveAvail; + pThisCC->INetworkDown.pfnReceive = elnkNet_Receive; + pThisCC->INetworkDown.pfnXmitPending = elnkNet_XmitPending; + /* INetworkConfig */ + pThisCC->INetworkConfig.pfnGetMac = elnkGetMac; + pThisCC->INetworkConfig.pfnGetLinkState = elnkGetLinkState; + pThisCC->INetworkConfig.pfnSetLinkState = elnkSetLinkState; + /* ILeds */ + pThisCC->ILeds.pfnQueryStatusLed = elnkQueryStatusLed; + + /* + * We use our own critical section (historical reasons). + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "3C501#%u", iInstance); + AssertRCReturn(rc, rc); + rc = PDMDevHlpSetDeviceCritSect(pDevIns, &pThis->CritSect); + AssertRCReturn(rc, rc); + + rc = RTSemEventCreate(&pThis->hEventOutOfRxSpace); + AssertRCReturn(rc, rc); + + /* + * Register ISA I/O ranges for the EtherLink 3C501. + */ + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->IOPortBase, 0x10 /*cPorts*/, elnkIOPortWrite, elnkIOPortRead, + "3C501", NULL /*paExtDesc*/, &pThis->hIoPortsIsa); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register DMA channel. + */ + if (pThis->uIsaDma <= ELNK_MAX_VALID_DMA) + { + rc = PDMDevHlpDMARegister(pDevIns, pThis->uIsaDma, elnkR3DMAXferHandler, pThis); + if (RT_FAILURE(rc)) + return rc; + LogRel(("3C501#%d: Enabling DMA channel %u\n", iInstance, pThis->uIsaDma)); + } + else + LogRel(("3C501#%d: Disabling DMA\n", iInstance)); + + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, elnkR3TimerRestore, NULL, TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, + "3C501 Restore Timer", &pThis->hTimerRestore); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpSSMRegisterEx(pDevIns, ELNK_SAVEDSTATE_VERSION, sizeof(*pThis), NULL, + NULL, elnkLiveExec, NULL, + elnkSavePrep, elnkSaveExec, NULL, + elnkLoadPrep, elnkLoadExec, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the transmit queue. + */ + rc = PDMDevHlpTaskCreate(pDevIns, PDMTASK_F_RZ, "3C501-Xmit", elnkR3XmitTaskCallback, NULL /*pvUser*/, &pThis->hXmitTask); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the RX notifier signaller. + */ + rc = PDMDevHlpTaskCreate(pDevIns, PDMTASK_F_RZ, "3C501-Rcv", elnkR3CanRxTaskCallback, NULL /*pvUser*/, &pThis->hCanRxTask); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register the info item. + */ + RTStrPrintf(szTmp, sizeof(szTmp), "elnk%d", pThis->iInstance); + PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "3C501 info", elnkR3Info); + + /* + * Attach status driver (optional). + */ + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else if ( rc != VERR_PDM_NO_ATTACHED_DRIVER + && rc != VERR_PDM_CFG_MISSING_DRIVER_NAME) + { + AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Attach driver. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->IBase, &pThisCC->pDrvBase, "Network Port"); + if (RT_SUCCESS(rc)) + { + pThisCC->pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMINETWORKUP); + AssertMsgReturn(pThisCC->pDrv, ("Failed to obtain the PDMINETWORKUP interface!\n"), + VERR_PDM_MISSING_INTERFACE_BELOW); + pThis->fDriverAttached = true; + } + else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER + || rc == VERR_PDM_CFG_MISSING_DRIVER_NAME) + { + /* No error! */ + Log(("No attached driver!\n")); + } + else + return rc; + + /* + * Reset the device state. (Do after attaching.) + */ + elnkR3HardReset(pDevIns, pThis); + + /* + * Register statistics counters. + */ + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatReceiveBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data received", "/Public/Net/EtherLink%u/BytesReceived", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data transmitted", "/Public/Net/EtherLink%u/BytesTransmitted", iInstance); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatReceiveBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data received", "/Devices/EtherLink%d/ReceiveBytes", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data transmitted", "/Devices/EtherLink%d/TransmitBytes", iInstance); + +#ifdef VBOX_WITH_STATISTICS + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatIOReadRZ, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling IO reads in RZ", "/Devices/EtherLink%d/IO/ReadRZ", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatIOReadR3, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling IO reads in R3", "/Devices/EtherLink%d/IO/ReadR3", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatIOWriteRZ, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling IO writes in RZ", "/Devices/EtherLink%d/IO/WriteRZ", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatIOWriteR3, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling IO writes in R3", "/Devices/EtherLink%d/IO/WriteR3", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatReceive, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling receive", "/Devices/EtherLink%d/Receive", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatRxOverflow, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_OCCURENCE, "Profiling RX overflows", "/Devices/EtherLink%d/RxOverflow", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatRxOverflowWakeup, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_OCCURENCE, "Nr of RX overflow wakeups", "/Devices/EtherLink%d/RxOverflowWakeup", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitRZ, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling transmits in RZ", "/Devices/EtherLink%d/Transmit/TotalRZ", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitR3, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling transmits in R3", "/Devices/EtherLink%d/Transmit/TotalR3", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitSendRZ, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling send transmit in RZ", "/Devices/EtherLink%d/Transmit/SendRZ", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatTransmitSendR3, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling send transmit in R3", "/Devices/EtherLink%d/Transmit/SendR3", iInstance); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatInterrupt, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling interrupt checks", "/Devices/EtherLink%d/UpdateIRQ", iInstance); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatResets, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of soft resets", "/Devices/EtherLink%d/SoftResets", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatDropPktAdrmDis, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Dropped packet, disabled address match", "/Devices/EtherLink%d/DropPktAdrmDis", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatDropPktZeroLen, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Dropped zero length packet", "/Devices/EtherLink%d/DropPktZeroLen", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatDropPktVMNotRunning,STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Dropped packet, VM not running", "/Devices/EtherLink%d/DropPktVMNotRunning", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatDropPktNoLink, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Dropped packet, no link", "/Devices/EtherLink%d/DropPktNoLink", iInstance); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatDropPktStaleRcv, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Dropped packet, status register unread", "/Devices/EtherLink%d/DropPktStaleRcv", iInstance); +#endif /* VBOX_WITH_STATISTICS */ + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->StatPktsLostReset, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of packets lost due to resets", "/Devices/EtherLink%d/PktsLostByReset", iInstance); + + return VINF_SUCCESS; +} + +#else + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) elnkRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PELNKSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PELNKSTATE); + + /* Critical section setup: */ + int rc = PDMDevHlpSetDeviceCritSect(pDevIns, &pThis->CritSect); + AssertRCReturn(rc, rc); + + /* ISA I/O ports: */ + if (pThis->hIoPortsIsa != NIL_IOMIOPORTHANDLE) + { + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsIsa, elnkIOPortWrite, elnkIOPortRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +#endif /* IN_RING3 */ + +/** + * The device registration structure. + */ +const PDMDEVREG g_Device3C501 = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "3c501", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_NETWORK, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(ELNKSTATE), + /* .cbInstanceCC = */ sizeof(ELNKSTATECC), + /* .cbInstanceRC = */ sizeof(ELNKSTATERC), + /* .cMaxPciDevices = */ 0, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "3Com EtherLink 3C501 adapter.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ elnkR3Construct, + /* .pfnDestruct = */ elnkR3Destruct, + /* .pfnRelocate = */ elnkR3Relocate, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ elnkR3Reset, + /* .pfnSuspend = */ elnkR3Suspend, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ elnkR3Attach, + /* .pfnDetach = */ elnkR3Detach, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ elnkR3PowerOff, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ elnkRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ -- cgit v1.2.3