Files
86Box/src/network/net_pcnet.c
2022-08-27 19:08:28 +02:00

3283 lines
99 KiB
C

/*
* 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.
*
*
*
* Authors: Miran Grca, <mgrca8@gmail.com>
* TheCollector1995, <mariogplayer@gmail.com>
* Antony T Curtis
*
* Copyright 2004-2019 Antony T Curtis
* Copyright 2016-2019 Miran Grca.
*/
#ifdef _WIN32
#include <winsock.h>
#else
#include <arpa/inet.h>
#endif
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <wchar.h>
#include <time.h>
#include <stdbool.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/dma.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/pci.h>
#include <86box/pic.h>
#include <86box/random.h>
#include <86box/device.h>
#include <86box/isapnp.h>
#include <86box/timer.h>
#include <86box/thread.h>
#include <86box/network.h>
#include <86box/net_pcnet.h>
#include <86box/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 number of times we report a link down to the guest (failure to send frame) */
#define PCNET_MAX_LINKDOWN_REPORTED 3
/** Maximum frame size we handle */
#define MAX_FRAME 1536
/** @name Bus configuration registers
* @{ */
#define BCR_MSRDA 0
#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_SWCONFIG 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)
static const uint8_t am79c961_pnp_rom[] = {
0x04, 0x96, 0x55, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, /* ADV55AA, dummy checksum (filled in by isapnp_add_card) */
0x0a, 0x10, 0x00, /* PnP version 1.0, vendor version 0.0 */
0x82, 0x1c, 0x00, 'A', 'M', 'D', ' ', 'E', 't', 'h', 'e', 'r', 'n', 'e', 't', ' ', 'N', 'e', 't', 'w', 'o', 'r', 'k', ' ', 'A', 'd', 'a', 'p', 't', 'e', 'r', /* ANSI identifier */
0x16, 0x04, 0x96, 0x55, 0xaa, 0x00, 0xbd, /* logical device ADV55AA, supports vendor-specific registers 0x38/0x3A/0x3B/0x3C/0x3D/0x3F */
0x1c, 0x41, 0xd0, 0x82, 0x8c, /* compatible device PNP828C */
0x47, 0x00, 0x00, 0x02, 0xe0, 0x03, 0x20, 0x18, /* I/O 0x200-0x3E0, decodes 10-bit, 32-byte alignment, 24 addresses */
0x2a, 0xe8, 0x02, /* DMA 3/5/6/7, compatibility, no count by word, no count by byte, not bus master, 16-bit only */
0x23, 0x38, 0x9e, 0x09, /* IRQ 3/4/5/9/10/11/12/15, low true level sensitive, high true edge sensitive */
0x79, 0x00 /* end tag, dummy checksum (filled in by isapnp_add_card) */
};
typedef struct {
mem_mapping_t mmio_mapping;
const char *name;
int board;
int is_pci, is_vlb, is_isa;
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[256];
uint16_t aCSR[CSR_MAX_REG];
uint16_t aBCR[BCR_MAX_RAP];
uint16_t aMII[MII_MAX_REG];
/** 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;
/** We are waiting/about to start waiting for more receive buffers. */
int fMaybeOutOfSpace;
/** 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;
uint16_t u16CSR0LastSeenByGuest;
/** If set the link is currently up. */
int fLinkUp;
/** If set the link is temporarily down because of a saved state load. */
int fLinkTempDown;
/** Number of times we've reported the link down. */
uint32_t cLinkDownReported;
/** MS to wait before we enable the link. */
uint32_t cMsLinkUpDelay;
int transfer_size;
uint8_t maclocal[6]; /* configured MAC (local) address */
pc_timer_t timer, timer_soft_int, timer_restore;
netcard_t *netcard;
} 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 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 int 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<<dev->base_irq);
else
picintc(1<<dev->base_irq);
}
}
/**
* Checks if the link is up.
* @returns true if the link is up.
* @returns false if the link is down.
*/
static __inline int
pcnetIsLinkUp(nic_t *dev)
{
return !dev->fLinkTempDown && dev->fLinkUp;
}
/**
* 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, bytes[4] = { 0, 0, 0, 0 };
uint16_t xda[4];
uint32_t xda32[4];
if (BCR_SWSTYLE(dev) == 0) {
dma_bm_read(addr, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)&xda[0], sizeof(xda), dev->transfer_size);
((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) {
dma_bm_read(addr + 4, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)tmd, 16, dev->transfer_size);
} else {
dma_bm_read(addr + 4, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)&xda32[0], sizeof(xda32), dev->transfer_size);
((uint32_t *)tmd)[0] = xda32[2];
((uint32_t *)tmd)[1] = xda32[1];
((uint32_t *)tmd)[2] = xda32[0];
((uint32_t *)tmd)[3] = xda32[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)
{
uint16_t xda[4];
uint32_t xda32[3];
if (BCR_SWSTYLE(dev) == 0) {
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;
#if 0
xda[1] |= 0x8000;
dma_bm_write(addr, (uint8_t*)&xda[0], sizeof(xda), dev->transfer_size);
#endif
xda[1] &= ~0x8000;
dma_bm_write(addr, (uint8_t*)&xda[0], sizeof(xda), dev->transfer_size);
} else if (BCR_SWSTYLE(dev) != 3) {
#if 0
((uint32_t*)tmd)[1] |= 0x80000000;
dma_bm_write(addr, (uint8_t*)tmd, 12, dev->transfer_size);
#endif
((uint32_t*)tmd)[1] &= ~0x80000000;
dma_bm_write(addr, (uint8_t*)tmd, 12, dev->transfer_size);
} else {
xda32[0] = ((uint32_t *)tmd)[2];
xda32[1] = ((uint32_t *)tmd)[1];
xda32[2] = ((uint32_t *)tmd)[0];
#if 0
xda32[1] |= 0x80000000;
dma_bm_write(addr, (uint8_t*)&xda32[0], sizeof(xda32), dev->transfer_size);
#endif
xda32[1] &= ~0x80000000;
dma_bm_write(addr, (uint8_t*)&xda32[0], sizeof(xda32), dev->transfer_size);
}
}
/**
* 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, bytes[4] = { 0, 0, 0, 0 };
uint16_t rda[4];
uint32_t rda32[4];
if (BCR_SWSTYLE(dev) == 0) {
dma_bm_read(addr, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)&rda[0], sizeof(rda), dev->transfer_size);
((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) {
dma_bm_read(addr + 4, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)rmd, 16, dev->transfer_size);
} else {
dma_bm_read(addr + 4, (uint8_t *) bytes, 4, dev->transfer_size);
ownbyte = bytes[3];
if (!(ownbyte & 0x80) && fRetIfNotOwn)
return 0;
dma_bm_read(addr, (uint8_t*)&rda32[0], sizeof(rda32), dev->transfer_size);
((uint32_t *)rmd)[0] = rda32[2];
((uint32_t *)rmd)[1] = rda32[1];
((uint32_t *)rmd)[2] = rda32[0];
((uint32_t *)rmd)[3] = rda32[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)
{
uint16_t rda[4];
uint32_t rda32[3];
if (BCR_SWSTYLE(dev) == 0) {
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;
#if 0
rda[1] |= 0x8000;
dma_bm_write(addr, (uint8_t*)&rda[0], sizeof(rda), dev->transfer_size);
#endif
rda[1] &= ~0x8000;
dma_bm_write(addr, (uint8_t*)&rda[0], sizeof(rda), dev->transfer_size);
} else if (BCR_SWSTYLE(dev) != 3) {
#if 0
((uint32_t*)rmd)[1] |= 0x80000000;
dma_bm_write(addr, (uint8_t*)rmd, 12, dev->transfer_size);
#endif
((uint32_t*)rmd)[1] &= ~0x80000000;
dma_bm_write(addr, (uint8_t*)rmd, 12, dev->transfer_size);
} else {
rda32[0] = ((uint32_t *)rmd)[2];
rda32[1] = ((uint32_t *)rmd)[1];
rda32[2] = ((uint32_t *)rmd)[0];
#if 0
rda32[1] |= 0x80000000;
dma_bm_write(addr, (uint8_t*)&rda32[0], sizeof(rda32), dev->transfer_size);
#endif
rda32[1] &= ~0x80000000;
dma_bm_write(addr, (uint8_t*)&rda32[0], sizeof(rda32), dev->transfer_size);
}
}
/** 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;
switch (dev->board) {
case DEV_AM79C970A:
dev->aCSR[88] = 0x1003;
dev->aCSR[89] = 0x0262;
break;
case DEV_AM79C973:
dev->aCSR[88] = 0x5003;
dev->aCSR[89] = 0x0262;
break;
case DEV_AM79C960:
case DEV_AM79C960_EB:
case DEV_AM79C960_VLB:
case DEV_AM79C961:
dev->aCSR[88] = 0x3003;
dev->aCSR[89] = 0x0262;
break;
}
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)
{
int iISR = 0;
uint16_t csr0;
csr0 = dev->aCSR[0];
csr0 &= ~0x0080; /* clear INTR */
if (((csr0 & ~dev->aCSR[3]) & 0x5f00) ||
(((dev->aCSR[4]>>1) & ~dev->aCSR[4]) & 0x0115) ||
(((dev->aCSR[5]>>1) & dev->aCSR[5]) & 0x0048)) {
iISR = !!(csr0 & 0x0040); /* CSR_INEA */
csr0 |= 0x0080; /* set INTR */
}
if (dev->aCSR[4] & 0x0080) { /* UINTCMD */
dev->aCSR[4] &= ~0x0080; /* clear UINTCMD */
dev->aCSR[4] |= 0x0040; /* set UINT */
pcnetlog(2, "%s: user int\n", dev->name);
}
if (dev->aCSR[4] & csr0 & 0x0040 /* CSR_INEA */) {
csr0 |= 0x0080; /* set INTR */
iISR = 1;
}
if (((dev->aCSR[5]>>1) & dev->aCSR[5]) & 0x0500) {
iISR = 1;
csr0 |= 0x0080; /* set INTR */
}
if ((dev->aCSR[7] & 0x0c00) == 0x0c00) /* STINT + STINTE */
iISR = 1;
dev->aCSR[0] = csr0;
pcnetlog(2, "%s: pcnetUpdateIrq: iISR=%d\n", dev->name, iISR);
pcnet_do_irq(dev, iISR);
dev->iISR = iISR;
}
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 { \
dma_bm_read(PHYSADDR(dev, CSR_IADR(dev)), \
(uint8_t *)&initblk, sizeof(initblk), dev->transfer_size); \
dev->aCSR[15] = le16_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] = le16_to_cpu(initblk.ladrf1); \
dev->aCSR[ 9] = le16_to_cpu(initblk.ladrf2); \
dev->aCSR[10] = le16_to_cpu(initblk.ladrf3); \
dev->aCSR[11] = le16_to_cpu(initblk.ladrf4); \
dev->aCSR[12] = le16_to_cpu(initblk.padr1); \
dev->aCSR[13] = le16_to_cpu(initblk.padr2); \
dev->aCSR[14] = le16_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
size_t cbRxBuffers = 0;
for (i = CSR_RCVRL(dev); i >= 1; i--) {
RMD rmd;
uint32_t rdaddr = PHYSADDR(dev, pcnetRdraAddr(dev, i));
/* 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;
}
}
/*
* 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*1024);
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 */
timer_set_delay_u64(&dev->timer, 2000 * TIMER_USEC);
}
/**
* 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;
timer_disable(&dev->timer);
}
/**
* 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.
*/
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 */
} 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 int
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 0;
/* if too small buffer, then expand it */
if (size < 60) {
memcpy(buf1, buf, size);
memset(buf1 + size, 0, 60 - size);
buf = buf1;
size = 60;
}
/*
* Drop packets if the cable is not connected
*/
if (!pcnetIsLinkUp(dev))
return 0;
dev->fMaybeOutOfSpace = !pcnetCanReceive(dev);
if (dev->fMaybeOutOfSpace)
return 0;
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 unsigned 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;
size_t 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 = (int)size;
pcnetRmdLoad(dev, &rmd, PHYSADDR(dev, crda), 0);
/* if (!CSR_LAPPEN(dev)) */
rmd.rmd1.stp = 1;
size_t 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
*/
dma_bm_write(rbadr, src, cbBuf, dev->transfer_size);
/* 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 0;
}
/* 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 - (size_t)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. */
dma_bm_write(rbadr2, src, cbBuf, dev->transfer_size);
/* 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 0;
}
/* 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(1, "%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(1, "%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;
}
}
pcnetUpdateIrq(dev);
return 1;
}
/**
* Fails a TMD with a link down error.
*/
static void
pcnetXmitFailTMDLinkDown(nic_t *dev, TMD *pTmd)
{
/* make carrier error - hope this is correct. */
dev->cLinkDownReported++;
pTmd->tmd2.lcar = pTmd->tmd1.err = 1;
dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR */
}
/**
* Fails a TMD with a generic error.
*/
static void
pcnetXmitFailTMDGeneric(nic_t *dev, TMD *pTmd)
{
/* make carrier error - hope this is correct. */
pTmd->tmd2.lcar = pTmd->tmd1.err = 1;
dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR */
}
/**
* Actually try transmit frames.
*
* @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.
*/
unsigned cFlushIrq = 0;
int cMax = 32;
do {
TMD tmd;
if (!pcnetTdtePoll(dev, &tmd))
break;
/* Don't continue sending packets when the link is down. */
if ((!pcnetIsLinkUp(dev)
&& dev->cLinkDownReported > PCNET_MAX_LINKDOWN_REPORTED)
)
break;
pcnetlog(3, "%s: TMDLOAD %#010x\n", dev->name, PHYSADDR(dev, CSR_CXDA(dev)));
int fLoopback = CSR_LOOP(dev);
/*
* The typical case - a complete packet.
*/
if (tmd.tmd1.stp && tmd.tmd1.enp) {
const int cb = 4096 - tmd.tmd1.bcnt;
pcnetlog("%s: pcnetAsyncTransmit: stp&enp: cb=%d xmtrc=%#x\n", dev->name, cb, CSR_XMTRC(dev));
if ((pcnetIsLinkUp(dev) || fLoopback)) {
/* From the manual: ``A zero length buffer is acceptable as
* long as it is not the last buffer in a chain (STP = 0 and
* ENP = 1).'' That means that the first buffer might have a
* zero length if it is not the last one in the chain. */
if (cb <= MAX_FRAME) {
dev->xmit_pos = cb;
dma_bm_read(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb, dev->transfer_size);
if (fLoopback) {
if (HOST_IS_OWNER(CSR_CRST(dev)))
pcnetRdtePoll(dev);
pcnetReceiveNoSync(dev, dev->abLoopBuf, dev->xmit_pos);
} else {
pcnetlog(3, "%s: pcnetAsyncTransmit: transmit loopbuf stp and enp, xmit pos = %d\n", dev->name, dev->xmit_pos);
network_tx(dev->netcard, 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 {
pcnetXmitFailTMDGeneric(dev, &tmd);
}
} else {
pcnetXmitFailTMDLinkDown(dev, &tmd);
}
/* 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.
*/
unsigned cb = 4096 - tmd.tmd1.bcnt;
dev->xmit_pos = pcnetCalcPacketLen(dev, cb);
dma_bm_read(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf, cb, dev->transfer_size);
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 */
/* Don't allow the guest to clear TINT before reading it */
dev->u16CSR0LastSeenByGuest &= ~0x0200;
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;
dma_bm_read(PHYSADDR(dev, tmd.tmd0.tbadr), dev->abLoopBuf + off, cb, dev->transfer_size);
}
/*
* Done already?
*/
if (tmd.tmd1.enp) {
if (fLoopback) {
if (HOST_IS_OWNER(CSR_CRST(dev)))
pcnetRdtePoll(dev);
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->netcard, 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 */
dev->xmit_pos = -1;
if (!CSR_TOKINTD(dev) /* Transmit OK Interrupt Disable, no infl. on errors. */
|| (CSR_LTINTEN(dev) && tmd.tmd1.ltint)
|| tmd.tmd1.err) {
cFlushIrq++;
}
if (--cMax == 0)
break;
} while (CSR_TXON(dev)); /* transfer on */
if (cFlushIrq) {
dev->aCSR[0] |= 0x0200; /* set TINT */
/* Don't allow the guest to clear TINT before reading it */
dev->u16CSR0LastSeenByGuest &= ~0x0200;
pcnetUpdateIrq(dev);
}
}
/**
* 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)) || dev->fMaybeOutOfSpace)
pcnetRdtePoll(dev);
}
if (CSR_TDMD(dev) || (CSR_TXON(dev) && !CSR_DPOLL(dev)))
pcnetAsyncTransmit(dev);
}
static void
pcnetPollTimer(void *p)
{
nic_t *dev = (nic_t *)p;
timer_advance_u64(&dev->timer, 2000 * TIMER_USEC);
if (CSR_TDMD(dev))
pcnetAsyncTransmit(dev);
pcnetUpdateIrq(dev);
if (!CSR_STOP(dev) && !CSR_SPND(dev) && (!CSR_DPOLL(dev) || dev->fMaybeOutOfSpace))
pcnetPollRxTx(dev);
}
static void
pcnetHardReset(nic_t *dev)
{
pcnetlog(2, "%s: pcnetHardReset\n", dev->name);
dev->iISR = 0;
pcnet_do_irq(dev, 0);
/* Many of the BCR values would normally be read from the EEPROM. */
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;
dev->aBCR[BCR_FDC] = 0x0000;
dev->aBCR[BCR_BSBC] = 0x9001;
dev->aBCR[BCR_EECAS] = 0x0002;
dev->aBCR[BCR_STVAL] = 0xffff;
dev->aCSR[58] = dev->aBCR[BCR_SWS] = 0x0200; /* CSR58 is an alias for BCR20 */
dev->iLog2DescSize = 3;
dev->aBCR[BCR_PLAT] = 0xff06;
dev->aBCR[BCR_MIICAS] = 0x20; /* Auto-negotiation on. */
dev->aBCR[BCR_MIIADDR] = 0; /* Internal PHY on Am79C973 would be (0x1e << 5) */
dev->aBCR[BCR_PCIVID] = 0x1022;
dev->aBCR[BCR_PCISID] = 0x0020;
dev->aBCR[BCR_PCISVID] = 0x1022;
/* Reset the error counter. */
dev->uCntBadRMD = 0;
pcnetSoftReset(dev);
}
static void
pcnet_csr_writew(nic_t *dev, uint16_t rap, uint16_t val)
{
pcnetlog(1, "%s: pcnet_csr_writew: rap=%d val=%#06x\n", dev->name, rap, val);
switch (rap) {
case 0:
{
uint16_t csr0 = dev->aCSR[0];
/* Clear any interrupt flags.
* Don't clear an interrupt flag which was not seen by the guest yet. */
csr0 &= ~(val & 0x7f00 & dev->u16CSR0LastSeenByGuest);
csr0 = (csr0 & ~0x0040) | (val & 0x0048);
val = (val & 0x007f) | (csr0 & 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]);
dev->aCSR[0] = csr0;
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->is_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 7: /* Extended Control and Interrupt 2 */
{
uint16_t csr7 = dev->aCSR[7];
csr7 &= ~0x0400;
csr7 &= ~(val & 0x0800);
csr7 |= (val & 0x0400);
dev->aCSR[7] = csr7;
}
return;
case 15: /* Mode */
break;
case 16: /* IADRL */
pcnet_csr_writew(dev,1,val);
return;
case 17: /* IADRH */
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;
dev->u16CSR0LastSeenByGuest = val;
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);
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;
}
dev->aCSR[58] = val;
/* 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:
case BCR_MIIADDR:
dev->aBCR[rap] = val;
break;
case BCR_MIICAS:
dev->netcard->byte_period = (dev->board == DEV_AM79C973 && (val & 0x28)) ? NET_PERIOD_100M : NET_PERIOD_10M;
dev->aBCR[rap] = val;
break;
case BCR_STVAL:
val &= 0xffff;
dev->aBCR[BCR_STVAL] = val;
if (dev->board == DEV_AM79C973)
timer_set_delay_u64(&dev->timer_soft_int, (12.8 * val) * TIMER_USEC);
break;
case BCR_MIIMDR:
dev->aMII[dev->aBCR[BCR_MIIADDR] & 0x1f] = val;
break;
default:
break;
}
}
static uint16_t
pcnet_mii_readw(nic_t *dev, uint16_t miiaddr)
{
uint16_t val;
int autoneg, duplex, fast, isolate;
/* If the DANAS (BCR32.7) bit is set, the MAC does not do any
* auto-negotiation and the PHY must be set up explicitly. DANAS
* effectively disables most other BCR32 bits.
*/
if (dev->aBCR[BCR_MIICAS] & 0x80) {
/* PHY controls auto-negotiation. */
autoneg = duplex = fast = 1;
} else {
/* BCR32 controls auto-negotiation. */
autoneg = (dev->aBCR[BCR_MIICAS] & 0x20) != 0;
duplex = (dev->aBCR[BCR_MIICAS] & 0x10) != 0;
fast = (dev->aBCR[BCR_MIICAS] & 0x08) != 0;
}
/* Electrically isolating the PHY mostly disables it. */
isolate = (dev->aMII[0] & 0x400) != 0;
switch (miiaddr) {
case 0:
/* MII basic mode control register. */
val = 0;
if (autoneg)
val |= 0x1000; /* Enable auto negotiation. */
if (fast)
val |= 0x2000; /* 100 Mbps */
if (duplex) /* Full duplex forced */
val |= 0x0100; /* Full duplex */
if (isolate) /* PHY electrically isolated. */
val |= 0x0400; /* Isolated */
break;
case 1:
/* MII basic mode status register. */
val = 0x7800 /* Can do 100mbps FD/HD and 10mbps FD/HD. */
| 0x0040 /* Mgmt frame preamble not required. */
| 0x0020 /* Auto-negotiation complete. */
| 0x0008 /* Able to do auto-negotiation. */
| 0x0004 /* Link up. */
| 0x0001; /* Extended Capability, i.e. registers 4+ valid. */
if (!pcnetIsLinkUp(dev) || isolate) {
val &= ~(0x0020 | 0x0004);
dev->cLinkDownReported++;
}
if (!autoneg) {
/* Auto-negotiation disabled. */
val &= ~(0x0020 | 0x0008);
if (duplex)
val &= ~0x2800; /* Full duplex forced. */
else
val &= ~0x5000; /* Half duplex forced. */
if (fast)
val &= ~0x1800; /* 100 Mbps forced */
else
val &= ~0x6000; /* 10 Mbps forced */
}
break;
case 2:
/* PHY identifier 1. */
val = 0x22; /* Am79C874/AC101 PHY */
break;
case 3:
/* PHY identifier 2. */
val = 0x561b; /* Am79C874/AC101 PHY */
break;
case 4:
/* Advertisement control register. */
val = 0x01e0 /* Try 100mbps FD/HD and 10mbps FD/HD. */
| 0x0001; /* CSMA selector. */
break;
case 5:
/* Link partner ability register. */
if (pcnetIsLinkUp(dev) && !isolate) {
val = 0x8000 /* Next page bit. */
| 0x4000 /* Link partner acked us. */
| 0x0400 /* Can do flow control. */
| 0x01e0 /* Can do 100mbps FD/HD and 10mbps FD/HD. */
| 0x0001; /* Use CSMA selector. */
} else {
val = 0;
dev->cLinkDownReported++;
}
break;
case 6:
/* Auto negotiation expansion register. */
if (pcnetIsLinkUp(dev) && !isolate) {
val = 0x0008 /* Link partner supports npage. */
| 0x0004 /* Enable npage words. */
| 0x0001; /* Can do N-way auto-negotiation. */
} else {
val = 0;
dev->cLinkDownReported++;
}
break;
case 18:
/* Diagnostic Register (FreeBSD pcn/ac101 driver reads this). */
if (pcnetIsLinkUp(dev) && !isolate) {
val = 0x1000 /* Receive PLL locked. */
| 0x0200; /* Signal detected. */
if (autoneg) {
val |= 0x0400 /* 100Mbps rate. */
| 0x0800; /* Full duplex. */
} else {
if (fast)
val |= 0x0400; /* 100Mbps rate. */
if (duplex)
val |= 0x0800; /* Full duplex. */
}
} else {
val = 0;
dev->cLinkDownReported++;
}
break;
default:
val = 0;
break;
}
return val;
}
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;
if (!(pcnetIsLinkUp(dev))) {
if (rap == 4)
dev->cLinkDownReported++;
val &= ~0x40;
}
val |= (val & 0x017f & dev->u32Lnkst) ? 0x8000 : 0;
break;
case BCR_MIIMDR:
if ((dev->board == DEV_AM79C973) && (((dev->aBCR[BCR_MIIADDR] >> 5) & 0x1f) == 0)) {
uint16_t miiaddr = dev->aBCR[BCR_MIIADDR] & 0x1f;
val = pcnet_mii_readw(dev, miiaddr);
} else
val = 0xffff;
break;
case BCR_SWCONFIG:
if (dev->board == DEV_AM79C961)
val = ((dev->base_irq & 0x0f) << 4) | (dev->dma_channel & 0x07);
else
val = 0xffff;
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 */
timer_set_delay_u64(&dev->timer, 2000 * TIMER_USEC);
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 uint8_t
pcnet_byte_read(nic_t *dev, uint32_t addr)
{
uint8_t val = 0xff;
if (!BCR_DWIO(dev)) {
switch (addr & 0x0f) {
case 0x04:
pcnetSoftReset(dev);
val = 0;
break;
}
}
pcnetUpdateIrq(dev);
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 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))
timer_set_delay_u64(&dev->timer, 2000 * TIMER_USEC);
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 */
timer_set_delay_u64(&dev->timer, 2000 * TIMER_USEC);
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))
timer_set_delay_u64(&dev->timer, 2000 * TIMER_USEC);
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 == 1)
retval = pcnet_byte_read(dev, addr);
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 (dev->board == DEV_AM79C973) ? 0x40 : 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 0x2C:
return 0x22;
case 0x2D:
return 0x10;
case 0x2E:
return 0x00;
case 0x2F:
return 0x20;
case 0x3C:
return dev->base_irq;
case 0x3D:
return PCI_INTA;
case 0x3E:
return 0x06;
case 0x3F:
return 0xff;
}
return(0);
}
static void
pcnet_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv)
{
if (ld)
return;
nic_t *dev = (nic_t *) priv;
dev->base_address = 0;
dev->base_irq = 0;
dev->dma_channel = -1;
if (dev->base_address) {
pcnet_ioremove(dev, dev->base_address, 0x20);
dev->base_address = 0;
}
if (config->activate) {
dev->base_address = config->io[0].base;
if (dev->base_address != ISAPNP_IO_DISABLED)
pcnet_ioset(dev, dev->base_address, 0x20);
dev->base_irq = config->irq[0].irq;
dev->dma_channel = config->dma[0].dma;
if (dev->dma_channel == ISAPNP_DMA_DISABLED)
dev->dma_channel = -1;
/* Update PnP register mirrors in ROM. */
dev->aPROM[32] = dev->base_address >> 8;
dev->aPROM[33] = dev->base_address;
dev->aPROM[34] = dev->base_irq;
dev->aPROM[35] = (config->irq[0].level << 1) | config->irq[0].type;
dev->aPROM[36] = (dev->dma_channel == -1) ? ISAPNP_DMA_DISABLED : dev->dma_channel;
}
}
static uint8_t
pcnet_pnp_read_vendor_reg(uint8_t ld, uint8_t reg, void *priv)
{
nic_t *dev = (nic_t *) priv;
if (!ld && (reg == 0xf0))
return dev->aPROM[50];
else
return 0x00;
}
static void
pcnet_pnp_write_vendor_reg(uint8_t ld, uint8_t reg, uint8_t val, void *priv)
{
if (ld)
return;
nic_t *dev = (nic_t *) priv;
if (reg == 0xf0)
dev->aPROM[50] = val & 0x1f;
}
/**
* Takes down the link temporarily if it's current status is up.
*
* This is used during restore and when replumbing the network link.
*
* The temporary link outage is supposed to indicate to the OS that all network
* connections have been lost and that it for instance is appropriate to
* renegotiate any DHCP lease.
*
* @param pThis The PCnet shared instance data.
*/
static void
pcnetTempLinkDown(nic_t *dev)
{
if (dev->fLinkUp) {
dev->fLinkTempDown = 1;
dev->cLinkDownReported = 0;
dev->aCSR[0] |= 0x8000 | 0x2000; /* ERR | CERR (this is probably wrong) */
timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC);
}
}
/**
* Check if the device/driver can receive data now.
*
* 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 int
pcnetCanReceive(nic_t *dev)
{
int rc = 0;
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 */
} else
rc = 1;
}
return rc;
}
static int
pcnetSetLinkState(void *priv, uint32_t link_state)
{
nic_t *dev = (nic_t *) priv;
if (link_state & NET_LINK_TEMP_DOWN) {
pcnetTempLinkDown(dev);
return 1;
}
bool link_up = !(link_state & NET_LINK_DOWN);
if (dev->fLinkUp != link_up) {
dev->fLinkUp = link_up;
if (link_up) {
dev->fLinkTempDown = 1;
dev->cLinkDownReported = 0;
dev->aCSR[0] |= 0x8000 | 0x2000;
timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC);
} else {
dev->cLinkDownReported = 0;
dev->aCSR[0] |= 0x8000 | 0x2000;
}
}
return 0;
}
static void
pcnetTimerSoftInt(void *priv)
{
nic_t *dev = (nic_t *) priv;
dev->aCSR[7] |= 0x0800; /* STINT */
pcnetUpdateIrq(dev);
timer_advance_u64(&dev->timer_soft_int, (12.8 * (dev->aBCR[BCR_STVAL] & 0xffff)) * TIMER_USEC);
}
static void
pcnetTimerRestore(void *priv)
{
nic_t *dev = (nic_t *) priv;
if (dev->cLinkDownReported <= PCNET_MAX_LINKDOWN_REPORTED) {
timer_advance_u64(&dev->timer_restore, 1500000 * TIMER_USEC);
} else {
dev->fLinkTempDown = 0;
if (dev->fLinkUp) {
dev->aCSR[0] &= ~(0x8000 | 0x2000); /* ERR | CERR - probably not 100% correct either... */
}
}
}
static void *
pcnet_init(const device_t *info)
{
uint32_t mac;
nic_t *dev;
int c;
uint16_t checksum;
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);
dev->is_isa = !!(info->flags & (DEVICE_ISA | DEVICE_AT));
if (dev->is_pci || dev->is_vlb)
dev->transfer_size = 4;
else
dev->transfer_size = 2;
if (dev->is_pci) {
pcnet_mem_init(dev, 0x0fffff00);
pcnet_mem_disable(dev);
}
dev->fLinkUp = 1;
dev->cMsLinkUpDelay = 5000;
if (dev->board == DEV_AM79C960_EB) {
dev->maclocal[0] = 0x02; /* 02:07:01 (Racal OID) */
dev->maclocal[1] = 0x07;
dev->maclocal[2] = 0x01;
} else {
dev->maclocal[0] = 0x00; /* 00:0C:87 (AMD OID) */
dev->maclocal[1] = 0x0C;
dev->maclocal[2] = 0x87;
}
/* See if we have a local MAC address configured. */
mac = device_get_config_mac("mac", -1);
/* 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 */
/* 0x00/0xFF=ISA, 0x01=PnP, 0x10=VLB, 0x11=PCI */
if (dev->is_pci)
dev->aPROM[9] = 0x11;
else if (dev->is_vlb)
dev->aPROM[9] = 0x10;
else if (dev->board == DEV_AM79C961)
dev->aPROM[9] = 0x01;
else
dev->aPROM[9] = 0x00;
/* User programmable space, init with 0 */
dev->aPROM[10] = dev->aPROM[11] = 0x00;
if (dev->board == DEV_AM79C960_EB) {
dev->aPROM[14] = 0x52;
dev->aPROM[15] = 0x44; /* NI6510 EtherBlaster 'RD' signature. */
} else {
/* 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 if (dev->board == DEV_AM79C961) {
dev->dma_channel = -1;
/* Weird secondary checksum. The datasheet isn't clear on what
role it might play with the PnP register mirrors before it. */
for (c = 0, checksum = 0; c < 54; c++)
checksum += dev->aPROM[c];
dev->aPROM[51] = checksum;
memcpy(&dev->aPROM[0x40], am79c961_pnp_rom, sizeof(am79c961_pnp_rom));
isapnp_add_card(&dev->aPROM[0x40], sizeof(am79c961_pnp_rom), pcnet_pnp_config_changed, NULL, pcnet_pnp_read_vendor_reg, pcnet_pnp_write_vendor_reg, 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 = -1;
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":"VLB/ISA", dev->base_address, dev->base_irq);
/* Reset the board. */
pcnetHardReset(dev);
/* Attach ourselves to the network module. */
dev->netcard = network_attach(dev, dev->aPROM, pcnetReceiveNoSync, pcnetSetLinkState);
dev->netcard->byte_period = (dev->board == DEV_AM79C973) ? NET_PERIOD_100M : NET_PERIOD_10M;
timer_add(&dev->timer, pcnetPollTimer, dev, 0);
if (dev->board == DEV_AM79C973)
timer_add(&dev->timer_soft_int, pcnetTimerSoftInt, dev, 0);
timer_add(&dev->timer_restore, pcnetTimerRestore, dev, 0);
return(dev);
}
static void
pcnet_close(void *priv)
{
nic_t *dev = (nic_t *)priv;
pcnetlog(1, "%s: closed\n", dev->name);
netcard_close(dev->netcard);
if (dev) {
free(dev);
dev = NULL;
}
}
// clang-format off
static const device_config_t pcnet_pci_config[] = {
{
.name = "mac",
.description = "MAC Address",
.type = CONFIG_MAC,
.default_string = "",
.default_int = -1
},
{ .name = "", .description = "", .type = CONFIG_END }
};
static const device_config_t pcnet_isa_config[] = {
{
.name = "base",
.description = "Address",
.type = CONFIG_HEX16,
.default_string = "",
.default_int = 0x300,
.file_filter = "",
.spinner = { 0 },
.selection = {
{ .description = "0x300", .value = 0x300 },
{ .description = "0x320", .value = 0x320 },
{ .description = "0x340", .value = 0x340 },
{ .description = "0x360", .value = 0x360 },
{ .description = "" }
},
},
{
.name = "irq",
.description = "IRQ",
.type = CONFIG_SELECTION,
.default_string = "",
.default_int = 3,
.file_filter = "",
.spinner = { 0 },
.selection = {
{ .description = "IRQ 3", .value = 3 },
{ .description = "IRQ 4", .value = 4 },
{ .description = "IRQ 5", .value = 5 },
{ .description = "IRQ 9", .value = 9 },
{ .description = "" }
},
},
{
.name = "dma",
.description = "DMA channel",
.type = CONFIG_SELECTION,
.default_string = "",
.default_int = 5,
.file_filter = "",
.spinner = { 0 },
.selection = {
{ .description = "DMA 3", .value = 3 },
{ .description = "DMA 5", .value = 5 },
{ .description = "DMA 6", .value = 6 },
{ .description = "DMA 7", .value = 7 },
{ .description = "" }
},
},
{
.name = "mac",
.description = "MAC Address",
.type = CONFIG_MAC,
.default_string = "",
.default_int = -1
},
{ .name = "", .description = "", .type = CONFIG_END }
};
static const device_config_t pcnet_vlb_config[] = {
{
.name = "base",
.description = "Address",
.type = CONFIG_HEX16,
.default_string = "",
.default_int = 0x300,
.file_filter = "",
.spinner = { 0 },
.selection = {
{ .description = "0x300", .value = 0x300 },
{ .description = "0x320", .value = 0x320 },
{ .description = "0x340", .value = 0x340 },
{ .description = "0x360", .value = 0x360 },
{ .description = "" }
},
},
{
.name = "irq",
.description = "IRQ",
.type = CONFIG_SELECTION,
.default_string = "",
.default_int = 3,
.file_filter = "",
.spinner = { 0 },
.selection = {
{ .description = "IRQ 3", .value = 3 },
{ .description = "IRQ 4", .value = 4 },
{ .description = "IRQ 5", .value = 5 },
{ .description = "IRQ 9", .value = 9 },
{ .description = "" }
},
},
{
.name = "mac",
.description = "MAC Address",
.type = CONFIG_MAC,
.default_string = "",
.default_int = -1
},
{ .name = "", .description = "", .type = CONFIG_END }
};
// clang-format on
const device_t pcnet_am79c960_device = {
.name = "AMD PCnet-ISA",
.internal_name = "pcnetisa",
.flags = DEVICE_AT | DEVICE_ISA,
.local = DEV_AM79C960,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_isa_config
};
const device_t pcnet_am79c960_eb_device = {
.name = "Racal Interlan EtherBlaster",
.internal_name = "pcnetracal",
.flags = DEVICE_AT | DEVICE_ISA,
.local = DEV_AM79C960_EB,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_isa_config
};
const device_t pcnet_am79c960_vlb_device = {
.name = "AMD PCnet-VL",
.internal_name = "pcnetvlb",
.flags = DEVICE_VLB,
.local = DEV_AM79C960_VLB,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_vlb_config
};
const device_t pcnet_am79c961_device = {
.name = "AMD PCnet-ISA+",
.internal_name = "pcnetisaplus",
.flags = DEVICE_AT | DEVICE_ISA,
.local = DEV_AM79C961,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_pci_config
};
const device_t pcnet_am79c970a_device = {
.name = "AMD PCnet-PCI II",
.internal_name = "pcnetpci",
.flags = DEVICE_PCI,
.local = DEV_AM79C970A,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_pci_config
};
const device_t pcnet_am79c973_device = {
.name = "AMD PCnet-FAST III",
.internal_name = "pcnetfast",
.flags = DEVICE_PCI,
.local = DEV_AM79C973,
.init = pcnet_init,
.close = pcnet_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = pcnet_pci_config
};