diff --git a/src/network/net_pcnet.c b/src/network/net_pcnet.c new file mode 100644 index 000000000..fa5cab9d6 --- /dev/null +++ b/src/network/net_pcnet.c @@ -0,0 +1,2753 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * Emulation of the AMD PCnet LANCE NIC controller for both the ISA, VLB, + * and PCI buses. + * + * Version: @(#)net_pcnet.c 1.0.0 2019/11/09 + * + * Authors: Miran Grca, + * TheCollector1995, + * Antony T Curtis + * + * Copyright 2004-2019 Antony T Curtis + * Copyright 2016-2019 Miran Grca. + */ +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include "../86box.h" +#include "../io.h" +#include "../timer.h" +#include "../dma.h" +#include "../mem.h" +#include "../rom.h" +#include "../pci.h" +#include "../pic.h" +#include "../random.h" +#include "../device.h" +#include "network.h" +#include "net_pcnet.h" +#include "bswap.h" + + +/* PCI info. */ +#define PCI_VENDID 0x1022 /* AMD */ +#define PCI_DEVID 0x2000 /* PCnet-PCI II (Am79c970A) */ +#define PCI_REGSIZE 256 /* size of PCI space */ + +#pragma pack(1) +typedef struct RTNETETHERHDR +{ + uint8_t DstMac[6]; + uint8_t SrcMac[6]; + /** Ethernet frame type or frame size, depending on the kind of ethernet. + * This is big endian on the wire. */ + uint16_t EtherType; +} RTNETETHERHDR; +#pragma pack() + +#define BCR_MAX_RAP 50 +#define MII_MAX_REG 32 +#define CSR_MAX_REG 128 + +/** Maximum frame size we handle */ +#define MAX_FRAME 1536 + + +/** @name Bus configuration registers + * @{ */ +#define BCR_MSRDA 0 +#define BCR_MSWRA 1 +#define BCR_MC 2 +#define BCR_RESERVED3 3 +#define BCR_LNKST 4 +#define BCR_LED1 5 +#define BCR_LED2 6 +#define BCR_LED3 7 +#define BCR_RESERVED8 8 +#define BCR_FDC 9 +/* 10 - 15 = reserved */ +#define BCR_IOBASEL 16 /* Reserved */ +#define BCR_IOBASEU 16 /* Reserved */ +#define BCR_BSBC 18 +#define BCR_EECAS 19 +#define BCR_SWS 20 +#define BCR_INTCON 21 /* Reserved */ +#define BCR_PLAT 22 +#define BCR_PCISVID 23 +#define BCR_PCISID 24 +#define BCR_SRAMSIZ 25 +#define BCR_SRAMB 26 +#define BCR_SRAMIC 27 +#define BCR_EBADDRL 28 +#define BCR_EBADDRU 29 +#define BCR_EBD 30 +#define BCR_STVAL 31 +#define BCR_MIICAS 32 +#define BCR_MIIADDR 33 +#define BCR_MIIMDR 34 +#define BCR_PCIVID 35 +#define BCR_PMC_A 36 +#define BCR_DATA0 37 +#define BCR_DATA1 38 +#define BCR_DATA2 39 +#define BCR_DATA3 40 +#define BCR_DATA4 41 +#define BCR_DATA5 42 +#define BCR_DATA6 43 +#define BCR_DATA7 44 +#define BCR_PMR1 45 +#define BCR_PMR2 46 +#define BCR_PMR3 47 +/** @} */ + +/** @name Bus configuration sub register accessors. + * @{ */ +#define BCR_DWIO(S) ((S)->aBCR[BCR_BSBC] & 0x0080) +#define BCR_SSIZE32(S) ((S)->aBCR[BCR_SWS ] & 0x0100) +#define BCR_SWSTYLE(S) ((S)->aBCR[BCR_SWS ] & 0x00FF) +/** @} */ + +/** @name CSR subregister accessors. + * @{ */ +#define CSR_INIT(S) ((S)->aCSR[0] & 0x0001) /**< Init assertion */ +#define CSR_STRT(S) ((S)->aCSR[0] & 0x0002) /**< Start assertion */ +#define CSR_STOP(S) ((S)->aCSR[0] & 0x0004) /**< Stop assertion */ +#define CSR_TDMD(S) ((S)->aCSR[0] & 0x0008) /**< Transmit demand. (perform xmit poll now (readable, settable, not clearable) */ +#define CSR_TXON(S) ((S)->aCSR[0] & 0x0010) /**< Transmit on (readonly) */ +#define CSR_RXON(S) ((S)->aCSR[0] & 0x0020) /**< Receive On */ +#define CSR_INEA(S) ((S)->aCSR[0] & 0x0040) /**< Interrupt Enable */ +#define CSR_LAPPEN(S) ((S)->aCSR[3] & 0x0020) /**< Look Ahead Packet Processing Enable */ +#define CSR_DXSUFLO(S) ((S)->aCSR[3] & 0x0040) /**< Disable Transmit Stop on Underflow error */ +#define CSR_ASTRP_RCV(S) ((S)->aCSR[4] & 0x0400) /**< Auto Strip Receive */ +#define CSR_DPOLL(S) ((S)->aCSR[4] & 0x1000) /**< Disable Transmit Polling */ +#define CSR_SPND(S) ((S)->aCSR[5] & 0x0001) /**< Suspend */ +#define CSR_LTINTEN(S) ((S)->aCSR[5] & 0x4000) /**< Last Transmit Interrupt Enable */ +#define CSR_TOKINTD(S) ((S)->aCSR[5] & 0x8000) /**< Transmit OK Interrupt Disable */ + +#define CSR_STINT ((S)->aCSR[7] & 0x0800) /**< Software Timer Interrupt */ +#define CSR_STINTE ((S)->aCSR[7] & 0x0400) /**< Software Timer Interrupt Enable */ + +#define CSR_DRX(S) ((S)->aCSR[15] & 0x0001) /**< Disable Receiver */ +#define CSR_DTX(S) ((S)->aCSR[15] & 0x0002) /**< Disable Transmit */ +#define CSR_LOOP(S) ((S)->aCSR[15] & 0x0004) /**< Loopback Enable */ +#define CSR_DRCVPA(S) ((S)->aCSR[15] & 0x2000) /**< Disable Receive Physical Address */ +#define CSR_DRCVBC(S) ((S)->aCSR[15] & 0x4000) /**< Disable Receive Broadcast */ +#define CSR_PROM(S) ((S)->aCSR[15] & 0x8000) /**< Promiscuous Mode */ + +/** @name CSR register accessors. + * @{ */ +#define CSR_IADR(S) (*(uint32_t*)((S)->aCSR + 1)) /**< Initialization Block Address */ +#define CSR_CRBA(S) (*(uint32_t*)((S)->aCSR + 18)) /**< Current Receive Buffer Address */ +#define CSR_CXBA(S) (*(uint32_t*)((S)->aCSR + 20)) /**< Current Transmit Buffer Address */ +#define CSR_NRBA(S) (*(uint32_t*)((S)->aCSR + 22)) /**< Next Receive Buffer Address */ +#define CSR_BADR(S) (*(uint32_t*)((S)->aCSR + 24)) /**< Base Address of Receive Ring */ +#define CSR_NRDA(S) (*(uint32_t*)((S)->aCSR + 26)) /**< Next Receive Descriptor Address */ +#define CSR_CRDA(S) (*(uint32_t*)((S)->aCSR + 28)) /**< Current Receive Descriptor Address */ +#define CSR_BADX(S) (*(uint32_t*)((S)->aCSR + 30)) /**< Base Address of Transmit Descriptor */ +#define CSR_NXDA(S) (*(uint32_t*)((S)->aCSR + 32)) /**< Next Transmit Descriptor Address */ +#define CSR_CXDA(S) (*(uint32_t*)((S)->aCSR + 34)) /**< Current Transmit Descriptor Address */ +#define CSR_NNRD(S) (*(uint32_t*)((S)->aCSR + 36)) /**< Next Next Receive Descriptor Address */ +#define CSR_NNXD(S) (*(uint32_t*)((S)->aCSR + 38)) /**< Next Next Transmit Descriptor Address */ +#define CSR_CRBC(S) ((S)->aCSR[40]) /**< Current Receive Byte Count */ +#define CSR_CRST(S) ((S)->aCSR[41]) /**< Current Receive Status */ +#define CSR_CXBC(S) ((S)->aCSR[42]) /**< Current Transmit Byte Count */ +#define CSR_CXST(S) ((S)->aCSR[43]) /**< Current transmit status */ +#define CSR_NRBC(S) ((S)->aCSR[44]) /**< Next Receive Byte Count */ +#define CSR_NRST(S) ((S)->aCSR[45]) /**< Next Receive Status */ +#define CSR_POLL(S) ((S)->aCSR[46]) /**< Transmit Poll Time Counter */ +#define CSR_PINT(S) ((S)->aCSR[47]) /**< Transmit Polling Interval */ +#define CSR_PXDA(S) (*(uint32_t*)((S)->aCSR + 60)) /**< Previous Transmit Descriptor Address*/ +#define CSR_PXBC(S) ((S)->aCSR[62]) /**< Previous Transmit Byte Count */ +#define CSR_PXST(S) ((S)->aCSR[63]) /**< Previous Transmit Status */ +#define CSR_NXBA(S) (*(uint32_t*)((S)->aCSR + 64)) /**< Next Transmit Buffer Address */ +#define CSR_NXBC(S) ((S)->aCSR[66]) /**< Next Transmit Byte Count */ +#define CSR_NXST(S) ((S)->aCSR[67]) /**< Next Transmit Status */ +#define CSR_RCVRC(S) ((S)->aCSR[72]) /**< Receive Descriptor Ring Counter */ +#define CSR_XMTRC(S) ((S)->aCSR[74]) /**< Transmit Descriptor Ring Counter */ +#define CSR_RCVRL(S) ((S)->aCSR[76]) /**< Receive Descriptor Ring Length */ +#define CSR_XMTRL(S) ((S)->aCSR[78]) /**< Transmit Descriptor Ring Length */ +#define CSR_MISSC(S) ((S)->aCSR[112]) /**< Missed Frame Count */ + +/** Calculates the full physical address. */ +#define PHYSADDR(S,A) ((A) | (S)->GCUpperPhys) + + +typedef struct { + mem_mapping_t mmio_mapping; + const char *name; + int board; + int is_pci, is_vlb; + int PCIBase; + int MMIOBase; + uint32_t base_address; + int base_irq; + int dma_channel; + int card; /* PCI card slot */ + int xmit_pos; + /** Register Address Pointer */ + uint32_t u32RAP; + /** Internal interrupt service */ + int32_t iISR; + /** ??? */ + uint32_t u32Lnkst; + /** Address of the RX descriptor table (ring). Loaded at init. */ + uint32_t GCRDRA; + /** Address of the TX descriptor table (ring). Loaded at init. */ + uint32_t GCTDRA; + uint8_t aPROM[16]; + uint16_t aCSR[CSR_MAX_REG]; + uint16_t aBCR[BCR_MAX_RAP]; + /** The loopback transmit buffer (avoid stack allocations). */ + uint8_t abLoopBuf[4096]; + /** The recv buffer. */ + uint8_t abRecvBuf[4096]; + /** Size of a RX/TX descriptor (8 or 16 bytes according to SWSTYLE */ + int iLog2DescSize; + /** Bits 16..23 in 16-bit mode */ + uint32_t GCUpperPhys; + /** True if we signal the guest that RX packets are missing. */ + int fSignalRxMiss; + /** Link speed to be reported through CSR68. */ + uint32_t u32LinkSpeed; + /** Error counter for bad receive descriptors. */ + uint32_t uCntBadRMD; + uint8_t maclocal[6]; /* configured MAC (local) address */ + pc_timer_t poll_timer; +} nic_t; + +/** @todo All structs: big endian? */ + +struct INITBLK16 +{ + uint16_t mode; /**< copied into csr15 */ + uint16_t padr1; /**< MAC 0..15 */ + uint16_t padr2; /**< MAC 16..32 */ + uint16_t padr3; /**< MAC 33..47 */ + uint16_t ladrf1; /**< logical address filter 0..15 */ + uint16_t ladrf2; /**< logical address filter 16..31 */ + uint16_t ladrf3; /**< logical address filter 32..47 */ + uint16_t ladrf4; /**< logical address filter 48..63 */ + uint32_t rdra:24; /**< address of receive descriptor ring */ + uint32_t res1:5; /**< reserved */ + uint32_t rlen:3; /**< number of receive descriptor ring entries */ + uint32_t tdra:24; /**< address of transmit descriptor ring */ + uint32_t res2:5; /**< reserved */ + uint32_t tlen:3; /**< number of transmit descriptor ring entries */ +}; + +/** bird: I've changed the type for the bitfields. They should only be 16-bit all together. + * frank: I've changed the bitfiled types to uint32_t to prevent compiler warnings. */ +struct INITBLK32 +{ + uint16_t mode; /**< copied into csr15 */ + uint16_t res1:4; /**< reserved */ + uint16_t rlen:4; /**< number of receive descriptor ring entries */ + uint16_t res2:4; /**< reserved */ + uint16_t tlen:4; /**< number of transmit descriptor ring entries */ + uint16_t padr1; /**< MAC 0..15 */ + uint16_t padr2; /**< MAC 16..31 */ + uint16_t padr3; /**< MAC 32..47 */ + uint16_t res3; /**< reserved */ + uint16_t ladrf1; /**< logical address filter 0..15 */ + uint16_t ladrf2; /**< logical address filter 16..31 */ + uint16_t ladrf3; /**< logical address filter 32..47 */ + uint16_t ladrf4; /**< logical address filter 48..63 */ + uint32_t rdra; /**< address of receive descriptor ring */ + uint32_t tdra; /**< address of transmit descriptor ring */ +}; + +/** Transmit Message Descriptor */ +typedef struct TMD +{ + struct + { + uint32_t tbadr; /**< transmit buffer address */ + } tmd0; + struct + { + uint32_t bcnt:12; /**< buffer byte count (two's complement) */ + uint32_t ones:4; /**< must be 1111b */ + uint32_t res:7; /**< reserved */ + uint32_t bpe:1; /**< bus parity error */ + uint32_t enp:1; /**< end of packet */ + uint32_t stp:1; /**< start of packet */ + uint32_t def:1; /**< deferred */ + uint32_t one:1; /**< exactly one retry was needed to transmit a frame */ + uint32_t ltint:1; /**< suppress interrupts after successful transmission */ + uint32_t nofcs:1; /**< when set, the state of DXMTFCS is ignored and + transmitter FCS generation is activated. */ + uint32_t err:1; /**< error occurred */ + uint32_t own:1; /**< 0=owned by guest driver, 1=owned by controller */ + } tmd1; + struct + { + uint32_t trc:4; /**< transmit retry count */ + uint32_t res:12; /**< reserved */ + uint32_t tdr:10; /**< ??? */ + uint32_t rtry:1; /**< retry error */ + uint32_t lcar:1; /**< loss of carrier */ + uint32_t lcol:1; /**< late collision */ + uint32_t exdef:1; /**< excessive deferral */ + uint32_t uflo:1; /**< underflow error */ + uint32_t buff:1; /**< out of buffers (ENP not found) */ + } tmd2; + struct + { + uint32_t res; /**< reserved for user defined space */ + } tmd3; +} TMD; + +/** Receive Message Descriptor */ +typedef struct RMD +{ + struct + { + uint32_t rbadr; /**< receive buffer address */ + } rmd0; + struct + { + uint32_t bcnt:12; /**< buffer byte count (two's complement) */ + uint32_t ones:4; /**< must be 1111b */ + uint32_t res:4; /**< reserved */ + uint32_t bam:1; /**< broadcast address match */ + uint32_t lafm:1; /**< logical filter address match */ + uint32_t pam:1; /**< physical address match */ + uint32_t bpe:1; /**< bus parity error */ + uint32_t enp:1; /**< end of packet */ + uint32_t stp:1; /**< start of packet */ + uint32_t buff:1; /**< buffer error */ + uint32_t crc:1; /**< crc error on incoming frame */ + uint32_t oflo:1; /**< overflow error (lost all or part of incoming frame) */ + uint32_t fram:1; /**< frame error */ + uint32_t err:1; /**< error occurred */ + uint32_t own:1; /**< 0=owned by guest driver, 1=owned by controller */ + } rmd1; + struct + { + uint32_t mcnt:12; /**< message byte count */ + uint32_t zeros:4; /**< 0000b */ + uint32_t rpc:8; /**< receive frame tag */ + uint32_t rcc:8; /**< receive frame tag + reserved */ + } rmd2; + struct + { + uint32_t res; /**< reserved for user defined space */ + } rmd3; +} RMD; + +static bar_t pcnet_pci_bar[3]; +static uint8_t pcnet_pci_regs[PCI_REGSIZE]; + +static void pcnetAsyncTransmit(nic_t *dev); +static void pcnetPollRxTx(nic_t *dev); +static void pcnetPollTimer(nic_t *dev); +static void pcnetUpdateIrq(nic_t *dev); +static uint16_t pcnet_bcr_readw(nic_t *dev, uint16_t rap); +static void pcnet_bcr_writew(nic_t *dev, uint16_t rap, uint16_t val); +static void pcnet_csr_writew(nic_t *dev, uint16_t rap, uint16_t val); +static void pcnetCanReceive(nic_t *dev); + + +#ifdef ENABLE_PCNET_LOG +int pcnet_do_log = ENABLE_PCNET_LOG; + +static void +pcnetlog(int lvl, const char *fmt, ...) +{ + va_list ap; + + if (pcnet_do_log >= lvl) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define pcnetlog(lvl, fmt, ...) +#endif + + +static void +pcnet_do_irq(nic_t *dev, int issue) +{ + if (dev->is_pci) { + if (issue) + pci_set_irq(dev->card, PCI_INTA); + else + pci_clear_irq(dev->card, PCI_INTA); + } else { + if (issue) + picint(1<base_irq); + else + picintc(1<base_irq); + } +} + + +/** + * Load transmit message descriptor + * Make sure we read the own flag first. + * + * @param pThis adapter private data + * @param addr physical address of the descriptor + * @param fRetIfNotOwn return immediately after reading the own flag if we don't own the descriptor + * @return true if we own the descriptor, false otherwise + */ +static __inline int +pcnetTmdLoad(nic_t *dev, TMD *tmd, uint32_t addr, int fRetIfNotOwn) +{ + uint8_t ownbyte; + + if (BCR_SWSTYLE(dev) == 0) { + uint16_t xda[4]; + + DMAPageRead(addr+3, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)&xda[0], sizeof(xda)); + ((uint32_t *)tmd)[0] = (uint32_t)xda[0] | ((uint32_t)(xda[1] & 0x00ff) << 16); + ((uint32_t *)tmd)[1] = (uint32_t)xda[2] | ((uint32_t)(xda[1] & 0xff00) << 16); + ((uint32_t *)tmd)[2] = (uint32_t)xda[3] << 16; + ((uint32_t *)tmd)[3] = 0; + } else if (BCR_SWSTYLE(dev) != 3) { + DMAPageRead(addr+7, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)tmd, 16); + } else { + uint32_t xda[4]; + DMAPageRead(addr+7, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)&xda[0], sizeof(xda)); + ((uint32_t *)tmd)[0] = xda[2]; + ((uint32_t *)tmd)[1] = xda[1]; + ((uint32_t *)tmd)[2] = xda[0]; + ((uint32_t *)tmd)[3] = xda[3]; + } + /* Double check the own bit; guest drivers might be buggy and lock prefixes in the recompiler are ignored by other threads. */ + if (tmd->tmd1.own == 1 && !(ownbyte & 0x80)) + pcnetlog(3, "%s: pcnetTmdLoad: own bit flipped while reading!!\n", dev->name); + if (!(ownbyte & 0x80)) + tmd->tmd1.own = 0; + + return !!tmd->tmd1.own; +} + + +/** + * Store transmit message descriptor and hand it over to the host (the VM guest). + * Make sure that all data are transmitted before we clear the own flag. + */ +static __inline void +pcnetTmdStorePassHost(nic_t *dev, TMD *tmd, uint32_t addr) +{ + if (BCR_SWSTYLE(dev) == 0) { + uint16_t xda[4]; + xda[0] = ((uint32_t *)tmd)[0] & 0xffff; + xda[1] = ((((uint32_t *)tmd)[0] >> 16) & 0xff) | ((((uint32_t *)tmd)[1]>>16) & 0xff00); + xda[2] = ((uint32_t *)tmd)[1] & 0xffff; + xda[3] = ((uint32_t *)tmd)[2] >> 16; + xda[1] |= 0x8000; + DMAPageWrite(addr, (uint8_t*)&xda[0], sizeof(xda)); + xda[1] &= ~0x8000; + DMAPageWrite(addr+3, (uint8_t*)xda + 3, 1); + } else if (BCR_SWSTYLE(dev) != 3) { + ((uint32_t*)tmd)[1] |= 0x80000000; + DMAPageWrite(addr, (uint8_t*)tmd, 12); + ((uint32_t*)tmd)[1] &= ~0x80000000; + DMAPageWrite(addr+7, (uint8_t*)tmd + 7, 1); + } else { + uint32_t xda[3]; + xda[0] = ((uint32_t *)tmd)[2]; + xda[1] = ((uint32_t *)tmd)[1]; + xda[2] = ((uint32_t *)tmd)[0]; + xda[1] |= 0x80000000; + DMAPageWrite(addr, (uint8_t*)&xda[0], sizeof(xda)); + xda[1] &= ~0x80000000; + DMAPageWrite(addr+7, (uint8_t*)xda + 7, 1); + } +} + + +/** + * Load receive message descriptor + * Make sure we read the own flag first. + * + * @param pThis adapter private data + * @param addr physical address of the descriptor + * @param fRetIfNotOwn return immediately after reading the own flag if we don't own the descriptor + * @return true if we own the descriptor, false otherwise + */ +static __inline int +pcnetRmdLoad(nic_t *dev, RMD *rmd, uint32_t addr, int fRetIfNotOwn) +{ + uint8_t ownbyte; + + if (BCR_SWSTYLE(dev) == 0) { + uint16_t rda[4]; + DMAPageRead(addr+3, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)&rda[0], sizeof(rda)); + ((uint32_t *)rmd)[0] = (uint32_t)rda[0] | ((rda[1] & 0x00ff) << 16); + ((uint32_t *)rmd)[1] = (uint32_t)rda[2] | ((rda[1] & 0xff00) << 16); + ((uint32_t *)rmd)[2] = (uint32_t)rda[3]; + ((uint32_t *)rmd)[3] = 0; + } else if (BCR_SWSTYLE(dev) != 3) { + DMAPageRead(addr+7, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)rmd, 16); + } else { + uint32_t rda[4]; + DMAPageRead(addr+7, &ownbyte, 1); + if (!(ownbyte & 0x80) && fRetIfNotOwn) + return 0; + DMAPageRead(addr, (uint8_t*)&rda[0], sizeof(rda)); + ((uint32_t *)rmd)[0] = rda[2]; + ((uint32_t *)rmd)[1] = rda[1]; + ((uint32_t *)rmd)[2] = rda[0]; + ((uint32_t *)rmd)[3] = rda[3]; + } + /* Double check the own bit; guest drivers might be buggy and lock prefixes in the recompiler are ignored by other threads. */ + if (rmd->rmd1.own == 1 && !(ownbyte & 0x80)) + pcnetlog(3, "%s: pcnetRmdLoad: own bit flipped while reading!!\n", dev->name); + + if (!(ownbyte & 0x80)) + rmd->rmd1.own = 0; + + return !!rmd->rmd1.own; +} + + +/** + * Store receive message descriptor and hand it over to the host (the VM guest). + * Make sure that all data are transmitted before we clear the own flag. + */ +static __inline void +pcnetRmdStorePassHost(nic_t *dev, RMD *rmd, uint32_t addr) +{ + if (BCR_SWSTYLE(dev) == 0) { + uint16_t rda[4]; + rda[0] = ((uint32_t *)rmd)[0] & 0xffff; + rda[1] = ((((uint32_t *)rmd)[0]>>16) & 0xff) | ((((uint32_t *)rmd)[1]>>16) & 0xff00); + rda[2] = ((uint32_t *)rmd)[1] & 0xffff; + rda[3] = ((uint32_t *)rmd)[2] & 0xffff; + rda[1] |= 0x8000; + DMAPageWrite(addr, (uint8_t*)&rda[0], sizeof(rda)); + rda[1] &= ~0x8000; + DMAPageWrite(addr+3, (uint8_t*)rda + 3, 1); + } else if (BCR_SWSTYLE(dev) != 3) { + ((uint32_t*)rmd)[1] |= 0x80000000; + DMAPageWrite(addr, (uint8_t*)rmd, 12); + ((uint32_t*)rmd)[1] &= ~0x80000000; + DMAPageWrite(addr+7, (uint8_t*)rmd + 7, 1); + } else { + uint32_t rda[3]; + rda[0] = ((uint32_t *)rmd)[2]; + rda[1] = ((uint32_t *)rmd)[1]; + rda[2] = ((uint32_t *)rmd)[0]; + rda[1] |= 0x80000000; + DMAPageWrite(addr, (uint8_t*)&rda[0], sizeof(rda)); + rda[1] &= ~0x80000000; + DMAPageWrite(addr+7, (uint8_t*)rda + 7, 1); + } +} + + +/** + * Read+Write a TX/RX descriptor to prevent DMAPageWrite() allocating + * pages later when we shouldn't schedule to EMT. Temporarily hack. + */ +static void +pcnetDescTouch(nic_t *dev, uint32_t addr) +{ + uint8_t aBuf[16]; + int cbDesc; + if (BCR_SWSTYLE(dev) == 0) + cbDesc = 8; + else + cbDesc = 16; + DMAPageRead(addr, aBuf, cbDesc); + DMAPageWrite(addr, aBuf, cbDesc); +} + + +/** Checks if it's a bad (as in invalid) RMD.*/ +#define IS_RMD_BAD(rmd) ((rmd).rmd1.ones != 15) + +/** The network card is the owner of the RDTE/TDTE, actually it is this driver */ +#define CARD_IS_OWNER(desc) (((desc) & 0x8000)) + +/** The host is the owner of the RDTE/TDTE -- actually the VM guest. */ +#define HOST_IS_OWNER(desc) (!((desc) & 0x8000)) + +#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() + +#define CRC(crc, ch) (crc = (crc >> 8) ^ crctab[(crc ^ (ch)) & 0xff]) + +#define MULTICAST_FILTER_LEN 8 + +static __inline uint32_t +lnc_mchash(const uint8_t *ether_addr) +{ +#define LNC_POLYNOMIAL 0xEDB88320UL + uint32_t crc = 0xFFFFFFFF; + int idx, bit; + uint8_t data; + + for (idx = 0; idx < ETHER_ADDR_LEN; idx++) + { + for (data = *ether_addr++, bit = 0; bit < MULTICAST_FILTER_LEN; bit++) + { + crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LNC_POLYNOMIAL : 0); + data >>= 1; + } + } + return crc; +#undef LNC_POLYNOMIAL +} + +#define CRC(crc, ch) (crc = (crc >> 8) ^ crctab[(crc ^ (ch)) & 0xff]) + +/* generated using the AUTODIN II polynomial + * x^32 + x^26 + x^23 + x^22 + x^16 + + * x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1 + */ +static const uint32_t crctab[256] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + + +static __inline int +padr_match(nic_t *dev, const uint8_t *buf, int size) +{ + struct ether_header *hdr = (struct ether_header *)buf; + int result; + uint8_t padr[6]; + padr[0] = dev->aCSR[12] & 0xff; + padr[1] = dev->aCSR[12] >> 8; + padr[2] = dev->aCSR[13] & 0xff; + padr[3] = dev->aCSR[13] >> 8; + padr[4] = dev->aCSR[14] & 0xff; + padr[5] = dev->aCSR[14] >> 8; + result = !CSR_DRCVPA(dev) && !memcmp(hdr->ether_dhost, padr, 6); + + pcnetlog(3, "%s: packet dhost=%02x:%02x:%02x:%02x:%02x:%02x, " + "padr=%02x:%02x:%02x:%02x:%02x:%02x => %d\n", dev->name, + hdr->ether_dhost[0],hdr->ether_dhost[1],hdr->ether_dhost[2], + hdr->ether_dhost[3],hdr->ether_dhost[4],hdr->ether_dhost[5], + padr[0],padr[1],padr[2],padr[3],padr[4],padr[5], result); + + return result; +} + + +static __inline int +padr_bcast(nic_t *dev, const uint8_t *buf, size_t size) +{ + static uint8_t aBCAST[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + struct ether_header *hdr = (struct ether_header *)buf; + int result = !CSR_DRCVBC(dev) && !memcmp(hdr->ether_dhost, aBCAST, 6); + pcnetlog(3, "%s: padr_bcast result=%d\n", dev->name, result); + return result; +} + + +static int +ladr_match(nic_t *dev, const uint8_t *buf, size_t size) +{ + struct ether_header *hdr = (struct ether_header *)buf; + if ((hdr->ether_dhost[0] & 0x01) && ((uint64_t *)&dev->aCSR[8])[0] != 0LL) { + int index; + uint8_t ladr[8]; + ladr[0] = dev->aCSR[8] & 0xff; + ladr[1] = dev->aCSR[8] >> 8; + ladr[2] = dev->aCSR[9] & 0xff; + ladr[3] = dev->aCSR[9] >> 8; + ladr[4] = dev->aCSR[10] & 0xff; + ladr[5] = dev->aCSR[10] >> 8; + ladr[6] = dev->aCSR[11] & 0xff; + ladr[7] = dev->aCSR[11] >> 8; + index = lnc_mchash(hdr->ether_dhost) >> 26; + return (ladr[index >> 3] & (1 << (index & 7))); + } + return 0; +} + + +/** + * Get the receive descriptor ring address with a given index. + */ +static __inline uint32_t +pcnetRdraAddr(nic_t *dev, int idx) +{ + return dev->GCRDRA + ((CSR_RCVRL(dev) - idx) << dev->iLog2DescSize); +} + + +/** + * Get the transmit descriptor ring address with a given index. + */ +static __inline uint32_t +pcnetTdraAddr(nic_t *dev, int idx) +{ + return dev->GCTDRA + ((CSR_XMTRL(dev) - idx) << dev->iLog2DescSize); +} + + +static void +pcnetSoftReset(nic_t *dev) +{ + pcnetlog(3, "%s: pcnetSoftReset\n", dev->name); + + dev->u32Lnkst = 0x40; + dev->GCRDRA = 0; + dev->GCTDRA = 0; + dev->u32RAP = 0; + + dev->aCSR[0] = 0x0004; + dev->aCSR[3] = 0x0000; + dev->aCSR[4] = 0x0115; + dev->aCSR[5] = 0x0000; + dev->aCSR[6] = 0x0000; + dev->aCSR[8] = 0; + dev->aCSR[9] = 0; + dev->aCSR[10] = 0; + dev->aCSR[11] = 0; + dev->aCSR[12] = le16_to_cpu(((uint16_t *)&dev->aPROM[0])[0]); + dev->aCSR[13] = le16_to_cpu(((uint16_t *)&dev->aPROM[0])[1]); + dev->aCSR[14] = le16_to_cpu(((uint16_t *)&dev->aPROM[0])[2]); + dev->aCSR[15] &= 0x21c4; + CSR_RCVRC(dev) = 1; + CSR_XMTRC(dev) = 1; + CSR_RCVRL(dev) = 1; + CSR_XMTRL(dev) = 1; + dev->aCSR[80] = 0x1410; + if (dev->is_pci) { + dev->aCSR[88] = 0x1003; + dev->aCSR[89] = 0x0262; + } else { + dev->aCSR[88] = 0x3003; + dev->aCSR[89] = 0x0000; + } + dev->aCSR[94] = 0x0000; + dev->aCSR[100] = 0x0200; + dev->aCSR[103] = 0x0105; + CSR_MISSC(dev) = 0; + dev->aCSR[114] = 0x0000; + dev->aCSR[122] = 0x0000; + dev->aCSR[124] = 0x0000; +} + + +static void +pcnetUpdateIrq(nic_t *dev) +{ + dev->iISR = 0; + + dev->aCSR[0] &= ~0x0080; /* clear INTR */ + + if ((!(dev->aCSR[3] & 0x4000) && (dev->aCSR[0] & 0x4000)) /* BABL */ || + (!(dev->aCSR[3] & 0x1000) && (dev->aCSR[0] & 0x1000)) /* MISS */ || + (!(dev->aCSR[3] & 0x0100) && (dev->aCSR[0] & 0x0100)) /* IDON */ || + (!(dev->aCSR[3] & 0x0200) && (dev->aCSR[0] & 0x0200)) /* TINT */ || + (!(dev->aCSR[3] & 0x0400) && (dev->aCSR[0] & 0x0400)) /* RINT */ || + (!(dev->aCSR[3] & 0x0800) && (dev->aCSR[0] & 0x0800)) /* MERR */ || + (!(dev->aCSR[4] & 0x0001) && (dev->aCSR[4] & 0x0002)) /* JAB */ || + (!(dev->aCSR[4] & 0x0004) && (dev->aCSR[4] & 0x0008)) /* TXSTRT */ || + (!(dev->aCSR[4] & 0x0010) && (dev->aCSR[4] & 0x0020)) /* RCVO */ || + (!(dev->aCSR[4] & 0x0100) && (dev->aCSR[4] & 0x0200)) /* MFCO */ || + ((dev->aCSR[5] & 0x0040) && (dev->aCSR[5] & 0x0080)) /* EXDINT */ || + ((dev->aCSR[5] & 0x0008) && (dev->aCSR[5] & 0x0010)) /* MPINT */) { + dev->iISR = CSR_INEA(dev); /* CSR_INEA */ + dev->aCSR[0] |= 0x0080; /* set INTR */ + } + + if (dev->aCSR[4] & 0x0080) { /* UINT */ + dev->aCSR[4] &= ~0x0080; + dev->aCSR[4] |= 0x0040; + dev->iISR = 1; /* CSR_INEA */ + dev->aCSR[0] |= 0x0080; /* set INTR */ + pcnetlog(3, "%s: user int\n", dev->name); + } + + if (((dev->aCSR[5] & 0x0400) && (dev->aCSR[5] & 0x0800)) /* SINT */ || + ((dev->aCSR[5] & 0x0100) && (dev->aCSR[5] & 0x0200)) /* SLPINT */ ) { + dev->iISR = 1; /* CSR_INEA */ + dev->aCSR[0] |= 0x0080; /* set INTR */ + } + + pcnetlog(3, "%s: pcnetUpdateIrq: set irq isr=%02x, TINT=%04x\n", dev->name, !!dev->iISR, dev->aCSR[0] & 0x0200); + + /* normal path is to _not_ change the IRQ status */ + if (dev->iISR) + pcnet_do_irq(dev, 1); +} + + +static void +pcnetInit(nic_t *dev) +{ + int i; + pcnetlog(3, "%s: pcnetInit: init_addr=%#010x\n", dev->name, PHYSADDR(dev, CSR_IADR(dev))); + + /** @todo Documentation says that RCVRL and XMTRL are stored as two's complement! + * Software is allowed to write these registers directly. */ +#define PCNET_INIT() do { \ + DMAPageRead(PHYSADDR(dev, CSR_IADR(dev)), \ + (uint8_t *)&initblk, sizeof(initblk)); \ + dev->aCSR[15] = le32_to_cpu(initblk.mode); \ + CSR_RCVRL(dev) = (initblk.rlen < 9) ? (1 << initblk.rlen) : 512; \ + CSR_XMTRL(dev) = (initblk.tlen < 9) ? (1 << initblk.tlen) : 512; \ + dev->aCSR[ 6] = (initblk.tlen << 12) | (initblk.rlen << 8); \ + dev->aCSR[ 8] = le32_to_cpu(initblk.ladrf1); \ + dev->aCSR[ 9] = le32_to_cpu(initblk.ladrf2); \ + dev->aCSR[10] = le32_to_cpu(initblk.ladrf3); \ + dev->aCSR[11] = le32_to_cpu(initblk.ladrf4); \ + dev->aCSR[12] = le32_to_cpu(initblk.padr1); \ + dev->aCSR[13] = le32_to_cpu(initblk.padr2); \ + dev->aCSR[14] = le32_to_cpu(initblk.padr3); \ + dev->GCRDRA = PHYSADDR(dev, initblk.rdra); \ + dev->GCTDRA = PHYSADDR(dev, initblk.tdra); \ +} while (0) + + if (BCR_SSIZE32(dev)) { + struct INITBLK32 initblk; + dev->GCUpperPhys = 0; + PCNET_INIT(); + pcnetlog(3, "%s: initblk.rlen=%#04x, initblk.tlen=%#04x\n", + dev->name, initblk.rlen, initblk.tlen); + } else { + struct INITBLK16 initblk; + dev->GCUpperPhys = (0xff00 & (uint32_t)dev->aCSR[2]) << 16; + PCNET_INIT(); + pcnetlog(3, "%s: initblk.rlen=%#04x, initblk.tlen=%#04x\n", + dev->name, initblk.rlen, initblk.tlen); + } + +#undef PCNET_INIT + + int cbRxBuffers = 0; + for (i = CSR_RCVRL(dev); i >= 1; i--) { + RMD rmd; + uint32_t rdaddr = PHYSADDR(dev, pcnetRdraAddr(dev, i)); + + pcnetDescTouch(dev, rdaddr); + /* At this time it is not guaranteed that the buffers are already initialized. */ + if (pcnetRmdLoad(dev, &rmd, rdaddr, 0)) { + uint32_t cbBuf = 4096U-rmd.rmd1.bcnt; + cbRxBuffers += cbBuf; + } + } + + for (i = CSR_XMTRL(dev); i >= 1; i--) { + uint32_t tdaddr = PHYSADDR(dev, pcnetTdraAddr(dev, i)); + + pcnetDescTouch(dev, tdaddr); + } + + /* + * Heuristics: The Solaris pcn driver allocates too few RX buffers (128 buffers of a + * size of 128 bytes are 16KB in summary) leading to frequent RX buffer overflows. In + * that case we don't signal RX overflows through the CSR0_MISS flag as the driver + * re-initializes the device on every miss. Other guests use at least 32 buffers of + * usually 1536 bytes and should therefore not run into condition. If they are still + * short in RX buffers we notify this condition. + */ + dev->fSignalRxMiss = (cbRxBuffers == 0 || cbRxBuffers >= 32*128); + + CSR_RCVRC(dev) = CSR_RCVRL(dev); + CSR_XMTRC(dev) = CSR_XMTRL(dev); + + /* Reset cached RX and TX states */ + CSR_CRST(dev) = CSR_CRBC(dev) = CSR_NRST(dev) = CSR_NRBC(dev) = 0; + CSR_CXST(dev) = CSR_CXBC(dev) = CSR_NXST(dev) = CSR_NXBC(dev) = 0; + + pcnetlog(1, "%s: Init: SWSTYLE=%d GCRDRA=%#010x[%d] GCTDRA=%#010x[%d]%s\n", + dev->name, BCR_SWSTYLE(dev), + dev->GCRDRA, CSR_RCVRL(dev), dev->GCTDRA, CSR_XMTRL(dev), + !dev->fSignalRxMiss ? " (CSR0_MISS disabled)" : ""); + + if (dev->GCRDRA & (dev->iLog2DescSize - 1)) + pcnetlog(1, "%s: Warning: Misaligned RDRA\n", dev->name); + if (dev->GCTDRA & (dev->iLog2DescSize - 1)) + pcnetlog(1, "%s: Warning: Misaligned TDRA\n", dev->name); + + dev->aCSR[0] |= 0x0101; /* Initialization done */ + dev->aCSR[0] &= ~0x0004; /* clear STOP bit */ +} + + +/** + * Start RX/TX operation. + */ +static void +pcnetStart(nic_t *dev) +{ + pcnetlog(3, "%s: pcnetStart: Poll timer\n", dev->name); + + /* Reset any cached RX/TX descriptor state. */ + CSR_CRDA(dev) = CSR_CRBA(dev) = CSR_NRDA(dev) = CSR_NRBA(dev) = 0; + CSR_CRBC(dev) = CSR_NRBC(dev) = CSR_CRST(dev) = 0; + + if (!CSR_DTX(dev)) + dev->aCSR[0] |= 0x0010; /* set TXON */ + if (!CSR_DRX(dev)) + dev->aCSR[0] |= 0x0020; /* set RXON */ + dev->aCSR[0] &= ~0x0004; /* clear STOP bit */ + dev->aCSR[0] |= 0x0002; /* STRT */ + pcnetPollTimer(dev); +} + + +/** + * Stop RX/TX operation. + */ +static void +pcnetStop(nic_t *dev) +{ + pcnetlog(3, "%s: pcnetStop: Poll timer\n", dev->name); + dev->aCSR[0] = 0x0004; + dev->aCSR[4] &= ~0x02c2; + dev->aCSR[5] &= ~0x0011; + pcnetPollTimer(dev); +} + + +/** + * Poll Receive Descriptor Table Entry and cache the results in the appropriate registers. + * Note: Once a descriptor belongs to the network card (this driver), it cannot be changed + * by the host (the guest driver) anymore. Well, it could but the results are undefined by + * definition. + * @param fSkipCurrent if true, don't scan the current RDTE. + */ +static void +pcnetRdtePoll(nic_t *dev) +{ + /* assume lack of a next receive descriptor */ + CSR_NRST(dev) = 0; + + if (dev->GCRDRA) { + /* + * The current receive message descriptor. + */ + RMD rmd; + int i = CSR_RCVRC(dev); + uint32_t addr; + + if (i < 1) + i = CSR_RCVRL(dev); + + addr = pcnetRdraAddr(dev, i); + CSR_CRDA(dev) = CSR_CRBA(dev) = 0; + CSR_CRBC(dev) = CSR_CRST(dev) = 0; + if (!pcnetRmdLoad(dev, &rmd, PHYSADDR(dev, addr), 1)) + return; + if (!IS_RMD_BAD(rmd)) { + CSR_CRDA(dev) = addr; /* Receive Descriptor Address */ + CSR_CRBA(dev) = rmd.rmd0.rbadr; /* Receive Buffer Address */ + CSR_CRBC(dev) = rmd.rmd1.bcnt; /* Receive Byte Count */ + CSR_CRST(dev) = ((uint32_t *)&rmd)[1] >> 16; /* Receive Status */ + pcnetCanReceive(dev); + } else { + /* This is not problematic since we don't own the descriptor + * We actually do own it, otherwise pcnetRmdLoad would have returned false. + * Don't flood the release log with errors. + */ + if (++dev->uCntBadRMD < 50) + pcnetlog(1, "%s: BAD RMD ENTRIES AT %#010x (i=%d)\n", + dev->name, addr, i); + return; + } + + /* + * The next descriptor. + */ + if (--i < 1) + i = CSR_RCVRL(dev); + addr = pcnetRdraAddr(dev, i); + CSR_NRDA(dev) = CSR_NRBA(dev) = 0; + CSR_NRBC(dev) = 0; + if (!pcnetRmdLoad(dev, &rmd, PHYSADDR(dev, addr), 1)) + return; + if (!IS_RMD_BAD(rmd)) { + CSR_NRDA(dev) = addr; /* Receive Descriptor Address */ + CSR_NRBA(dev) = rmd.rmd0.rbadr; /* Receive Buffer Address */ + CSR_NRBC(dev) = rmd.rmd1.bcnt; /* Receive Byte Count */ + CSR_NRST(dev) = ((uint32_t *)&rmd)[1] >> 16; /* Receive Status */ + } else { + /* This is not problematic since we don't own the descriptor + * We actually do own it, otherwise pcnetRmdLoad would have returned false. + * Don't flood the release log with errors. + */ + if (++dev->uCntBadRMD < 50) + pcnetlog(1, "%s: BAD RMD ENTRIES + AT %#010x (i=%d)\n", + dev->name, addr, i); + return; + } + + /** + * @todo NNRD + */ + } else { + CSR_CRDA(dev) = CSR_CRBA(dev) = CSR_NRDA(dev) = CSR_NRBA(dev) = 0; + CSR_CRBC(dev) = CSR_NRBC(dev) = CSR_CRST(dev) = 0; + } +} + + +/** + * Poll Transmit Descriptor Table Entry + * @return true if transmit descriptors available + */ +static int +pcnetTdtePoll(nic_t *dev, TMD *tmd) +{ + if (dev->GCTDRA) { + uint32_t cxda = pcnetTdraAddr(dev, CSR_XMTRC(dev)); + + if (!pcnetTmdLoad(dev, tmd, PHYSADDR(dev, cxda), 1)) + return 0; + + if (tmd->tmd1.ones != 15) { + pcnetlog(1, "%s: BAD TMD XDA=%#010x\n", + dev->name, PHYSADDR(dev, cxda)); + return 0; + } + + /* previous xmit descriptor */ + CSR_PXDA(dev) = CSR_CXDA(dev); + CSR_PXBC(dev) = CSR_CXBC(dev); + CSR_PXST(dev) = CSR_CXST(dev); + + /* set current transmit descriptor. */ + CSR_CXDA(dev) = cxda; + CSR_CXBC(dev) = tmd->tmd1.bcnt; + CSR_CXST(dev) = ((uint32_t *)tmd)[1] >> 16; + return CARD_IS_OWNER(CSR_CXST(dev)); + } else { + /** @todo consistency with previous receive descriptor */ + CSR_CXDA(dev) = 0; + CSR_CXBC(dev) = CSR_CXST(dev) = 0; + return 0; + } +} + + +/** + * Poll Transmit Descriptor Table Entry + * @return true if transmit descriptors available + */ +static int +pcnetCalcPacketLen(nic_t *dev, int cb) +{ + TMD tmd; + int cbPacket = cb; + uint32_t iDesc = CSR_XMTRC(dev); + uint32_t iFirstDesc = iDesc; + + do + { + /* Advance the ring counter */ + if (iDesc < 2) + iDesc = CSR_XMTRL(dev); + else + iDesc--; + + if (iDesc == iFirstDesc) + break; + + uint32_t addrDesc = pcnetTdraAddr(dev, iDesc); + + if (!pcnetTmdLoad(dev, &tmd, PHYSADDR(dev, addrDesc), 1)) { + /* + * No need to count further since this packet won't be sent anyway + * due to underflow. + */ + pcnetlog(3, "%s: pcnetCalcPacketLen: underflow, return %u\n", dev->name, cbPacket); + return cbPacket; + } + if (tmd.tmd1.ones != 15) { + pcnetlog(1, "%s: BAD TMD XDA=%#010x\n", + dev->name, PHYSADDR(dev, addrDesc)); + pcnetlog(3, "%s: pcnetCalcPacketLen: bad TMD, return %u\n", dev->name, cbPacket); + return cbPacket; + } + pcnetlog(3, "%s: pcnetCalcPacketLen: got valid TMD, cb=%u\n", dev->name, 4096 - tmd.tmd1.bcnt); + cbPacket += 4096 - tmd.tmd1.bcnt; + } while (!tmd.tmd1.enp); + + pcnetlog(3, "#%d pcnetCalcPacketLen: return %u\n", dev->name, cbPacket); + return cbPacket; +} + + +/** + * Write data into guest receive buffers. + */ +static void +pcnetReceiveNoSync(void *priv, uint8_t *buf, int size) +{ + nic_t *dev = (nic_t *)priv; + int is_padr = 0, is_bcast = 0, is_ladr = 0; + uint32_t iRxDesc; + int cbPacket; + uint8_t buf1[60]; + + + if (CSR_DRX(dev) || CSR_STOP(dev) || CSR_SPND(dev) || !size) + return; + + /* if too small buffer, then expand it */ + if (size < 60) { + memcpy(buf1, buf, size); + memset(buf1 + size, 0, 60 - size); + buf = buf1; + size = 60; + } + + pcnetlog(1, "%s: pcnetReceiveNoSync: RX %x:%x:%x:%x:%x:%x > %x:%x:%x:%x:%x:%x len %d\n", dev->name, + buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + size); + + /* + * Perform address matching. + */ + if (CSR_PROM(dev) + || (is_padr = padr_match(dev, buf, size)) + || (is_bcast = padr_bcast(dev, buf, size)) + || (is_ladr = ladr_match(dev, buf, size))) { + + if (HOST_IS_OWNER(CSR_CRST(dev))) + pcnetRdtePoll(dev); + + if (HOST_IS_OWNER(CSR_CRST(dev))) { + /* Not owned by controller. This should not be possible as + * we already called pcnetCanReceive(). */ + const uint32_t cb = 1 << dev->iLog2DescSize; + uint32_t GCPhys = dev->GCRDRA; + iRxDesc = CSR_RCVRL(dev); + + while (iRxDesc-- > 0) { + RMD rmd; + pcnetRmdLoad(dev, &rmd, PHYSADDR(dev, GCPhys), 0); + GCPhys += cb; + } + dev->aCSR[0] |= 0x1000; /* Set MISS flag */ + CSR_MISSC(dev)++; + pcnetlog(2, "%s: pcnetReceiveNoSync: packet missed\n", dev->name); + } else { + RTNETETHERHDR *pEth = (RTNETETHERHDR *)buf; + int fStrip = 0; + int len_802_3; + uint8_t *src = &dev->abRecvBuf[8]; + uint32_t crda = CSR_CRDA(dev); + uint32_t next_crda; + RMD rmd, next_rmd; + + /* + * Ethernet framing considers these two octets to be + * payload type; 802.3 framing considers them to be + * payload length. IEEE 802.3x-1997 restricts Ethernet + * type to be greater than or equal to 1536 (0x0600), so + * that both framings can coexist on the wire. + * + * NB: CSR_ASTRP_RCV bit affects only 802.3 frames! + */ + len_802_3 = cpu_to_be16(pEth->EtherType); + if (len_802_3 < 46 && CSR_ASTRP_RCV(dev)) { + size = MIN(sizeof(RTNETETHERHDR) + len_802_3, size); + fStrip = 1; + } + + memcpy(src, buf, size); + + if (!fStrip) { + /* In loopback mode, Runt Packed Accept is always enabled internally; + * don't do any padding because guest may be looping back very short packets. + */ + if (!CSR_LOOP(dev)) + while (size < 60) + src[size++] = 0; + + uint32_t fcs = UINT32_MAX; + uint8_t *p = src; + + while (p != &src[size]) + CRC(fcs, *p++); + + /* FCS at the end of the packet */ + ((uint32_t *)&src[size])[0] = htonl(fcs); + size += 4; + } + + cbPacket = size; + + pcnetRmdLoad(dev, &rmd, PHYSADDR(dev, crda), 0); + /* if (!CSR_LAPPEN(dev)) */ + rmd.rmd1.stp = 1; + + int cbBuf = MIN(4096 - rmd.rmd1.bcnt, size); + uint32_t rbadr = PHYSADDR(dev, rmd.rmd0.rbadr); + + /* save the old value to check if it was changed as long as we didn't + * hold the critical section */ + iRxDesc = CSR_RCVRC(dev); + + /* We have to leave the critical section here or we risk deadlocking + * with EMT when the write is to an unallocated page or has an access + * handler associated with it. + * + * This shouldn't be a problem because: + * - any modification to the RX descriptor by the driver is + * forbidden as long as it is owned by the device + * - we don't cache any register state beyond this point + */ + + DMAPageWrite(rbadr, src, cbBuf); + + /* RX disabled in the meantime? If so, abort RX. */ + if (CSR_DRX(dev) || CSR_STOP(dev) || CSR_SPND(dev)) { + pcnetlog(3, "%s: RX disabled 1\n", dev->name); + return; + } + + /* Was the register modified in the meantime? If so, don't touch the + * register but still update the RX descriptor. */ + if (iRxDesc == CSR_RCVRC(dev)) { + if (iRxDesc-- < 2) + iRxDesc = CSR_RCVRL(dev); + CSR_RCVRC(dev) = iRxDesc; + } else + iRxDesc = CSR_RCVRC(dev); + + src += cbBuf; + size -= cbBuf; + + while (size > 0) { + /* Read the entire next descriptor as we're likely to need it. */ + next_crda = pcnetRdraAddr(dev, iRxDesc); + + /* Check next descriptor's own bit. If we don't own it, we have + * to quit and write error status into the last descriptor we own. + */ + if (!pcnetRmdLoad(dev, &next_rmd, PHYSADDR(dev, next_crda), 1)) + break; + + /* Write back current descriptor, clear the own bit. */ + pcnetRmdStorePassHost(dev, &rmd, PHYSADDR(dev, crda)); + + /* Switch to the next descriptor */ + crda = next_crda; + rmd = next_rmd; + + cbBuf = MIN(4096 - rmd.rmd1.bcnt, size); + uint32_t rbadr2 = PHYSADDR(dev, rmd.rmd0.rbadr); + + /* We have to leave the critical section here or we risk deadlocking + * with EMT when the write is to an unallocated page or has an access + * handler associated with it. See above for additional comments. */ + DMAPageWrite(rbadr2, src, cbBuf); + + /* RX disabled in the meantime? If so, abort RX. */ + if (CSR_DRX(dev) || CSR_STOP(dev) || CSR_SPND(dev)) { + pcnetlog(3, "%s: RX disabled 2\n", dev->name); + return; + } + + /* Was the register modified in the meantime? If so, don't touch the + * register but still update the RX descriptor. */ + if (iRxDesc == CSR_RCVRC(dev)) { + if (iRxDesc-- < 2) + iRxDesc = CSR_RCVRL(dev); + CSR_RCVRC(dev) = iRxDesc; + } else { + iRxDesc = CSR_RCVRC(dev); + } + + src += cbBuf; + size -= cbBuf; + } + + if (size == 0) { + rmd.rmd1.enp = 1; + rmd.rmd1.pam = !CSR_PROM(dev) && is_padr; + rmd.rmd1.lafm = !CSR_PROM(dev) && is_ladr; + rmd.rmd1.bam = !CSR_PROM(dev) && is_bcast; + rmd.rmd2.mcnt = cbPacket; + rmd.rmd2.zeros = 0; + } else { + pcnetlog(2, "%s: Overflow by %ubytes\n", dev->name, size); + rmd.rmd1.oflo = 1; + rmd.rmd1.buff = 1; + rmd.rmd1.err = 1; + } + + /* write back, clear the own bit */ + pcnetRmdStorePassHost(dev, &rmd, PHYSADDR(dev, crda)); + + dev->aCSR[0] |= 0x0400; + pcnetlog(2, "%s: RINT set, RCVRC=%d CRDA=%#010x\n", dev->name, + CSR_RCVRC(dev), PHYSADDR(dev, CSR_CRDA(dev))); + + /* guest driver is owner: force repoll of current and next RDTEs */ + CSR_CRST(dev) = 0; + } + } + + if (!CSR_LOOP(dev)) + pcnetPollRxTx(dev); + + pcnetUpdateIrq(dev); +} + + +/** + * Actually try transmit frames. + * + * @threads TX or EMT. + */ +static void +pcnetAsyncTransmit(nic_t *dev) +{ + /* + * Just cleared transmit demand if the transmitter is off. + */ + if (!CSR_TXON(dev)) { + dev->aCSR[0] &= ~0x0008; /* Clear TDMD */ + return; + } + + /* + * Iterate the transmit descriptors. + */ + int cMax = 32; + do { + TMD tmd; + if (!pcnetTdtePoll(dev, &tmd)) + break; + + pcnetlog(3, "%s: TMDLOAD %#010x\n", dev->name, PHYSADDR(dev, CSR_CXDA(dev))); + + int fLoopback = CSR_LOOP(dev); + + /* + * The typical case - a complete packet. + */ + if (tmd.tmd1.stp && tmd.tmd1.enp) { + const int cb = 4096 - tmd.tmd1.bcnt; + pcnetlog(1, "%s: pcnetAsyncTransmit: stp&enp: cb=%d xmtrc=%#x\n", dev->name, cb, CSR_XMTRC(dev)); + dev->xmit_pos = cb; + DMAPageRead(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb); + + if (fLoopback) { + /* From the manual: ``A zero length buffer is acceptable as + * long as it is not the last buffer in a chain (STP = 0 and + * ENP = 1).'' That means that the first buffer might have a + * zero length if it is not the last one in the chain. */ + if (cb <= MAX_FRAME) { + if (HOST_IS_OWNER(CSR_CRST(dev))) + pcnetRdtePoll(dev); + + pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos); + } else if (cb == 4096) { + /* The Windows NT4 pcnet driver sometimes marks the first + * unused descriptor as owned by us. Ignore that (by + * passing it back). Do not update the ring counter in this + * case (otherwise that driver becomes even more confused, + * which causes transmit to stall for about 10 seconds). + * This is just a workaround, not a final solution. */ + /* r=frank: IMHO this is the correct implementation. The + * manual says: ``If the OWN bit is set and the buffer + * length is 0, the OWN bit will be cleared. In the C-LANCE + * the buffer length of 0 is interpreted as a 4096-byte + * buffer.'' */ + /* r=michaln: Perhaps not quite right. The C-LANCE (Am79C90) + * datasheet explains that the old LANCE (Am7990) ignored + * the top four bits next to BCNT and a count of 0 was + * interpreted as 4096. In the C-LANCE, that is still the + * case if the top bits are all ones. If all 16 bits are + * zero, the C-LANCE interprets it as zero-length transmit + * buffer. It's not entirely clear if the later models + * (PCnet-ISA, PCnet-PCI) behave like the C-LANCE or not. + * It is possible that the actual behavior of the C-LANCE + * and later hardware is that the buffer lengths are *16-bit* + * two's complement numbers between 0 and 4096. AMD's drivers + * in fact generally treat the length as a 16-bit quantity. */ + pcnetlog(1, "%s: pcnetAsyncTransmit: illegal 4kb frame -> ignoring\n", dev->name); + pcnetTmdStorePassHost(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev))); + break; + } + } else { + pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf stp and enp\n", dev->name); + network_tx(dev->abLoopBuf, dev->xmit_pos); + } + + /* Write back the TMD and pass it to the host (clear own bit). */ + pcnetTmdStorePassHost(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev))); + + /* advance the ring counter register */ + if (CSR_XMTRC(dev) < 2) { + CSR_XMTRC(dev) = CSR_XMTRL(dev); + } else { + CSR_XMTRC(dev)--; + } + } else if (tmd.tmd1.stp) { + /* + * Read TMDs until end-of-packet or tdte poll fails (underflow). + * + * We allocate a maximum sized buffer here since we do not wish to + * waste time finding out how much space we actually need even if + * we could reliably do that on SMP guests. + */ + int cb = 4096 - tmd.tmd1.bcnt; + dev->xmit_pos = pcnetCalcPacketLen(dev, cb); + DMAPageRead(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb); + if (fLoopback) { + if (HOST_IS_OWNER(CSR_CRST(dev))) + pcnetRdtePoll(dev); + pcnetlog(3, "%s: pcnetAsyncTransmit: receive loopback stp\n", dev->name); + pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos); + } else { + pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf stp\n", dev->name); + network_tx(dev->abLoopBuf, dev->xmit_pos); + } + + for (;;) { + /* + * Advance the ring counter register and check the next tmd. + */ + const uint32_t GCPhysPrevTmd = PHYSADDR(dev, CSR_CXDA(dev)); + if (CSR_XMTRC(dev) < 2) + CSR_XMTRC(dev) = CSR_XMTRL(dev); + else + CSR_XMTRC(dev)--; + + TMD dummy; + if (!pcnetTdtePoll(dev, &dummy)) { + /* + * Underflow! + */ + tmd.tmd2.buff = tmd.tmd2.uflo = tmd.tmd1.err = 1; + dev->aCSR[0] |= 0x0200; /* set TINT */ + if (!CSR_DXSUFLO(dev)) /* stop on xmit underflow */ + dev->aCSR[0] &= ~0x0010; /* clear TXON */ + pcnetTmdStorePassHost(dev, &tmd, GCPhysPrevTmd); + pcnetlog(3,"%s: pcnetAsyncTransmit: Underflow!!!\n", dev->name); + break; + } + + /* release & save the previous tmd, pass it to the host */ + pcnetTmdStorePassHost(dev, &tmd, GCPhysPrevTmd); + + /* + * The next tmd. + */ + pcnetTmdLoad(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev)), 0); + cb = 4096 - tmd.tmd1.bcnt; + if (dev->xmit_pos + cb <= MAX_FRAME) { /** @todo this used to be ... + cb < MAX_FRAME. */ + int off = dev->xmit_pos; + dev->xmit_pos = cb + off; + DMAPageRead(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf + off, cb); + } + + /* + * Done already? + */ + if (tmd.tmd1.enp) { + if (fLoopback) { + pcnetlog(3, "%s: pcnetAsyncTransmit: receive loopback enp\n", dev->name); + pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos); + } else { + pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf enp\n", dev->name); + network_tx(dev->abLoopBuf, dev->xmit_pos); + } + + /* Write back the TMD, pass it to the host */ + pcnetTmdStorePassHost(dev, &tmd, PHYSADDR(dev, CSR_CXDA(dev))); + + /* advance the ring counter register */ + if (CSR_XMTRC(dev) < 2) + CSR_XMTRC(dev) = CSR_XMTRL(dev); + else + CSR_XMTRC(dev)--; + break; + } + } /* the loop */ + } + /* Update TDMD, TXSTRT and TINT. */ + dev->aCSR[0] &= ~0x0008; /* clear TDMD */ + dev->aCSR[4] |= 0x0008; /* set TXSTRT */ + if ( !CSR_TOKINTD(dev) /* Transmit OK Interrupt Disable, no infl. on errors. */ + || (CSR_LTINTEN(dev) && tmd.tmd1.ltint) + || tmd.tmd1.err) { + dev->aCSR[0] |= 0x0200; /* set TINT */ + pcnetUpdateIrq(dev); + } + if (--cMax == 0) + break; + } while (CSR_TXON(dev)); /* transfer on */ +} + + +/** + * Poll for changes in RX and TX descriptor rings. + */ +static void +pcnetPollRxTx(nic_t *dev) +{ + if (CSR_RXON(dev)) { + /* + * The second case is important for pcnetWaitReceiveAvail(): If CSR_CRST(dev) was + * true but pcnetCanReceive() returned false for some other reason we need to check + * _now_ if we have to wakeup pcnetWaitReceiveAvail(). + */ + if (HOST_IS_OWNER(CSR_CRST(dev))) + pcnetRdtePoll(dev); + } + + if (CSR_TDMD(dev) || (CSR_TXON(dev) && !CSR_DPOLL(dev))) + pcnetAsyncTransmit(dev); +} + + +static void +pcnetPollTimer(nic_t *dev) +{ + if (CSR_TDMD(dev)) + pcnetAsyncTransmit(dev); + + pcnetUpdateIrq(dev); + + if (!CSR_STOP(dev) && !CSR_SPND(dev) && !CSR_DPOLL(dev)) + pcnetPollRxTx(dev); +} + + +static void +pcnetHardReset(nic_t *dev) +{ + pcnetlog(2, "%s: pcnetHardReset\n", dev->name); + + dev->aBCR[BCR_MSRDA] = 0x0005; + dev->aBCR[BCR_MSWRA] = 0x0005; + dev->aBCR[BCR_MC ] = 0x0002; + dev->aBCR[BCR_LNKST] = 0x00c0; + dev->aBCR[BCR_LED1 ] = 0x0084; + dev->aBCR[BCR_LED2 ] = 0x0088; + dev->aBCR[BCR_LED3 ] = 0x0090; + + /* For ISA PnP cards, BCR8 reports IRQ/DMA (e.g. 0x0035 means IRQ 3, DMA 5). */ + if (dev->board == PCNET_ISA) + dev->aBCR[8] = (dev->dma_channel) | (dev->base_irq << 4); + + dev->aBCR[BCR_FDC ] = 0x0000; + dev->aBCR[BCR_BSBC ] = 0x9001; + dev->aBCR[BCR_EECAS] = 0x0002; + dev->aBCR[BCR_SWS ] = 0x0200; + dev->iLog2DescSize = 3; + dev->aBCR[BCR_PLAT ] = 0xff06; + + /* Reset the error counter. */ + dev->uCntBadRMD = 0; + + pcnetSoftReset(dev); +} + + +static void +pcnet_csr_writew(nic_t *dev, uint16_t rap, uint16_t val) +{ + uint16_t old_inea = CSR_INEA(dev); + + pcnetlog(1, "%s: pcnet_csr_writew: rap=%d val=%#06x\n", dev->name, rap, val); + switch (rap) { + case 0: + /* Clear any interrupt flags. */ + dev->aCSR[0] &= ~(val & 0x7f00); + dev->aCSR[0] = (dev->aCSR[0] & ~0x0040) | (val & 0x0048); + val = (val & 0x007f) | (dev->aCSR[0] & 0x7f00); + + /* If STOP, STRT and INIT are set, clear STRT and INIT */ + if ((val & 7) == 7) + val &= ~3; + + pcnetlog(2, "%s: CSR0 val = %04x, val2 = %04x\n", dev->name, val, dev->aCSR[0]); + + if (old_inea && !(val & 0x40)) { + pcnetlog(3, "%s: pcnet_csr_writew(): Disable IRQ\n", dev->name); + dev->iISR = 0; + dev->aCSR[0] &= ~0x0080; + pcnet_do_irq(dev, 0); + } + + if (!CSR_STOP(dev) && (val & 4)) { + pcnetlog(3, "%s: pcnet_csr_writew(): Stop\n", dev->name); + pcnetStop(dev); + } + + if (!CSR_INIT(dev) && (val & 1)) { + pcnetlog(3, "%s: pcnet_csr_writew(): Init\n", dev->name); + pcnetInit(dev); + } + + if (!CSR_STRT(dev) && (val & 2)) { + pcnetlog(3, "%s: pcnet_csr_writew(): Start\n", dev->name); + pcnetStart(dev); + } + + if (CSR_TDMD(dev)) { + pcnetlog(3, "%s: pcnet_csr_writew(): Transmit\n", dev->name); + pcnetAsyncTransmit(dev); + } + + return; + case 2: /* IADRH */ + if (dev->board == PCNET_ISA) + val &= 0x00ff; /* Upper 8 bits ignored on ISA chips. */ + case 1: /* IADRL */ + case 8: /* LADRF 0..15 */ + case 9: /* LADRF 16..31 */ + case 10: /* LADRF 32..47 */ + case 11: /* LADRF 48..63 */ + case 12: /* PADR 0..15 */ + case 13: /* PADR 16..31 */ + case 14: /* PADR 32..47 */ + case 18: /* CRBAL */ + case 19: /* CRBAU */ + case 20: /* CXBAL */ + case 21: /* CXBAU */ + case 22: /* NRBAL */ + case 23: /* NRBAU */ + case 26: /* NRDAL */ + case 27: /* NRDAU */ + case 28: /* CRDAL */ + case 29: /* CRDAU */ + case 32: /* NXDAL */ + case 33: /* NXDAU */ + case 34: /* CXDAL */ + case 35: /* CXDAU */ + case 36: /* NNRDL */ + case 37: /* NNRDU */ + case 38: /* NNXDL */ + case 39: /* NNXDU */ + case 40: /* CRBCL */ + case 41: /* CRBCU */ + case 42: /* CXBCL */ + case 43: /* CXBCU */ + case 44: /* NRBCL */ + case 45: /* NRBCU */ + case 46: /* POLL */ + case 47: /* POLLINT */ + case 72: /* RCVRC */ + case 74: /* XMTRC */ + case 112: /* MISSC */ + if (CSR_STOP(dev) || CSR_SPND(dev)) + break; + return; + case 3: /* Interrupt Mask and Deferral Control */ + break; + case 4: /* Test and Features Control */ + dev->aCSR[4] &= ~(val & 0x026a); + val &= ~0x026a; + val |= dev->aCSR[4] & 0x026a; + break; + case 5: /* Extended Control and Interrupt 1 */ + dev->aCSR[5] &= ~(val & 0x0a90); + val &= ~0x0a90; + val |= dev->aCSR[5] & 0x0a90; + break; + case 16: + pcnet_csr_writew(dev,1,val); + return; + case 17: + pcnet_csr_writew(dev,2,val); + return; + /* + * 24 and 25 are the Base Address of Receive Descriptor. + * We combine and mirror these in GCRDRA. + */ + case 24: /* BADRL */ + case 25: /* BADRU */ + if (!CSR_STOP(dev) && !CSR_SPND(dev)) { + pcnetlog(3, "%s: WRITE CSR%d, %#06x, ignoring!!\n", dev->name, rap, val); + return; + } + if (rap == 24) + dev->GCRDRA = (dev->GCRDRA & 0xffff0000) | (val & 0x0000ffff); + else + dev->GCRDRA = (dev->GCRDRA & 0x0000ffff) | ((val & 0x0000ffff) << 16); + pcnetlog(3, "%s: WRITE CSR%d, %#06x => GCRDRA=%08x (alt init)\n", dev->name, rap, val, dev->GCRDRA); + if (dev->GCRDRA & (dev->iLog2DescSize - 1)) + pcnetlog(1, "%s: Warning: Misaligned RDRA (GCRDRA=%#010x)\n", dev->name, dev->GCRDRA); + break; + /* + * 30 & 31 are the Base Address of Transmit Descriptor. + * We combine and mirrorthese in GCTDRA. + */ + case 30: /* BADXL */ + case 31: /* BADXU */ + if (!CSR_STOP(dev) && !CSR_SPND(dev)) { + pcnetlog(3, "%s: WRITE CSR%d, %#06x !!\n", dev->name, rap, val); + return; + } + if (rap == 30) + dev->GCTDRA = (dev->GCTDRA & 0xffff0000) | (val & 0x0000ffff); + else + dev->GCTDRA = (dev->GCTDRA & 0x0000ffff) | ((val & 0x0000ffff) << 16); + pcnetlog(3, "%s: WRITE CSR%d, %#06x => GCTDRA=%08x (alt init)\n", dev->name, rap, val, dev->GCTDRA); + if (dev->GCTDRA & (dev->iLog2DescSize - 1)) + pcnetlog(1, "%s: Warning: Misaligned TDRA (GCTDRA=%#010x)\n", dev->name, dev->GCTDRA); + break; + case 58: /* Software Style */ + pcnet_bcr_writew(dev,BCR_SWS,val); + break; + /* + * Registers 76 and 78 aren't stored correctly (see todos), but I'm don't dare + * try fix that right now. So, as a quick hack for 'alt init' I'll just correct them here. + */ + case 76: /* RCVRL */ /** @todo call pcnetUpdateRingHandlers */ + /** @todo receive ring length is stored in two's complement! */ + case 78: /* XMTRL */ /** @todo call pcnetUpdateRingHandlers */ + /** @todo transmit ring length is stored in two's complement! */ + if (!CSR_STOP(dev) && !CSR_SPND(dev)) { + pcnetlog(3, "%s: WRITE CSR%d, %#06x !!\n", dev->name, rap, val); + return; + } + pcnetlog(3, "%s: WRITE CSR%d, %#06x (hacked %#06x) (alt init)\n", dev->name, + rap, val, 1 + ~val); + val = 1 + ~val; + + /* + * HACK ALERT! Set the counter registers too. + */ + dev->aCSR[rap - 4] = val; + break; + default: + return; + } + + dev->aCSR[rap] = val; +} + + +/** + * Encode a 32-bit link speed into a custom 16-bit floating-point value + */ +static uint16_t +pcnetLinkSpd(uint32_t speed) +{ + unsigned exp = 0; + + while (speed & 0xFFFFE000) { + speed /= 10; + ++exp; + } + return (exp << 13) | speed; +} + + +static uint16_t +pcnet_csr_readw(nic_t *dev, uint16_t rap) +{ + uint16_t val; + + switch (rap) { + case 0: + pcnetUpdateIrq(dev); + val = dev->aCSR[0]; + val |= (val & 0x7800) ? 0x8000 : 0; + break; + case 16: + return pcnet_csr_readw(dev,1); + case 17: + return pcnet_csr_readw(dev,2); + case 58: + return pcnet_bcr_readw(dev,BCR_SWS); + case 68: /* Custom register to pass link speed to driver */ + return pcnetLinkSpd(dev->u32LinkSpeed); + case 88: + val = dev->aCSR[89]; + val <<= 16; + val |= dev->aCSR[88]; + break; + default: + val = dev->aCSR[rap]; + break; + } + pcnetlog(3, "%s: pcnet_csr_readw rap=%d val=0x%04x\n", dev->name, rap, val); + return val; +} + + +static void +pcnet_bcr_writew(nic_t *dev, uint16_t rap, uint16_t val) +{ + rap &= 0x7f; + pcnetlog(3, "%s: pcnet_bcr_writew rap=%d val=0x%04x\n", dev->name, rap, val); + switch (rap) { + case BCR_SWS: + if (!(CSR_STOP(dev) || CSR_SPND(dev))) + return; + val &= ~0x0300; + switch (val & 0x00ff) { + default: + case 0: + val |= 0x0200; /* 16 bit */ + dev->iLog2DescSize = 3; + dev->GCUpperPhys = (0xff00 & dev->aCSR[2]) << 16; + break; + case 1: + val |= 0x0100; /* 32 bit */ + dev->iLog2DescSize = 4; + dev->GCUpperPhys = 0; + break; + case 2: + case 3: + val |= 0x0300; /* 32 bit */ + dev->iLog2DescSize = 4; + dev->GCUpperPhys = 0; + break; + } + /* fall through */ + case BCR_LNKST: + case BCR_LED1: + case BCR_LED2: + case BCR_LED3: + case BCR_MC: + case BCR_FDC: + case BCR_BSBC: + case BCR_EECAS: + case BCR_PLAT: + dev->aBCR[rap] = val; + break; + default: + break; + } +} + + +static uint16_t +pcnet_bcr_readw(nic_t *dev, uint16_t rap) +{ + uint16_t val; + rap &= 0x7f; + switch (rap) { + case BCR_LNKST: + case BCR_LED1: + case BCR_LED2: + case BCR_LED3: + val = dev->aBCR[rap] & ~0x8000; + val |= (val & 0x017f & dev->u32Lnkst) ? 0x8000 : 0; + break; + default: + val = rap < BCR_MAX_RAP ? dev->aBCR[rap] : 0; + break; + } + + pcnetlog(3, "pcnet_bcr_readw rap=%d val=0x%04x\n", rap, val); + return val; +} + + +static void +pcnet_word_write(nic_t *dev, uint32_t addr, uint16_t val) +{ + pcnetlog(3, "%s: pcnet_word_write: addr = %04x, val = %04x, DWIO not set = %04x\n", dev->name, addr & 0x0f, val, !BCR_DWIO(dev)); + + if (!BCR_DWIO(dev)) { + switch (addr & 0x0f) { + case 0x00: /* RDP */ + pcnetPollTimer(dev); + pcnet_csr_writew(dev, dev->u32RAP, val); + pcnetUpdateIrq(dev); + break; + case 0x02: + dev->u32RAP = val & 0x7f; + break; + case 0x06: + pcnet_bcr_writew(dev, dev->u32RAP, val); + break; + } + } +} + + +static uint16_t +pcnet_word_read(nic_t *dev, uint32_t addr) +{ + uint16_t val = 0xffff; + + if (!BCR_DWIO(dev)) { + switch (addr & 0x0f) { + case 0x00: /* RDP */ + /** @note if we're not polling, then the guest will tell us when to poll by setting TDMD in CSR0 */ + /** Polling is then useless here and possibly expensive. */ + if (!CSR_DPOLL(dev)) + pcnetPollTimer(dev); + + val = pcnet_csr_readw(dev, dev->u32RAP); + if (dev->u32RAP == 0) + goto skip_update_irq; + break; + case 0x02: + val = dev->u32RAP; + goto skip_update_irq; + case 0x04: + pcnetSoftReset(dev); + val = 0; + break; + case 0x06: + val = pcnet_bcr_readw(dev, dev->u32RAP); + break; + } + } + + pcnetUpdateIrq(dev); + +skip_update_irq: + pcnetlog(3, "%s: pcnet_word_read: addr = %04x, val = %04x, DWIO not set = %04x\n", dev->name, addr & 0x0f, val, !BCR_DWIO(dev)); + + return(val); +} + + +static void +pcnet_dword_write(nic_t *dev, uint32_t addr, uint32_t val) +{ + if (BCR_DWIO(dev)) { + switch (addr & 0x0f) { + case 0x00: /* RDP */ + pcnetPollTimer(dev); + pcnet_csr_writew(dev, dev->u32RAP, val & 0xffff); + pcnetUpdateIrq(dev); + break; + case 0x04: + dev->u32RAP = val & 0x7f; + break; + case 0x0c: + pcnet_bcr_writew(dev, dev->u32RAP, val & 0xffff); + break; + } + } else if ((addr & 0x0f) == 0) { + /* switch device to dword i/o mode */ + pcnet_bcr_writew(dev, BCR_BSBC, pcnet_bcr_readw(dev, BCR_BSBC) | 0x0080); + pcnetlog(3, "%s: device switched into dword i/o mode\n", dev->name); + }; +} + + +static uint32_t +pcnet_dword_read(nic_t *dev, uint32_t addr) +{ + uint32_t val = 0xffffffff; + + if (BCR_DWIO(dev)) { + switch (addr & 0x0f) { + case 0x00: /* RDP */ + if (!CSR_DPOLL(dev)) + pcnetPollTimer(dev); + val = pcnet_csr_readw(dev, dev->u32RAP); + if (dev->u32RAP == 0) + goto skip_update_irq; + break; + case 0x04: + val = dev->u32RAP; + goto skip_update_irq; + case 0x08: + pcnetSoftReset(dev); + val = 0; + break; + case 0x0c: + val = pcnet_bcr_readw(dev, dev->u32RAP); + break; + } + } + + pcnetUpdateIrq(dev); + +skip_update_irq: + pcnetlog(3, "%s: Read Long mode, addr = %08x, val = %08x\n", dev->name, addr, val); + return(val); +} + + +static void +pcnet_aprom_writeb(nic_t *dev, uint32_t addr, uint32_t val) +{ + /* Check APROMWE bit to enable write access */ + if (pcnet_bcr_readw(dev, 2) & 0x80) + dev->aPROM[addr & 0x0f] = val & 0xff; +} + + +static uint32_t +pcnet_aprom_readb(nic_t *dev, uint32_t addr) +{ + uint32_t val = dev->aPROM[addr & 15]; + + return val; +} + + +static void +pcnet_write(nic_t *dev, uint32_t addr, uint32_t val, int len) +{ + uint16_t off = addr & 0x1f; + + pcnetlog(3, "%s: write addr %x, val %x, off %x, len %d\n", dev->name, addr, val, off, len); + + if (off < 0x10) { + if (!BCR_DWIO(dev) && len == 1) + pcnet_aprom_writeb(dev, addr, val); + else if (!BCR_DWIO(dev) && ((addr & 1) == 0) && len == 2) { + pcnet_aprom_writeb(dev, addr, val); + pcnet_aprom_writeb(dev, addr + 1, val >> 8); + } else if (BCR_DWIO(dev) && ((addr & 3) == 0) && len == 4) { + pcnet_aprom_writeb(dev, addr, val); + pcnet_aprom_writeb(dev, addr + 1, val >> 8); + pcnet_aprom_writeb(dev, addr + 2, val >> 16); + pcnet_aprom_writeb(dev, addr + 3, val >> 24); + } + } else { + if (len == 2) + pcnet_word_write(dev, addr, val); + else if (len == 4) + pcnet_dword_write(dev, addr, val); + } +} + + +static void +pcnet_writeb(uint16_t addr, uint8_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 1); +} + + +static void +pcnet_writew(uint16_t addr, uint16_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 2); +} + + +static void +pcnet_writel(uint16_t addr, uint32_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 4); +} + + +static uint32_t +pcnet_read(nic_t *dev, uint32_t addr, int len) +{ + uint32_t retval = 0xffffffff; + uint16_t off = addr & 0x1f; + + pcnetlog(3, "%s: read addr %x, off %x, len %d\n", dev->name, addr, off, len); + + if (off < 0x10) { + if (!BCR_DWIO(dev) && len == 1) + retval = pcnet_aprom_readb(dev, addr); + else if (!BCR_DWIO(dev) && ((addr & 1) == 0) && len == 2) + retval = pcnet_aprom_readb(dev, addr) | (pcnet_aprom_readb(dev, addr + 1) << 8); + else if (BCR_DWIO(dev) && ((addr & 3) == 0) && len == 4) { + retval = pcnet_aprom_readb(dev, addr) | (pcnet_aprom_readb(dev, addr + 1) << 8) | + (pcnet_aprom_readb(dev, addr + 2) << 16) | (pcnet_aprom_readb(dev, addr + 3) << 24); + } + } else { + if (len == 2) + retval = pcnet_word_read(dev, addr); + else if (len == 4) + retval = pcnet_dword_read(dev, addr); + } + + pcnetlog(3, "%s: value in read - %08x\n", dev->name, retval); + return(retval); +} + + +static uint8_t +pcnet_readb(uint16_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 1)); +} + + +static uint16_t +pcnet_readw(uint16_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 2)); +} + + +static uint32_t +pcnet_readl(uint16_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 4)); +} + + +static void +pcnet_mmio_writeb(uint32_t addr, uint8_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 1); +} + + +static void +pcnet_mmio_writew(uint32_t addr, uint16_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 2); +} + + +static void +pcnet_mmio_writel(uint32_t addr, uint32_t val, void *priv) +{ + pcnet_write((nic_t *)priv, addr, val, 4); +} + + +static uint8_t +pcnet_mmio_readb(uint32_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 1)); +} + + +static uint16_t +pcnet_mmio_readw(uint32_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 2)); +} + + +static uint32_t +pcnet_mmio_readl(uint32_t addr, void *priv) +{ + return(pcnet_read((nic_t *)priv, addr, 4)); +} + + +static void +pcnet_mem_init(nic_t *dev, uint32_t addr) +{ + mem_mapping_add(&dev->mmio_mapping, addr, 32, + pcnet_mmio_readb, pcnet_mmio_readw, pcnet_mmio_readl, + pcnet_mmio_writeb, pcnet_mmio_writew, pcnet_mmio_writel, + NULL, MEM_MAPPING_EXTERNAL, dev); +} + + +static void +pcnet_mem_set_addr(nic_t *dev, uint32_t base) +{ + mem_mapping_set_addr(&dev->mmio_mapping, base, 32); +} + + +static void +pcnet_mem_disable(nic_t *dev) +{ + mem_mapping_disable(&dev->mmio_mapping); +} + + +static void +pcnet_ioremove(nic_t *dev, uint16_t addr, int len) +{ + if (dev->is_pci || dev->is_vlb) { + io_removehandler(addr, len, + pcnet_readb, pcnet_readw, pcnet_readl, + pcnet_writeb, pcnet_writew, pcnet_writel, dev); + } else { + io_removehandler(addr, len, + pcnet_readb, pcnet_readw, NULL, + pcnet_writeb, pcnet_writew, NULL, dev); + } +} + + +static void +pcnet_ioset(nic_t *dev, uint16_t addr, int len) +{ + pcnet_ioremove(dev, addr, len); + + if (dev->is_pci || dev->is_vlb) { + io_sethandler(addr, len, + pcnet_readb, pcnet_readw, pcnet_readl, + pcnet_writeb, pcnet_writew, pcnet_writel, dev); + } else { + io_sethandler(addr, len, + pcnet_readb, pcnet_readw, NULL, + pcnet_writeb, pcnet_writew, NULL, dev); + } +} + + +static void +pcnet_pci_write(int func, int addr, uint8_t val, void *p) +{ + nic_t *dev = (nic_t *)p; + uint8_t valxor; + + pcnetlog(4, "%s: Write value %02X to register %02X\n", dev->name, val, addr & 0xff); + + switch (addr) { + case 0x04: + valxor = (val & 0x57) ^ pcnet_pci_regs[addr]; + if (valxor & PCI_COMMAND_IO) { + pcnet_ioremove(dev, dev->PCIBase, 32); + if ((dev->PCIBase != 0) && (val & PCI_COMMAND_IO)) + pcnet_ioset(dev, dev->PCIBase, 32); + } + if (valxor & PCI_COMMAND_MEM) { + pcnet_mem_disable(dev); + if ((dev->MMIOBase != 0) && (val & PCI_COMMAND_MEM)) + pcnet_mem_set_addr(dev, dev->MMIOBase); + } + pcnet_pci_regs[addr] = val & 0x57; + break; + + case 0x05: + pcnet_pci_regs[addr] = val & 0x01; + break; + + case 0x0D: + pcnet_pci_regs[addr] = val; + break; + + case 0x10: case 0x11: case 0x12: case 0x13: + /* I/O Base set. */ + /* First, remove the old I/O. */ + pcnet_ioremove(dev, dev->PCIBase, 32); + /* Then let's set the PCI regs. */ + pcnet_pci_bar[0].addr_regs[addr & 3] = val; + /* Then let's calculate the new I/O base. */ + pcnet_pci_bar[0].addr &= 0xff00; + dev->PCIBase = pcnet_pci_bar[0].addr; + /* Log the new base. */ + pcnetlog(4, "%s: New I/O base is %04X\n" , dev->name, dev->PCIBase); + /* We're done, so get out of the here. */ + if (pcnet_pci_regs[4] & PCI_COMMAND_IO) { + if (dev->PCIBase != 0) + pcnet_ioset(dev, dev->PCIBase, 32); + } + return; + + case 0x15: case 0x16: case 0x17: + /* MMIO Base set. */ + /* First, remove the old I/O. */ + pcnet_mem_disable(dev); + /* Then let's set the PCI regs. */ + pcnet_pci_bar[1].addr_regs[addr & 3] = val; + /* Then let's calculate the new I/O base. */ + pcnet_pci_bar[1].addr &= 0xffffc000; + dev->MMIOBase = pcnet_pci_bar[1].addr & 0xffffc000; + /* Log the new base. */ + pcnetlog(4, "%s: New MMIO base is %08X\n" , dev->name, dev->MMIOBase); + /* We're done, so get out of the here. */ + if (pcnet_pci_regs[4] & PCI_COMMAND_MEM) { + if (dev->MMIOBase != 0) + pcnet_mem_set_addr(dev, dev->MMIOBase); + } + return; + + case 0x3C: + dev->base_irq = val; + pcnet_pci_regs[addr] = val; + return; + } +} + + +static uint8_t +pcnet_pci_read(int func, int addr, void *p) +{ + nic_t *dev = (nic_t *)p; + + pcnetlog(4, "%s: Read to register %02X\n", dev->name, addr & 0xff); + + switch (addr) { + case 0x00: + return 0x22; + case 0x01: + return 0x10; + case 0x02: + return 0x00; + case 0x03: + return 0x20; + case 0x04: + return pcnet_pci_regs[0x04] & 0x57; /*Respond to IO and memory accesses*/ + case 0x05: + return pcnet_pci_regs[0x05] & 0x01; + case 0x06: + return 0x80; + case 0x07: + return 2; + case 0x08: + return 0x10; /*Revision ID*/ + case 0x09: + return 0; /*Programming interface*/ + case 0x0A: + return 0; /*devubclass*/ + case 0x0B: + return 2; /*Class code*/ + case 0x0D: + return pcnet_pci_regs[addr]; + case 0x0E: + return 0; /*Header type */ + case 0x10: + return 1; /*I/O space*/ + case 0x11: + return pcnet_pci_bar[0].addr_regs[1]; + case 0x12: + return pcnet_pci_bar[0].addr_regs[2]; + case 0x13: + return pcnet_pci_bar[0].addr_regs[3]; + case 0x14: + return 0; /*Memory space*/ + case 0x15: + return pcnet_pci_bar[1].addr_regs[1]; + case 0x16: + return pcnet_pci_bar[1].addr_regs[2]; + case 0x17: + return pcnet_pci_bar[1].addr_regs[3]; + case 0x3C: + return dev->base_irq; + case 0x3D: + return PCI_INTA; + case 0x3E: + return 0x06; + case 0x3F: + return 0xff; + } + + return(0); +} + + +/** + * Check if the device/driver can receive data now. + * + * Worker for pcnetNetworkDown_WaitReceiveAvail(). This must be called before + * the pfnRecieve() method is called. + * + * @returns VBox status code. + * @param pThis The PCnet instance data. + */ +static void +pcnetCanReceive(nic_t *dev) +{ + if (!CSR_DRX(dev) && !CSR_STOP(dev) && !CSR_SPND(dev)) { + if (HOST_IS_OWNER(CSR_CRST(dev)) && dev->GCRDRA) + pcnetRdtePoll(dev); + + if (HOST_IS_OWNER(CSR_CRST(dev))) { + /** @todo Notify the guest _now_. Will potentially increase the interrupt load */ + if (dev->fSignalRxMiss) + dev->aCSR[0] |= 0x1000; /* Set MISS flag */ + } + } +} + + +static void * +pcnet_init(const device_t *info) +{ + uint32_t mac; + nic_t *dev; +#ifdef ENABLE_NIC_LOG + int i; +#endif + int c; + uint16_t checksum; + + /* Get the desired debug level. */ +#ifdef ENABLE_NIC_LOG + i = device_get_config_int("debug"); + if (i > 0) pcnet_do_log = i; +#endif + + dev = malloc(sizeof(nic_t)); + memset(dev, 0x00, sizeof(nic_t)); + dev->name = info->name; + dev->board = info->local; + + dev->is_pci = !!(info->flags & DEVICE_PCI); + dev->is_vlb = !!(info->flags & DEVICE_VLB); + + if (dev->is_pci) { + pcnet_mem_init(dev, 0x0fffff00); + pcnet_mem_disable(dev); + } + + dev->maclocal[0] = 0x00; /* 00:0C:87 (AMD OID) */ + dev->maclocal[1] = 0x0C; + dev->maclocal[2] = 0x87; + + /* See if we have a local MAC address configured. */ + mac = device_get_config_mac("mac", -1); + + /* 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); + } + + memcpy(dev->aPROM, dev->maclocal, sizeof(dev->maclocal)); + + /* Reserved Location: must be 00h */ + dev->aPROM[6] = dev->aPROM[7] = 0x00; + + /* Reserved Location: must be 00h */ + dev->aPROM[8] = 0x00; + + /* Hardware ID: must be 11h if compatibility to AMD drivers is desired */ + if (dev->is_pci) + dev->aPROM[9] = 0x11; + else if (dev->is_vlb) + dev->aPROM[9] = 0x10; + else + dev->aPROM[9] = 0x00; + + /* User programmable space, init with 0 */ + dev->aPROM[10] = dev->aPROM[11] = 0x00; + + /* LSByte of two-byte checksum, which is the sum of bytes 00h-0Bh + and bytes 0Eh and 0Fh, must therefore be initialized with 0! */ + dev->aPROM[12] = dev->aPROM[13] = 0x00; + + /* Must be ASCII W (57h) if compatibility to AMD + driver software is desired */ + dev->aPROM[14] = dev->aPROM[15] = 0x57; + + for (c = 0, checksum = 0; c < 16; c++) + checksum += dev->aPROM[c]; + + *(uint16_t *)&dev->aPROM[12] = cpu_to_le16(checksum); + + /* + * Make this device known to the I/O system. + * PCI devices start with address spaces inactive. + */ + if (dev->is_pci) { + /* + * Configure the PCI space registers. + * + * We do this here, so the I/O routines are generic. + */ + + /* Enable our address space in PCI. */ + pcnet_pci_bar[0].addr_regs[0] = 1; + pcnet_pci_bar[1].addr_regs[0] = 0; + pcnet_pci_regs[0x04] = 3; + + /* Add device to the PCI bus, keep its slot number. */ + dev->card = pci_add_card(PCI_ADD_NORMAL, + pcnet_pci_read, pcnet_pci_write, dev); + } else { + dev->base_address = device_get_config_hex16("base"); + dev->base_irq = device_get_config_int("irq"); + if (dev->is_vlb) + dev->dma_channel = 0; + else + dev->dma_channel = device_get_config_int("dma"); + pcnet_ioset(dev, dev->base_address, 0x20); + } + + pcnetlog(2, "%s: I/O=%04x, IRQ=%d, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, dev->base_address, dev->base_irq, + dev->aPROM[0], dev->aPROM[1], dev->aPROM[2], + dev->aPROM[3], dev->aPROM[4], dev->aPROM[5]); + + pcnetlog(1, "%s: %s attached IO=0x%X IRQ=%d\n", dev->name, + dev->is_pci?"PCI":"ISA", dev->base_address, dev->base_irq); + + /* Reset the board. */ + pcnetHardReset(dev); + + /* Attach ourselves to the network module. */ + network_attach(dev, dev->aPROM, pcnetReceiveNoSync); + + return(dev); +} + + +static void +pcnet_close(void *priv) +{ + nic_t *dev = (nic_t *)priv; + + pcnetlog(1, "%s: closed\n", dev->name); + + free(dev); +} + + +static const device_config_t pcnet_pci_config[] = +{ + { + "mac", "MAC Address", CONFIG_MAC, "", -1 + }, + { + "", "", -1 + } +}; + + +static const device_config_t pcnet_isa_config[] = +{ + { + "base", "Address", CONFIG_HEX16, "", 0x300, + { + { + "0x300", 0x300 + }, + { + "0x320", 0x320 + }, + { + "0x340", 0x340 + }, + { + "0x360", 0x360 + }, + { + "" + } + }, + }, + { + "irq", "IRQ", CONFIG_SELECTION, "", 3, + { + { + "IRQ 3", 3 + }, + { + "IRQ 4", 4 + }, + { + "IRQ 5", 5 + }, + { + "IRQ 9", 9 + }, + { + "" + } + }, + }, + { + "dma", "DMA channel", CONFIG_SELECTION, "", 5, + { + { + "DMA 3", 3 + }, + { + "DMA 5", 5 + }, + { + "DMA 6", 6 + }, + { + "DMA 7", 7 + }, + { + "" + } + }, + }, + { + "mac", "MAC Address", CONFIG_MAC, "", -1 + }, + { + "", "", -1 + } +}; + +static const device_config_t pcnet_vlb_config[] = +{ + { + "base", "Address", CONFIG_HEX16, "", 0x300, + { + { + "0x300", 0x300 + }, + { + "0x320", 0x320 + }, + { + "0x340", 0x340 + }, + { + "0x360", 0x360 + }, + { + "" + } + }, + }, + { + "irq", "IRQ", CONFIG_SELECTION, "", 3, + { + { + "IRQ 3", 3 + }, + { + "IRQ 4", 4 + }, + { + "IRQ 5", 5 + }, + { + "IRQ 9", 9 + }, + { + "" + } + }, + }, + { + "mac", "MAC Address", CONFIG_MAC, "", -1 + }, + { + "", "", -1 + } +}; + +const device_t pcnet_pci_device = { + "AMD PCnet-PCI", + DEVICE_PCI, + PCNET_PCI, + pcnet_init, pcnet_close, NULL, + NULL, NULL, NULL, + pcnet_pci_config +}; + +const device_t pcnet_isa_device = { + "AMD PCnet-ISA", + DEVICE_AT | DEVICE_ISA, + PCNET_ISA, + pcnet_init, pcnet_close, NULL, + NULL, NULL, NULL, + pcnet_isa_config +}; + +const device_t pcnet_vlb_device = { + "AMD PCnet-VL", + DEVICE_VLB, + PCNET_VLB, + pcnet_init, pcnet_close, NULL, + NULL, NULL, NULL, + pcnet_vlb_config +}; diff --git a/src/network/net_pcnet.h b/src/network/net_pcnet.h new file mode 100644 index 000000000..da4859368 --- /dev/null +++ b/src/network/net_pcnet.h @@ -0,0 +1,35 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * Emulation of the AMD PCnet LANCE NIC controller for both the ISA + * and PCI buses. + * + * Version: @(#)net_pcnet.c 1.0.0 2019/11/09 + * + * Authors: Miran Grca, + * TheCollector1995, + * Antony T Curtis + * + * Copyright 2004-2019 Antony T Curtis + * Copyright 2016-2019 Miran Grca. + */ +#ifndef NET_PCNET_H +# define NET_PCNET_H + + +enum { + PCNET_NONE = 0, + PCNET_ISA = 1, /* 16-bit ISA */ + PCNET_PCI = 2, /* 32-bit PCI */ + PCNET_VLB = 3 /* 32-bit VLB */ +}; + + +extern const device_t pcnet_isa_device; +extern const device_t pcnet_pci_device; +extern const device_t pcnet_vlb_device; + +#endif /*NET_PCNET_H*/ diff --git a/src/network/network.c b/src/network/network.c index 6cff7945d..475a3674e 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -12,7 +12,7 @@ * it should be malloc'ed and then linked to the NETCARD def. * Will be done later. * - * Version: @(#)network.c 1.0.12 2019/11/14 + * Version: @(#)network.c 1.0.13 2019/12/02 * * Author: Fred N. van Kempen, * @@ -62,6 +62,7 @@ #include "network.h" #include "net_3c503.h" #include "net_ne2000.h" +#include "net_pcnet.h" #include "net_wd8003.h" @@ -70,6 +71,8 @@ static netcard_t net_cards[] = { NULL }, { "[ISA] 3Com EtherLink II (3C503)","3c503", &threec503_device, NULL }, + { "[ISA] AMD PCnet-ISA", "pcnetisa", &pcnet_isa_device, + NULL }, { "[ISA] Novell NE1000", "ne1k", &ne1000_device, NULL }, { "[ISA] Novell NE2000", "ne2k", &ne2000_device, @@ -88,8 +91,12 @@ static netcard_t net_cards[] = { NULL }, { "[MCA] Western Digital WD8003E/A", "wd8003ea", &wd8003ea_device, NULL }, + { "[PCI] AMD PCnet-PCI", "pcnetpci", &pcnet_pci_device, + NULL }, { "[PCI] Realtek RTL8029AS", "ne2kpci", &rtl8029as_device, NULL }, + { "[VLB] AMD PCnet-VL", "pcnetvlb", &pcnet_vlb_device, + NULL }, { "", "", NULL, NULL } }; diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index b90bdde28..e37ba29a6 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -607,7 +607,7 @@ NETOBJ := network.o \ sbuf.o tcp_output.o udp.o if.o mbuf.o slirp.o tcp_subr.o \ net_dp8390.o \ net_3c503.o net_ne2000.o \ - net_wd8003.o + net_pcnet.o net_wd8003.o PRINTOBJ := png.o prt_cpmap.o \ prt_escp.o prt_text.o prt_ps.o diff --git a/src/win/Makefile_ndr.mingw b/src/win/Makefile_ndr.mingw index 0a10a0c32..466bf472f 100644 --- a/src/win/Makefile_ndr.mingw +++ b/src/win/Makefile_ndr.mingw @@ -613,7 +613,7 @@ NETOBJ := network.o \ sbuf.o tcp_output.o udp.o if.o mbuf.o slirp.o tcp_subr.o \ net_dp8390.o \ net_3c503.o net_ne2000.o \ - net_wd8003.o + net_pcnet.o net_wd8003.o PRINTOBJ := png.o prt_cpmap.o \ prt_escp.o prt_text.o prt_ps.o