summaryrefslogtreecommitdiff
path: root/sys/src/9/pc/etherelnk3.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/pc/etherelnk3.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/pc/etherelnk3.c')
-rwxr-xr-xsys/src/9/pc/etherelnk3.c2134
1 files changed, 2134 insertions, 0 deletions
diff --git a/sys/src/9/pc/etherelnk3.c b/sys/src/9/pc/etherelnk3.c
new file mode 100755
index 000000000..9c612b136
--- /dev/null
+++ b/sys/src/9/pc/etherelnk3.c
@@ -0,0 +1,2134 @@
+/*
+ * Etherlink III, Fast EtherLink and Fast EtherLink XL adapters.
+ * To do:
+ * check robustness in the face of errors (e.g. busmaster & rxUnderrun);
+ * RxEarly and busmaster;
+ * autoSelect;
+ * PCI latency timer and master enable;
+ * errata list;
+ * rewrite all initialisation.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+
+#define XCVRDEBUG if(0)print
+
+enum {
+ IDport = 0x0110, /* anywhere between 0x0100 and 0x01F0 */
+};
+
+enum { /* all windows */
+ CommandR = 0x000E,
+ IntStatusR = 0x000E,
+};
+
+enum { /* Commands */
+ GlobalReset = 0x0000,
+ SelectRegisterWindow = 0x0001,
+ EnableDcConverter = 0x0002,
+ RxDisable = 0x0003,
+ RxEnable = 0x0004,
+ RxReset = 0x0005,
+ Stall = 0x0006, /* 3C90x */
+ TxDone = 0x0007,
+ RxDiscard = 0x0008,
+ TxEnable = 0x0009,
+ TxDisable = 0x000A,
+ TxReset = 0x000B,
+ RequestInterrupt = 0x000C,
+ AcknowledgeInterrupt = 0x000D,
+ SetInterruptEnable = 0x000E,
+ SetIndicationEnable = 0x000F, /* SetReadZeroMask */
+ SetRxFilter = 0x0010,
+ SetRxEarlyThresh = 0x0011,
+ SetTxAvailableThresh = 0x0012,
+ SetTxStartThresh = 0x0013,
+ StartDma = 0x0014, /* initiate busmaster operation */
+ StatisticsEnable = 0x0015,
+ StatisticsDisable = 0x0016,
+ DisableDcConverter = 0x0017,
+ SetTxReclaimThresh = 0x0018, /* PIO-only adapters */
+ PowerUp = 0x001B, /* not all adapters */
+ PowerDownFull = 0x001C, /* not all adapters */
+ PowerAuto = 0x001D, /* not all adapters */
+};
+
+enum { /* (Global|Rx|Tx)Reset command bits */
+ tpAuiReset = 0x0001, /* 10BaseT and AUI transceivers */
+ endecReset = 0x0002, /* internal Ethernet encoder/decoder */
+ networkReset = 0x0004, /* network interface logic */
+ fifoReset = 0x0008, /* FIFO control logic */
+ aismReset = 0x0010, /* autoinitialise state-machine logic */
+ hostReset = 0x0020, /* bus interface logic */
+ dmaReset = 0x0040, /* bus master logic */
+ vcoReset = 0x0080, /* on-board 10Mbps VCO */
+ updnReset = 0x0100, /* upload/download (Rx/TX) logic */
+
+ resetMask = 0x01FF,
+};
+
+enum { /* Stall command bits */
+ upStall = 0x0000,
+ upUnStall = 0x0001,
+ dnStall = 0x0002,
+ dnUnStall = 0x0003,
+};
+
+enum { /* SetRxFilter command bits */
+ receiveIndividual = 0x0001, /* match station address */
+ receiveMulticast = 0x0002,
+ receiveBroadcast = 0x0004,
+ receiveAllFrames = 0x0008, /* promiscuous */
+};
+
+enum { /* StartDma command bits */
+ Upload = 0x0000, /* transfer data from adapter to memory */
+ Download = 0x0001, /* transfer data from memory to adapter */
+};
+
+enum { /* IntStatus bits */
+ interruptLatch = 0x0001,
+ hostError = 0x0002, /* Adapter Failure */
+ txComplete = 0x0004,
+ txAvailable = 0x0008,
+ rxComplete = 0x0010,
+ rxEarly = 0x0020,
+ intRequested = 0x0040,
+ updateStats = 0x0080,
+ transferInt = 0x0100, /* Bus Master Transfer Complete */
+ dnComplete = 0x0200,
+ upComplete = 0x0400,
+ busMasterInProgress = 0x0800,
+ commandInProgress = 0x1000,
+
+ interruptMask = 0x07FE,
+};
+
+#define COMMAND(port, cmd, a) outs((port)+CommandR, ((cmd)<<11)|(a))
+#define STATUS(port) ins((port)+IntStatusR)
+
+enum { /* Window 0 - setup */
+ Wsetup = 0x0000,
+ /* registers */
+ ManufacturerID = 0x0000, /* 3C5[08]*, 3C59[27] */
+ ProductID = 0x0002, /* 3C5[08]*, 3C59[27] */
+ ConfigControl = 0x0004, /* 3C5[08]*, 3C59[27] */
+ AddressConfig = 0x0006, /* 3C5[08]*, 3C59[27] */
+ ResourceConfig = 0x0008, /* 3C5[08]*, 3C59[27] */
+ EepromCommand = 0x000A,
+ EepromData = 0x000C,
+ /* AddressConfig Bits */
+ autoSelect9 = 0x0080,
+ xcvrMask9 = 0xC000,
+ /* ConfigControl bits */
+ Ena = 0x0001,
+ base10TAvailable9 = 0x0200,
+ coaxAvailable9 = 0x1000,
+ auiAvailable9 = 0x2000,
+ /* EepromCommand bits */
+ EepromReadRegister = 0x0080,
+ EepromReadOffRegister = 0x00B0,
+ EepromRead8bRegister = 0x0230,
+ EepromBusy = 0x8000,
+};
+
+#define EEPROMCMD(port, cmd, a) outs((port)+EepromCommand, (cmd)|(a))
+#define EEPROMBUSY(port) (ins((port)+EepromCommand) & EepromBusy)
+#define EEPROMDATA(port) ins((port)+EepromData)
+
+enum { /* Window 1 - operating set */
+ Wop = 0x0001,
+ /* registers */
+ Fifo = 0x0000,
+ RxError = 0x0004, /* 3C59[0257] only */
+ RxStatus = 0x0008,
+ TIMER = 0x000A,
+ TxStatus = 0x000B,
+ TxFree = 0x000C,
+ /* RxError bits */
+ rxOverrun = 0x0001,
+ runtFrame = 0x0002,
+ alignmentError = 0x0004, /* Framing */
+ crcError = 0x0008,
+ oversizedFrame = 0x0010,
+ dribbleBits = 0x0080,
+ /* RxStatus bits */
+ rxBytes = 0x1FFF, /* 3C59[0257] mask */
+ rxBytes9 = 0x07FF, /* 3C5[078]9 mask */
+ rxError9 = 0x3800, /* 3C5[078]9 error mask */
+ rxOverrun9 = 0x0000,
+ oversizedFrame9 = 0x0800,
+ dribbleBits9 = 0x1000,
+ runtFrame9 = 0x1800,
+ alignmentError9 = 0x2000, /* Framing */
+ crcError9 = 0x2800,
+ rxError = 0x4000,
+ rxIncomplete = 0x8000,
+ /* TxStatus Bits */
+ txStatusOverflow = 0x0004,
+ maxCollisions = 0x0008,
+ txUnderrun = 0x0010,
+ txJabber = 0x0020,
+ interruptRequested = 0x0040,
+ txStatusComplete = 0x0080,
+};
+
+enum { /* Window 2 - station address */
+ Wstation = 0x0002,
+
+ ResetOp905B = 0x000C,
+};
+
+enum { /* Window 3 - FIFO management */
+ Wfifo = 0x0003,
+ /* registers */
+ InternalConfig = 0x0000, /* 3C509B, 3C589, 3C59[0257] */
+ OtherInt = 0x0004, /* 3C59[0257] */
+ RomControl = 0x0006, /* 3C509B, 3C59[27] */
+ MacControl = 0x0006, /* 3C59[0257] */
+ ResetOptions = 0x0008, /* 3C59[0257] */
+ MediaOptions = 0x0008, /* 3C905B */
+ RxFree = 0x000A,
+ /* InternalConfig bits */
+ disableBadSsdDetect = 0x00000100,
+ ramLocation = 0x00000200, /* 0 external, 1 internal */
+ ramPartition5to3 = 0x00000000,
+ ramPartition3to1 = 0x00010000,
+ ramPartition1to1 = 0x00020000,
+ ramPartition3to5 = 0x00030000,
+ ramPartitionMask = 0x00030000,
+ xcvr10BaseT = 0x00000000,
+ xcvrAui = 0x00100000, /* 10BASE5 */
+ xcvr10Base2 = 0x00300000,
+ xcvr100BaseTX = 0x00400000,
+ xcvr100BaseFX = 0x00500000,
+ xcvrMii = 0x00600000,
+ xcvrMask = 0x00700000,
+ autoSelect = 0x01000000,
+ /* MacControl bits */
+ deferExtendEnable = 0x0001,
+ deferTIMERSelect = 0x001E, /* mask */
+ fullDuplexEnable = 0x0020,
+ allowLargePackets = 0x0040,
+ extendAfterCollision = 0x0080, /* 3C90xB */
+ flowControlEnable = 0x0100, /* 3C90xB */
+ vltEnable = 0x0200, /* 3C90xB */
+ /* ResetOptions bits */
+ baseT4Available = 0x0001,
+ baseTXAvailable = 0x0002,
+ baseFXAvailable = 0x0004,
+ base10TAvailable = 0x0008,
+ coaxAvailable = 0x0010,
+ auiAvailable = 0x0020,
+ miiConnector = 0x0040,
+};
+
+enum { /* Window 4 - diagnostic */
+ Wdiagnostic = 0x0004,
+ /* registers */
+ VcoDiagnostic = 0x0002,
+ FifoDiagnostic = 0x0004,
+ NetworkDiagnostic = 0x0006,
+ PhysicalMgmt = 0x0008,
+ MediaStatus = 0x000A,
+ BadSSD = 0x000C,
+ UpperBytesOk = 0x000D,
+ /* FifoDiagnostic bits */
+ txOverrun = 0x0400,
+ rxUnderrun = 0x2000,
+ receiving = 0x8000,
+ /* PhysicalMgmt bits */
+ mgmtClk = 0x0001,
+ mgmtData = 0x0002,
+ mgmtDir = 0x0004,
+ cat5LinkTestDefeat = 0x8000,
+ /* MediaStatus bits */
+ dataRate100 = 0x0002,
+ crcStripDisable = 0x0004,
+ enableSqeStats = 0x0008,
+ collisionDetect = 0x0010,
+ carrierSense = 0x0020,
+ jabberGuardEnable = 0x0040,
+ linkBeatEnable = 0x0080,
+ jabberDetect = 0x0200,
+ polarityReversed = 0x0400,
+ linkBeatDetect = 0x0800,
+ txInProg = 0x1000,
+ dcConverterEnabled = 0x4000,
+ auiDisable = 0x8000, /* 10BaseT transceiver selected */
+};
+
+enum { /* Window 5 - internal state */
+ Wstate = 0x0005,
+ /* registers */
+ TxStartThresh = 0x0000,
+ TxAvailableThresh = 0x0002,
+ RxEarlyThresh = 0x0006,
+ RxFilter = 0x0008,
+ InterruptEnable = 0x000A,
+ IndicationEnable = 0x000C,
+};
+
+enum { /* Window 6 - statistics */
+ Wstatistics = 0x0006,
+ /* registers */
+ CarrierLost = 0x0000,
+ SqeErrors = 0x0001,
+ MultipleColls = 0x0002,
+ SingleCollFrames = 0x0003,
+ LateCollisions = 0x0004,
+ RxOverruns = 0x0005,
+ FramesXmittedOk = 0x0006,
+ FramesRcvdOk = 0x0007,
+ FramesDeferred = 0x0008,
+ UpperFramesOk = 0x0009,
+ BytesRcvdOk = 0x000A,
+ BytesXmittedOk = 0x000C,
+};
+
+enum { /* Window 7 - bus master operations */
+ Wmaster = 0x0007,
+ /* registers */
+ MasterAddress = 0x0000,
+ MasterLen = 0x0006,
+ MasterStatus = 0x000C,
+ /* MasterStatus bits */
+ masterAbort = 0x0001,
+ targetAbort = 0x0002,
+ targetRetry = 0x0004,
+ targetDisc = 0x0008,
+ masterDownload = 0x1000,
+ masterUpload = 0x4000,
+ masterInProgress = 0x8000,
+
+ masterMask = 0xD00F,
+};
+
+enum { /* 3C90x extended register set */
+ TIMER905 = 0x001A, /* 8-bits */
+ TxStatus905 = 0x001B, /* 8-bits */
+ PktStatus = 0x0020, /* 32-bits */
+ DnListPtr = 0x0024, /* 32-bits, 8-byte aligned */
+ FragAddr = 0x0028, /* 32-bits */
+ FragLen = 0x002C, /* 16-bits */
+ ListOffset = 0x002E, /* 8-bits */
+ TxFreeThresh = 0x002F, /* 8-bits */
+ UpPktStatus = 0x0030, /* 32-bits */
+ FreeTIMER = 0x0034, /* 16-bits */
+ UpListPtr = 0x0038, /* 32-bits, 8-byte aligned */
+
+ /* PktStatus bits */
+ fragLast = 0x00000001,
+ dnCmplReq = 0x00000002,
+ dnStalled = 0x00000004,
+ upCompleteX = 0x00000008,
+ dnCompleteX = 0x00000010,
+ upRxEarlyEnable = 0x00000020,
+ armCountdown = 0x00000040,
+ dnInProg = 0x00000080,
+ counterSpeed = 0x00000010, /* 0 3.2uS, 1 320nS */
+ countdownMode = 0x00000020,
+ /* UpPktStatus bits (dpd->control) */
+ upPktLenMask = 0x00001FFF,
+ upStalled = 0x00002000,
+ upError = 0x00004000,
+ upPktComplete = 0x00008000,
+ upOverrun = 0x00010000, /* RxError<<16 */
+ upRuntFrame = 0x00020000,
+ upAlignmentError = 0x00040000,
+ upCRCError = 0x00080000,
+ upOversizedFrame = 0x00100000,
+ upDribbleBits = 0x00800000,
+ upOverflow = 0x01000000,
+
+ dnIndicate = 0x80000000, /* FrameStartHeader (dpd->control) */
+
+ updnLastFrag = 0x80000000, /* (dpd->len) */
+
+ Nup = 32,
+ Ndn = 64,
+};
+
+/*
+ * Up/Dn Packet Descriptors.
+ * The hardware info (np, control, addr, len) must be 8-byte aligned
+ * and this structure size must be a multiple of 8.
+ */
+typedef struct Pd Pd;
+typedef struct Pd {
+ ulong np; /* next pointer */
+ ulong control; /* FSH or UpPktStatus */
+ ulong addr;
+ ulong len;
+
+ Pd* next;
+ Block* bp;
+} Pd;
+
+typedef struct Ctlr Ctlr;
+typedef struct Ctlr {
+ int port;
+ Pcidev* pcidev;
+ int irq;
+ Ctlr* next;
+ int active;
+ int did;
+
+ Lock wlock; /* window access */
+
+ int attached;
+ int busmaster;
+ Block* rbp; /* receive buffer */
+
+ Block* txbp; /* FIFO -based transmission */
+ int txthreshold;
+ int txbusy;
+
+ int nup; /* full-busmaster -based reception */
+ void* upbase;
+ Pd* upr;
+ Pd* uphead;
+
+ int ndn; /* full-busmaster -based transmission */
+ void* dnbase;
+ Pd* dnr;
+ Pd* dnhead;
+ Pd* dntail;
+ int dnq;
+
+ long interrupts; /* statistics */
+ long bogusinterrupts;
+ long timer[2];
+ long stats[BytesRcvdOk+3];
+
+ int upqmax;
+ int upqmaxhw;
+ ulong upinterrupts;
+ ulong upqueued;
+ ulong upstalls;
+ int dnqmax;
+ int dnqmaxhw;
+ ulong dninterrupts;
+ ulong dnqueued;
+
+ int xcvr; /* transceiver type */
+ int eepromcmd; /* EEPROM read command */
+ int rxstatus9; /* old-style RxStatus register */
+ int rxearly; /* RxEarlyThreshold */
+ int ts; /* threshold shift */
+ int upenabled;
+ int dnenabled;
+ ulong cbfnpa; /* CardBus functions */
+ ulong* cbfn;
+} Ctlr;
+
+static Ctlr* ctlrhead;
+static Ctlr* ctlrtail;
+
+static void
+init905(Ctlr* ctlr)
+{
+ Block *bp;
+ Pd *pd, *prev;
+
+ /*
+ * Create rings for the receive and transmit sides.
+ * Take care with alignment:
+ * make sure ring base is 8-byte aligned;
+ * make sure each entry is 8-byte aligned.
+ */
+ ctlr->upbase = malloc((ctlr->nup+1)*sizeof(Pd));
+ ctlr->upr = (Pd*)ROUNDUP((ulong)ctlr->upbase, 8);
+
+ prev = ctlr->upr;
+ for(pd = &ctlr->upr[ctlr->nup-1]; pd >= ctlr->upr; pd--){
+ pd->np = PADDR(&prev->np);
+ pd->control = 0;
+ bp = iallocb(sizeof(Etherpkt));
+ if(bp == nil)
+ panic("can't allocate ethernet receive ring");
+ pd->addr = PADDR(bp->rp);
+ pd->len = updnLastFrag|sizeof(Etherpkt);
+
+ pd->next = prev;
+ prev = pd;
+ pd->bp = bp;
+ }
+ ctlr->uphead = ctlr->upr;
+
+ ctlr->dnbase = malloc((ctlr->ndn+1)*sizeof(Pd));
+ ctlr->dnr = (Pd*)ROUNDUP((ulong)ctlr->dnbase, 8);
+
+ prev = ctlr->dnr;
+ for(pd = &ctlr->dnr[ctlr->ndn-1]; pd >= ctlr->dnr; pd--){
+ pd->next = prev;
+ prev = pd;
+ }
+ ctlr->dnhead = ctlr->dnr;
+ ctlr->dntail = ctlr->dnr;
+ ctlr->dnq = 0;
+}
+
+static Block*
+rbpalloc(Block* (*f)(int))
+{
+ Block *bp;
+ ulong addr;
+
+ /*
+ * The receive buffers must be on a 32-byte
+ * boundary for EISA busmastering.
+ */
+ if(bp = f(ROUNDUP(sizeof(Etherpkt), 4) + 31)){
+ addr = (ulong)bp->base;
+ addr = ROUNDUP(addr, 32);
+ bp->rp = (uchar*)addr;
+ }
+
+ return bp;
+}
+
+static uchar*
+startdma(Ether* ether, ulong address)
+{
+ int port, status, w;
+ uchar *wp;
+
+ port = ether->port;
+
+ w = (STATUS(port)>>13) & 0x07;
+ COMMAND(port, SelectRegisterWindow, Wmaster);
+
+ wp = KADDR(inl(port+MasterAddress));
+ status = ins(port+MasterStatus);
+ if(status & (masterInProgress|targetAbort|masterAbort))
+ print("#l%d: BM status 0x%uX\n", ether->ctlrno, status);
+ outs(port+MasterStatus, masterMask);
+ outl(port+MasterAddress, address);
+ outs(port+MasterLen, sizeof(Etherpkt));
+ COMMAND(port, StartDma, Upload);
+
+ COMMAND(port, SelectRegisterWindow, w);
+ return wp;
+}
+
+static void
+promiscuous(void* arg, int on)
+{
+ int filter, port;
+ Ether *ether;
+
+ ether = (Ether*)arg;
+ port = ether->port;
+
+ filter = receiveBroadcast|receiveIndividual;
+ if(ether->nmaddr)
+ filter |= receiveMulticast;
+ if(on)
+ filter |= receiveAllFrames;
+ COMMAND(port, SetRxFilter, filter);
+}
+
+static void
+multicast(void* arg, uchar *addr, int on)
+{
+ int filter, port;
+ Ether *ether;
+
+ USED(addr, on);
+
+ ether = (Ether*)arg;
+ port = ether->port;
+
+ filter = receiveBroadcast|receiveIndividual;
+ if(ether->nmaddr)
+ filter |= receiveMulticast;
+ if(ether->prom)
+ filter |= receiveAllFrames;
+ COMMAND(port, SetRxFilter, filter);
+}
+
+/* On the 575B and C, interrupts need to be acknowledged in CardBus memory space */
+static void
+intrackcb(ulong *cbfn)
+{
+ cbfn[1] = 0x8000;
+}
+
+static void
+attach(Ether* ether)
+{
+ int port, x;
+ Ctlr *ctlr;
+
+ ctlr = ether->ctlr;
+ ilock(&ctlr->wlock);
+ if(ctlr->attached){
+ iunlock(&ctlr->wlock);
+ return;
+ }
+
+ port = ether->port;
+
+ /*
+ * Set the receiver packet filter for this and broadcast addresses,
+ * set the interrupt masks for all interrupts, enable the receiver
+ * and transmitter.
+ */
+ promiscuous(ether, ether->prom);
+
+ x = interruptMask;
+ if(ctlr->busmaster == 1)
+ x &= ~(rxEarly|rxComplete);
+ else{
+ if(ctlr->dnenabled)
+ x &= ~transferInt;
+ if(ctlr->upenabled)
+ x &= ~(rxEarly|rxComplete);
+ }
+ COMMAND(port, SetIndicationEnable, x);
+ COMMAND(port, SetInterruptEnable, x);
+ COMMAND(port, RxEnable, 0);
+ COMMAND(port, TxEnable, 0);
+
+ /*
+ * If this is a CardBus card, acknowledge any interrupts.
+ */
+ if(ctlr->cbfn != nil)
+ intrackcb(ctlr->cbfn);
+
+ /*
+ * Prime the busmaster channel for receiving directly into a
+ * receive packet buffer if necessary.
+ */
+ if(ctlr->busmaster == 1)
+ startdma(ether, PADDR(ctlr->rbp->rp));
+ else{
+ if(ctlr->upenabled)
+ outl(port+UpListPtr, PADDR(&ctlr->uphead->np));
+ }
+
+ ctlr->attached = 1;
+ iunlock(&ctlr->wlock);
+}
+
+static void
+statistics(Ether* ether)
+{
+ int port, i, u, w;
+ Ctlr *ctlr;
+
+ port = ether->port;
+ ctlr = ether->ctlr;
+
+ /*
+ * 3C59[27] require a read between a PIO write and
+ * reading a statistics register.
+ */
+ w = (STATUS(port)>>13) & 0x07;
+ COMMAND(port, SelectRegisterWindow, Wstatistics);
+ STATUS(port);
+
+ for(i = 0; i < UpperFramesOk; i++)
+ ctlr->stats[i] += inb(port+i) & 0xFF;
+ u = inb(port+UpperFramesOk) & 0xFF;
+ ctlr->stats[FramesXmittedOk] += (u & 0x30)<<4;
+ ctlr->stats[FramesRcvdOk] += (u & 0x03)<<8;
+ ctlr->stats[BytesRcvdOk] += ins(port+BytesRcvdOk) & 0xFFFF;
+ ctlr->stats[BytesRcvdOk+1] += ins(port+BytesXmittedOk) & 0xFFFF;
+
+ switch(ctlr->xcvr){
+
+ case xcvrMii:
+ case xcvr100BaseTX:
+ case xcvr100BaseFX:
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ STATUS(port);
+ ctlr->stats[BytesRcvdOk+2] += inb(port+BadSSD);
+ break;
+ }
+
+ COMMAND(port, SelectRegisterWindow, w);
+}
+
+static void
+txstart(Ether* ether)
+{
+ int port, len;
+ Ctlr *ctlr;
+ Block *bp;
+
+ port = ether->port;
+ ctlr = ether->ctlr;
+
+ /*
+ * Attempt to top-up the transmit FIFO. If there's room simply
+ * stuff in the packet length (unpadded to a dword boundary), the
+ * packet data (padded) and remove the packet from the queue.
+ * If there's no room post an interrupt for when there is.
+ * This routine is called both from the top level and from interrupt
+ * level and expects to be called with ctlr->wlock already locked
+ * and the correct register window (Wop) in place.
+ */
+ for(;;){
+ if(ctlr->txbp){
+ bp = ctlr->txbp;
+ ctlr->txbp = 0;
+ }
+ else{
+ bp = qget(ether->oq);
+ if(bp == nil)
+ break;
+ }
+
+ len = ROUNDUP(BLEN(bp), 4);
+ if(len+4 <= ins(port+TxFree)){
+ outl(port+Fifo, BLEN(bp));
+ outsl(port+Fifo, bp->rp, len/4);
+
+ freeb(bp);
+
+ ether->outpackets++;
+ }
+ else{
+ ctlr->txbp = bp;
+ if(ctlr->txbusy == 0){
+ ctlr->txbusy = 1;
+ COMMAND(port, SetTxAvailableThresh, len>>ctlr->ts);
+ }
+ break;
+ }
+ }
+}
+
+static void
+txstart905(Ether* ether)
+{
+ Ctlr *ctlr;
+ int port, stalled, timeo;
+ Block *bp;
+ Pd *pd;
+
+ ctlr = ether->ctlr;
+ port = ether->port;
+
+ /*
+ * Free any completed packets.
+ */
+ pd = ctlr->dntail;
+ while(ctlr->dnq){
+ if(PADDR(&pd->np) == inl(port+DnListPtr))
+ break;
+ if(pd->bp){
+ freeb(pd->bp);
+ pd->bp = nil;
+ }
+ ctlr->dnq--;
+ pd = pd->next;
+ }
+ ctlr->dntail = pd;
+
+ stalled = 0;
+ while(ctlr->dnq < (ctlr->ndn-1)){
+ bp = qget(ether->oq);
+ if(bp == nil)
+ break;
+
+ pd = ctlr->dnhead->next;
+ pd->np = 0;
+ pd->control = dnIndicate|BLEN(bp);
+ pd->addr = PADDR(bp->rp);
+ pd->len = updnLastFrag|BLEN(bp);
+ pd->bp = bp;
+
+ if(stalled == 0 && ctlr->dnq && inl(port+DnListPtr)){
+ COMMAND(port, Stall, dnStall);
+ for(timeo = 100; (STATUS(port) & commandInProgress) && timeo; timeo--)
+ ;
+ if(timeo == 0)
+ print("#l%d: dnstall %d\n", ether->ctlrno, timeo);
+ stalled = 1;
+ }
+
+ coherence();
+ ctlr->dnhead->np = PADDR(&pd->np);
+ ctlr->dnhead->control &= ~dnIndicate;
+ ctlr->dnhead = pd;
+ if(ctlr->dnq == 0)
+ ctlr->dntail = pd;
+ ctlr->dnq++;
+
+ ctlr->dnqueued++;
+ }
+
+ if(ctlr->dnq > ctlr->dnqmax)
+ ctlr->dnqmax = ctlr->dnq;
+
+ /*
+ * If the adapter is not currently processing anything
+ * and there is something on the queue, start it processing.
+ */
+ if(inl(port+DnListPtr) == 0 && ctlr->dnq)
+ outl(port+DnListPtr, PADDR(&ctlr->dnhead->np));
+ if(stalled)
+ COMMAND(port, Stall, dnUnStall);
+}
+
+static void
+transmit(Ether* ether)
+{
+ Ctlr *ctlr;
+ int port, w;
+
+ port = ether->port;
+ ctlr = ether->ctlr;
+
+ ilock(&ctlr->wlock);
+ if(ctlr->dnenabled)
+ txstart905(ether);
+ else{
+ w = (STATUS(port)>>13) & 0x07;
+ COMMAND(port, SelectRegisterWindow, Wop);
+ txstart(ether);
+ COMMAND(port, SelectRegisterWindow, w);
+ }
+ iunlock(&ctlr->wlock);
+}
+
+static void
+receive905(Ether* ether)
+{
+ Ctlr *ctlr;
+ int len, port, q;
+ Pd *pd;
+ Block *bp;
+
+ ctlr = ether->ctlr;
+ port = ether->port;
+
+ if(inl(port+UpPktStatus) & upStalled)
+ ctlr->upstalls++;
+ q = 0;
+ for(pd = ctlr->uphead; pd->control & upPktComplete; pd = pd->next){
+ if(pd->control & upError){
+ if(pd->control & upOverrun)
+ ether->overflows++;
+ if(pd->control & (upOversizedFrame|upRuntFrame))
+ ether->buffs++;
+ if(pd->control & upAlignmentError)
+ ether->frames++;
+ if(pd->control & upCRCError)
+ ether->crcs++;
+ }
+ else if(bp = iallocb(sizeof(Etherpkt)+4)){
+ len = pd->control & rxBytes;
+ pd->bp->wp = pd->bp->rp+len;
+ etheriq(ether, pd->bp, 1);
+ pd->bp = bp;
+ pd->addr = PADDR(bp->rp);
+ coherence();
+ }
+
+ pd->control = 0;
+ COMMAND(port, Stall, upUnStall);
+
+ q++;
+ }
+ ctlr->uphead = pd;
+
+ ctlr->upqueued += q;
+ if(q > ctlr->upqmax)
+ ctlr->upqmax = q;
+}
+
+static void
+receive(Ether* ether)
+{
+ int len, port, rxerror, rxstatus;
+ Ctlr *ctlr;
+ Block *bp;
+
+ port = ether->port;
+ ctlr = ether->ctlr;
+
+ while(((rxstatus = ins(port+RxStatus)) & rxIncomplete) == 0){
+ if(ctlr->busmaster == 1 && (STATUS(port) & busMasterInProgress))
+ break;
+
+ /*
+ * If there was an error, log it and continue.
+ * Unfortunately the 3C5[078]9 has the error info in the status register
+ * and the 3C59[0257] implement a separate RxError register.
+ */
+ if(rxstatus & rxError){
+ if(ctlr->rxstatus9){
+ switch(rxstatus & rxError9){
+
+ case rxOverrun9:
+ ether->overflows++;
+ break;
+
+ case oversizedFrame9:
+ case runtFrame9:
+ ether->buffs++;
+ break;
+
+ case alignmentError9:
+ ether->frames++;
+ break;
+
+ case crcError9:
+ ether->crcs++;
+ break;
+
+ }
+ }
+ else{
+ rxerror = inb(port+RxError);
+ if(rxerror & rxOverrun)
+ ether->overflows++;
+ if(rxerror & (oversizedFrame|runtFrame))
+ ether->buffs++;
+ if(rxerror & alignmentError)
+ ether->frames++;
+ if(rxerror & crcError)
+ ether->crcs++;
+ }
+ }
+
+ /*
+ * If there was an error or a new receive buffer can't be
+ * allocated, discard the packet and go on to the next.
+ */
+ if((rxstatus & rxError) || (bp = rbpalloc(iallocb)) == 0){
+ COMMAND(port, RxDiscard, 0);
+ while(STATUS(port) & commandInProgress)
+ ;
+
+ if(ctlr->busmaster == 1)
+ startdma(ether, PADDR(ctlr->rbp->rp));
+
+ continue;
+ }
+
+ /*
+ * A valid receive packet awaits:
+ * if using PIO, read it into the buffer;
+ * discard the packet from the FIFO;
+ * if using busmastering, start a new transfer for
+ * the next packet and as a side-effect get the
+ * end-pointer of the one just received;
+ * pass the packet on to whoever wants it.
+ */
+ if(ctlr->busmaster == 0 || ctlr->busmaster == 2){
+ len = (rxstatus & rxBytes9);
+ ctlr->rbp->wp = ctlr->rbp->rp + len;
+ insl(port+Fifo, ctlr->rbp->rp, HOWMANY(len, 4));
+ }
+
+ COMMAND(port, RxDiscard, 0);
+ while(STATUS(port) & commandInProgress)
+ ;
+
+ if(ctlr->busmaster == 1)
+ ctlr->rbp->wp = startdma(ether, PADDR(bp->rp));
+
+ etheriq(ether, ctlr->rbp, 1);
+ ctlr->rbp = bp;
+ }
+}
+
+static int
+ejectable(int did)
+{
+ switch (did) {
+ case 0x5157:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void
+interrupt(Ureg*, void* arg)
+{
+ Ether *ether;
+ int port, status, s, txstatus, w, x;
+ Ctlr *ctlr;
+
+ ether = arg;
+ port = ether->port;
+ ctlr = ether->ctlr;
+
+ ilock(&ctlr->wlock);
+ status = STATUS(port);
+ if(!(status & (interruptMask|interruptLatch))){
+ ctlr->bogusinterrupts++;
+ iunlock(&ctlr->wlock);
+ return;
+ }
+ w = (status>>13) & 0x07;
+ COMMAND(port, SelectRegisterWindow, Wop);
+
+ ctlr->interrupts++;
+ if(ctlr->busmaster == 2)
+ ctlr->timer[0] += inb(port+TIMER905) & 0xFF;
+ else
+ ctlr->timer[0] += inb(port+TIMER) & 0xFF;
+
+ do{
+ if(status & hostError){
+ /*
+ * Adapter failure, try to find out why, reset if
+ * necessary. What happens if Tx is active and a reset
+ * occurs, need to retransmit? This probably isn't right.
+ */
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+FifoDiagnostic);
+ COMMAND(port, SelectRegisterWindow, Wop);
+
+ if (status == 0xFFFF && x == 0xFFFF && ejectable(ctlr->did)) {
+ print("#l%d: Card ejected?\n", ether->ctlrno);
+ iunlock(&ctlr->wlock);
+ return;
+ }
+
+ print("#l%d: status 0x%uX, diag 0x%uX\n",
+ ether->ctlrno, status, x);
+
+ if(x & txOverrun){
+ if(ctlr->busmaster == 0)
+ COMMAND(port, TxReset, 0);
+ else
+ COMMAND(port, TxReset, (updnReset|dmaReset));
+ COMMAND(port, TxEnable, 0);
+ }
+
+ if(x & rxUnderrun){
+ /*
+ * This shouldn't happen...
+ * Reset the receiver and restore the filter and RxEarly
+ * threshold before re-enabling.
+ * Need to restart any busmastering?
+ */
+ COMMAND(port, SelectRegisterWindow, Wstate);
+ s = (port+RxFilter) & 0x000F;
+ COMMAND(port, SelectRegisterWindow, Wop);
+ COMMAND(port, RxReset, 0);
+ while(STATUS(port) & commandInProgress)
+ ;
+ COMMAND(port, SetRxFilter, s);
+ COMMAND(port, SetRxEarlyThresh, ctlr->rxearly>>ctlr->ts);
+ COMMAND(port, RxEnable, 0);
+ }
+
+ status &= ~hostError;
+ }
+
+ if(status & (transferInt|rxComplete)){
+ receive(ether);
+ status &= ~(transferInt|rxComplete);
+ }
+
+ if(status & (upComplete)){
+ COMMAND(port, AcknowledgeInterrupt, upComplete);
+ receive905(ether);
+ status &= ~upComplete;
+ ctlr->upinterrupts++;
+ }
+
+ if(status & txComplete){
+ /*
+ * Pop the TxStatus stack, accumulating errors.
+ * Adjust the TX start threshold if there was an underrun.
+ * If there was a Jabber or Underrun error, reset
+ * the transmitter, taking care not to reset the dma logic
+ * as a busmaster receive may be in progress.
+ * For all conditions enable the transmitter.
+ */
+ if(ctlr->busmaster == 2)
+ txstatus = port+TxStatus905;
+ else
+ txstatus = port+TxStatus;
+ s = 0;
+ do{
+ if(x = inb(txstatus))
+ outb(txstatus, 0);
+ s |= x;
+ }while(STATUS(port) & txComplete);
+
+ if(s & txUnderrun){
+ if(ctlr->dnenabled){
+ while(inl(port+PktStatus) & dnInProg)
+ ;
+ }
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ while(ins(port+MediaStatus) & txInProg)
+ ;
+ COMMAND(port, SelectRegisterWindow, Wop);
+ if(ctlr->txthreshold < ETHERMAXTU)
+ ctlr->txthreshold += ETHERMINTU;
+ }
+
+ /*
+ * According to the manual, maxCollisions does not require
+ * a TxReset, merely a TxEnable. However, evidence points to
+ * it being necessary on the 3C905. The jury is still out.
+ * On busy or badly configured networks maxCollisions can
+ * happen frequently enough for messages to be annoying so
+ * keep quiet about them by popular request.
+ */
+ if(s & (txJabber|txUnderrun|maxCollisions)){
+ if(ctlr->busmaster == 0)
+ COMMAND(port, TxReset, 0);
+ else
+ COMMAND(port, TxReset, (updnReset|dmaReset));
+ while(STATUS(port) & commandInProgress)
+ ;
+ COMMAND(port, SetTxStartThresh, ctlr->txthreshold>>ctlr->ts);
+ if(ctlr->busmaster == 2)
+ outl(port+TxFreeThresh, HOWMANY(ETHERMAXTU, 256));
+ if(ctlr->dnenabled)
+ status |= dnComplete;
+ }
+
+ if(s & ~(txStatusComplete|maxCollisions))
+ print("#l%d: txstatus 0x%uX, threshold %d\n",
+ ether->ctlrno, s, ctlr->txthreshold);
+ COMMAND(port, TxEnable, 0);
+ ether->oerrs++;
+ status &= ~txComplete;
+ status |= txAvailable;
+ }
+
+ if(status & txAvailable){
+ COMMAND(port, AcknowledgeInterrupt, txAvailable);
+ ctlr->txbusy = 0;
+ txstart(ether);
+ status &= ~txAvailable;
+ }
+
+ if(status & dnComplete){
+ COMMAND(port, AcknowledgeInterrupt, dnComplete);
+ txstart905(ether);
+ status &= ~dnComplete;
+ ctlr->dninterrupts++;
+ }
+
+ if(status & updateStats){
+ statistics(ether);
+ status &= ~updateStats;
+ }
+
+ /*
+ * Currently, this shouldn't happen.
+ */
+ if(status & rxEarly){
+ COMMAND(port, AcknowledgeInterrupt, rxEarly);
+ status &= ~rxEarly;
+ }
+
+ /*
+ * Panic if there are any interrupts not dealt with.
+ */
+ if(status & interruptMask)
+ panic("#l%d: interrupt mask 0x%uX\n", ether->ctlrno, status);
+
+ COMMAND(port, AcknowledgeInterrupt, interruptLatch);
+ if(ctlr->cbfn != nil)
+ intrackcb(ctlr->cbfn);
+
+ }while((status = STATUS(port)) & (interruptMask|interruptLatch));
+
+ if(ctlr->busmaster == 2)
+ ctlr->timer[1] += inb(port+TIMER905) & 0xFF;
+ else
+ ctlr->timer[1] += inb(port+TIMER) & 0xFF;
+
+ COMMAND(port, SelectRegisterWindow, w);
+ iunlock(&ctlr->wlock);
+}
+
+static long
+ifstat(Ether* ether, void* a, long n, ulong offset)
+{
+ char *p;
+ int len;
+ Ctlr *ctlr;
+
+ if(n == 0)
+ return 0;
+
+ ctlr = ether->ctlr;
+
+ ilock(&ctlr->wlock);
+ statistics(ether);
+ iunlock(&ctlr->wlock);
+
+ p = malloc(READSTR);
+ len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
+ len += snprint(p+len, READSTR-len, "bogusinterrupts: %lud\n", ctlr->bogusinterrupts);
+ len += snprint(p+len, READSTR-len, "timer: %lud %lud\n",
+ ctlr->timer[0], ctlr->timer[1]);
+ len += snprint(p+len, READSTR-len, "carrierlost: %lud\n",
+ ctlr->stats[CarrierLost]);
+ len += snprint(p+len, READSTR-len, "sqeerrors: %lud\n",
+ ctlr->stats[SqeErrors]);
+ len += snprint(p+len, READSTR-len, "multiplecolls: %lud\n",
+ ctlr->stats[MultipleColls]);
+ len += snprint(p+len, READSTR-len, "singlecollframes: %lud\n",
+ ctlr->stats[SingleCollFrames]);
+ len += snprint(p+len, READSTR-len, "latecollisions: %lud\n",
+ ctlr->stats[LateCollisions]);
+ len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n",
+ ctlr->stats[RxOverruns]);
+ len += snprint(p+len, READSTR-len, "framesxmittedok: %lud\n",
+ ctlr->stats[FramesXmittedOk]);
+ len += snprint(p+len, READSTR-len, "framesrcvdok: %lud\n",
+ ctlr->stats[FramesRcvdOk]);
+ len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n",
+ ctlr->stats[FramesDeferred]);
+ len += snprint(p+len, READSTR-len, "bytesrcvdok: %lud\n",
+ ctlr->stats[BytesRcvdOk]);
+ len += snprint(p+len, READSTR-len, "bytesxmittedok: %lud\n",
+ ctlr->stats[BytesRcvdOk+1]);
+
+ if(ctlr->upenabled){
+ if(ctlr->upqmax > ctlr->upqmaxhw)
+ ctlr->upqmaxhw = ctlr->upqmax;
+ len += snprint(p+len, READSTR-len, "up: q %lud i %lud m %d h %d s %lud\n",
+ ctlr->upqueued, ctlr->upinterrupts,
+ ctlr->upqmax, ctlr->upqmaxhw, ctlr->upstalls);
+ ctlr->upqmax = 0;
+ }
+ if(ctlr->dnenabled){
+ if(ctlr->dnqmax > ctlr->dnqmaxhw)
+ ctlr->dnqmaxhw = ctlr->dnqmax;
+ len += snprint(p+len, READSTR-len, "dn: q %lud i %lud m %d h %d\n",
+ ctlr->dnqueued, ctlr->dninterrupts, ctlr->dnqmax, ctlr->dnqmaxhw);
+ ctlr->dnqmax = 0;
+ }
+
+ snprint(p+len, READSTR-len, "badssd: %lud\n", ctlr->stats[BytesRcvdOk+2]);
+
+ n = readstr(offset, a, n, p);
+ free(p);
+
+ return n;
+}
+
+static void
+txrxreset(int port)
+{
+ COMMAND(port, TxReset, 0);
+ while(STATUS(port) & commandInProgress)
+ ;
+ COMMAND(port, RxReset, 0);
+ while(STATUS(port) & commandInProgress)
+ ;
+}
+
+static Ctlr*
+tcmadapter(int port, int irq, Pcidev* pcidev)
+{
+ Ctlr *ctlr;
+
+ ctlr = malloc(sizeof(Ctlr));
+ ctlr->port = port;
+ ctlr->irq = irq;
+ ctlr->pcidev = pcidev;
+ ctlr->eepromcmd = EepromReadRegister;
+
+ if(ctlrhead != nil)
+ ctlrtail->next = ctlr;
+ else
+ ctlrhead = ctlr;
+ ctlrtail = ctlr;
+
+ return ctlr;
+}
+
+/*
+ * Write two 0 bytes to identify the IDport and then reset the
+ * ID sequence. Then send the ID sequence to the card to get
+ * the card into command state.
+ */
+static void
+idseq(void)
+{
+ int i;
+ uchar al;
+ static int reset, untag;
+
+ /*
+ * One time only:
+ * reset any adapters listening
+ */
+ if(reset == 0){
+ outb(IDport, 0);
+ outb(IDport, 0);
+ outb(IDport, 0xC0);
+ delay(20);
+ reset = 1;
+ }
+
+ outb(IDport, 0);
+ outb(IDport, 0);
+ for(al = 0xFF, i = 0; i < 255; i++){
+ outb(IDport, al);
+ if(al & 0x80){
+ al <<= 1;
+ al ^= 0xCF;
+ }
+ else
+ al <<= 1;
+ }
+
+ /*
+ * One time only:
+ * write ID sequence to get the attention of all adapters;
+ * untag all adapters.
+ * If a global reset is done here on all adapters it will confuse
+ * any ISA cards configured for EISA mode.
+ */
+ if(untag == 0){
+ outb(IDport, 0xD0);
+ untag = 1;
+ }
+}
+
+static ulong
+activate(void)
+{
+ int i;
+ ushort x, acr;
+
+ /*
+ * Do the little configuration dance:
+ *
+ * 2. write the ID sequence to get to command state.
+ */
+ idseq();
+
+ /*
+ * 3. Read the Manufacturer ID from the EEPROM.
+ * This is done by writing the IDPort with 0x87 (0x80
+ * is the 'read EEPROM' command, 0x07 is the offset of
+ * the Manufacturer ID field in the EEPROM).
+ * The data comes back 1 bit at a time.
+ * A delay seems necessary between reading the bits.
+ *
+ * If the ID doesn't match, there are no more adapters.
+ */
+ outb(IDport, 0x87);
+ delay(20);
+ for(x = 0, i = 0; i < 16; i++){
+ delay(20);
+ x <<= 1;
+ x |= inb(IDport) & 0x01;
+ }
+ if(x != 0x6D50)
+ return 0;
+
+ /*
+ * 3. Read the Address Configuration from the EEPROM.
+ * The Address Configuration field is at offset 0x08 in the EEPROM).
+ */
+ outb(IDport, 0x88);
+ for(acr = 0, i = 0; i < 16; i++){
+ delay(20);
+ acr <<= 1;
+ acr |= inb(IDport) & 0x01;
+ }
+
+ return (acr & 0x1F)*0x10 + 0x200;
+}
+
+static void
+tcm509isa(void)
+{
+ int irq, port;
+
+ /*
+ * Attempt to activate all adapters. If adapter is set for
+ * EISA mode (0x3F0), tag it and ignore. Otherwise, activate
+ * it fully.
+ */
+ while(port = activate()){
+ if(ioalloc(port, 0x10, 0, "tcm509isa") < 0){
+ print("tcm509isa: port 0x%uX in use\n", port);
+ continue;
+ }
+
+ /*
+ * 6. Tag the adapter so it won't respond in future.
+ */
+ outb(IDport, 0xD1);
+ if(port == 0x3F0){
+ iofree(port);
+ continue;
+ }
+
+ /*
+ * 6. Activate the adapter by writing the Activate command
+ * (0xFF).
+ */
+ outb(IDport, 0xFF);
+ delay(20);
+
+ /*
+ * 8. Can now talk to the adapter's I/O base addresses.
+ * Use the I/O base address from the acr just read.
+ *
+ * Enable the adapter and clear out any lingering status
+ * and interrupts.
+ */
+ while(STATUS(port) & commandInProgress)
+ ;
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ outs(port+ConfigControl, Ena);
+
+ txrxreset(port);
+ COMMAND(port, AcknowledgeInterrupt, 0xFF);
+
+ irq = (ins(port+ResourceConfig)>>12) & 0x0F;
+ tcmadapter(port, irq, nil);
+ }
+}
+
+static void
+tcm5XXeisa(void)
+{
+ ushort x;
+ int irq, port, slot;
+
+ /*
+ * Check if this is an EISA machine.
+ * If not, nothing to do.
+ */
+ if(strncmp((char*)KADDR(0xFFFD9), "EISA", 4))
+ return;
+
+ /*
+ * Continue through the EISA slots looking for a match on both
+ * 3COM as the manufacturer and 3C579-* or 3C59[27]-* as the product.
+ * If an adapter is found, select window 0, enable it and clear
+ * out any lingering status and interrupts.
+ */
+ for(slot = 1; slot < MaxEISA; slot++){
+ port = slot*0x1000;
+ if(ioalloc(port, 0x1000, 0, "tcm5XXeisa") < 0){
+ print("tcm5XXeisa: port 0x%uX in use\n", port);
+ continue;
+ }
+ if(ins(port+0xC80+ManufacturerID) != 0x6D50){
+ iofree(port);
+ continue;
+ }
+ x = ins(port+0xC80+ProductID);
+ if((x & 0xF0FF) != 0x9050 && (x & 0xFF00) != 0x5900){
+ iofree(port);
+ continue;
+ }
+
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ outs(port+ConfigControl, Ena);
+
+ txrxreset(port);
+ COMMAND(port, AcknowledgeInterrupt, 0xFF);
+
+ irq = (ins(port+ResourceConfig)>>12) & 0x0F;
+ tcmadapter(port, irq, nil);
+ }
+}
+
+static void
+tcm59Xpci(void)
+{
+ Pcidev *p;
+ Ctlr *ctlr;
+ int irq, port;
+
+ p = nil;
+ while(p = pcimatch(p, 0x10B7, 0)){
+ if(p->ccrb != 0x02 || p->ccru != 0)
+ continue;
+ /*
+ * Not prepared to deal with memory-mapped
+ * devices yet.
+ */
+ if(!(p->mem[0].bar & 0x01))
+ continue;
+ port = p->mem[0].bar & ~0x01;
+ if((port = ioalloc((port == 0)? -1: port, p->mem[0].size,
+ 0, "tcm59Xpci")) < 0){
+ print("tcm59Xpci: port 0x%uX in use\n", port);
+ continue;
+ }
+ irq = p->intl;
+
+ txrxreset(port);
+ COMMAND(port, AcknowledgeInterrupt, 0xFF);
+
+ ctlr = tcmadapter(port, irq, p);
+ switch(p->did){
+ default:
+ break;
+ case 0x5157:
+ ctlr->eepromcmd = EepromRead8bRegister;
+ ctlr->cbfnpa = p->mem[2].bar&~0x0F;
+ ctlr->cbfn = vmap(p->mem[2].bar&~0x0F, p->mem[2].size);
+ break;
+ case 0x6056:
+ ctlr->eepromcmd = EepromReadOffRegister;
+ ctlr->cbfnpa = p->mem[2].bar&~0x0F;
+ ctlr->cbfn = vmap(p->mem[2].bar&~0x0F, p->mem[2].size);
+ break;
+ }
+ pcisetbme(p);
+ }
+}
+
+static char* tcmpcmcia[] = {
+ "3C589", /* 3COM 589[ABCD] */
+ "3C562", /* 3COM 562 */
+ "589E", /* 3COM Megahertz 589E */
+ nil,
+};
+
+static Ctlr*
+tcm5XXpcmcia(Ether* ether)
+{
+ int i;
+ Ctlr *ctlr;
+
+ if(ether->type == nil)
+ return nil;
+
+ for(i = 0; tcmpcmcia[i] != nil; i++){
+ if(cistrcmp(ether->type, tcmpcmcia[i]))
+ continue;
+ ctlr = tcmadapter(ether->port, ether->irq, nil);
+ ctlr->active = 1;
+ return ctlr;
+ }
+
+ return nil;
+}
+
+static void
+setxcvr(Ctlr* ctlr, int xcvr)
+{
+ int port, x;
+
+ port = ctlr->port;
+ if(ctlr->rxstatus9){
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ x = ins(port+AddressConfig) & ~xcvrMask9;
+ x |= (xcvr>>20)<<14;
+ outs(port+AddressConfig, x);
+ }
+ else{
+ COMMAND(port, SelectRegisterWindow, Wfifo);
+ x = inl(port+InternalConfig) & ~xcvrMask;
+ x |= xcvr;
+ outl(port+InternalConfig, x);
+ }
+
+ txrxreset(port);
+}
+
+static void
+setfullduplex(int port)
+{
+ int x;
+
+ COMMAND(port, SelectRegisterWindow, Wfifo);
+ x = ins(port+MacControl);
+ outs(port+MacControl, fullDuplexEnable|x);
+
+ txrxreset(port);
+}
+
+static int
+miimdi(int port, int n)
+{
+ int data, i;
+
+ /*
+ * Read n bits from the MII Management Register.
+ */
+ data = 0;
+ for(i = n-1; i >= 0; i--){
+ if(ins(port) & mgmtData)
+ data |= (1<<i);
+ microdelay(1);
+ outs(port, mgmtClk);
+ microdelay(1);
+ outs(port, 0);
+ microdelay(1);
+ }
+
+ return data;
+}
+
+static void
+miimdo(int port, int bits, int n)
+{
+ int i, mdo;
+
+ /*
+ * Write n bits to the MII Management Register.
+ */
+ for(i = n-1; i >= 0; i--){
+ if(bits & (1<<i))
+ mdo = mgmtDir|mgmtData;
+ else
+ mdo = mgmtDir;
+ outs(port, mdo);
+ microdelay(1);
+ outs(port, mdo|mgmtClk);
+ microdelay(1);
+ outs(port, mdo);
+ microdelay(1);
+ }
+}
+
+static int
+miir(int port, int phyad, int regad)
+{
+ int data, w;
+
+ w = (STATUS(port)>>13) & 0x07;
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ port += PhysicalMgmt;
+
+ /*
+ * Preamble;
+ * ST+OP+PHYAD+REGAD;
+ * TA + 16 data bits.
+ */
+ miimdo(port, 0xFFFFFFFF, 32);
+ miimdo(port, 0x1800|(phyad<<5)|regad, 14);
+ data = miimdi(port, 18);
+
+ port -= PhysicalMgmt;
+ COMMAND(port, SelectRegisterWindow, w);
+
+ if(data & 0x10000)
+ return -1;
+
+ return data & 0xFFFF;
+}
+
+static int
+scanphy(int port)
+{
+ int i, x;
+
+ for(i = 0; i < 32; i++){
+ if((x = miir(port, i, 2)) == -1 || x == 0)
+ continue;
+ x <<= 6;
+ x |= miir(port, i, 3)>>10;
+ XCVRDEBUG("phy%d: oui %uX reg1 %uX\n", i, x, miir(port, i, 1));
+ USED(x);
+
+ return i;
+ }
+ return 24;
+}
+
+static struct {
+ char *name;
+ int avail;
+ int xcvr;
+} media[] = {
+ "10BaseT", base10TAvailable, xcvr10BaseT,
+ "10Base2", coaxAvailable, xcvr10Base2,
+ "100BaseTX", baseTXAvailable, xcvr100BaseTX,
+ "100BaseFX", baseFXAvailable, xcvr100BaseFX,
+ "aui", auiAvailable, xcvrAui,
+ "mii", miiConnector, xcvrMii
+};
+
+static int
+autoselect(Ctlr* ctlr)
+{
+ int media, port, x;
+
+ /*
+ * Pathetic attempt at automatic media selection.
+ * Really just to get the Fast Etherlink 10BASE-T/100BASE-TX
+ * cards operational.
+ * It's a bonus if it works for anything else.
+ */
+ port = ctlr->port;
+ if(ctlr->rxstatus9){
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ x = ins(port+ConfigControl);
+ media = 0;
+ if(x & base10TAvailable9)
+ media |= base10TAvailable;
+ if(x & coaxAvailable9)
+ media |= coaxAvailable;
+ if(x & auiAvailable9)
+ media |= auiAvailable;
+ }
+ else{
+ COMMAND(port, SelectRegisterWindow, Wfifo);
+ media = ins(port+ResetOptions);
+ }
+ XCVRDEBUG("autoselect: media %uX\n", media);
+
+ if(media & miiConnector)
+ return xcvrMii;
+
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ XCVRDEBUG("autoselect: media status %uX\n", ins(port+MediaStatus));
+
+ if(media & baseTXAvailable){
+ /*
+ * Must have InternalConfig register.
+ */
+ setxcvr(ctlr, xcvr100BaseTX);
+
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+MediaStatus) & ~(dcConverterEnabled|jabberGuardEnable);
+ outs(port+MediaStatus, linkBeatEnable|x);
+ delay(10);
+
+ if(ins(port+MediaStatus) & linkBeatDetect)
+ return xcvr100BaseTX;
+ outs(port+MediaStatus, x);
+ }
+
+ if(media & base10TAvailable){
+ setxcvr(ctlr, xcvr10BaseT);
+
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+MediaStatus) & ~dcConverterEnabled;
+ outs(port+MediaStatus, linkBeatEnable|jabberGuardEnable|x);
+ delay(100);
+
+ XCVRDEBUG("autoselect: 10BaseT media status %uX\n", ins(port+MediaStatus));
+ if(ins(port+MediaStatus) & linkBeatDetect)
+ return xcvr10BaseT;
+ outs(port+MediaStatus, x);
+ }
+
+ /*
+ * Botch.
+ */
+ return autoSelect;
+}
+
+static int
+eepromdata(Ctlr* ctlr, int offset)
+{
+ int port;
+
+ port = ctlr->port;
+
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ while(EEPROMBUSY(port))
+ ;
+ EEPROMCMD(port, ctlr->eepromcmd, offset);
+ while(EEPROMBUSY(port))
+ ;
+ return EEPROMDATA(port);
+}
+
+static void
+resetctlr(Ctlr *ctlr)
+{
+ int x, port = ctlr->port;
+
+ txrxreset(port);
+ x = ins(port+ResetOp905B);
+ XCVRDEBUG("905[BC] reset ops 0x%uX\n", x);
+ x &= ~0x4010;
+ if(ctlr->did == 0x5157){
+ x |= 0x0010; /* Invert LED */
+ outs(port+ResetOp905B, x);
+ }
+ if(ctlr->did == 0x6056){
+ x |= 0x4000;
+ outs(port+ResetOp905B, x);
+
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ outs(port, 0x0800);
+ }
+}
+
+static void
+shutdown(Ether *ether)
+{
+print("etherelnk3 shutting down\n");
+ resetctlr(ether->ctlr);
+}
+
+int
+etherelnk3reset(Ether* ether)
+{
+ char *p;
+ Ctlr *ctlr;
+ uchar ea[Eaddrlen];
+ static int scandone;
+ int anar, anlpar, i, j, phyaddr, phystat, port, timeo, x;
+
+ /*
+ * Scan for adapter on PCI, EISA and finally
+ * using the little ISA configuration dance.
+ */
+ if(scandone == 0){
+ tcm59Xpci();
+ tcm5XXeisa();
+ tcm509isa();
+ scandone = 1;
+ }
+
+ /*
+ * Any adapter matches if no ether->port is supplied,
+ * otherwise the ports must match.
+ */
+ for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
+ if(ctlr->active)
+ continue;
+ if(ether->port == 0 || ether->port == ctlr->port){
+ ctlr->active = 1;
+ break;
+ }
+ }
+ if(ctlr == nil && (ctlr = tcm5XXpcmcia(ether)) == 0)
+ return -1;
+
+ ether->ctlr = ctlr;
+ port = ctlr->port;
+ ether->port = port;
+ ether->irq = ctlr->irq;
+ if(ctlr->pcidev != nil)
+ ether->tbdf = ctlr->pcidev->tbdf;
+ else
+ ether->tbdf = BUSUNKNOWN;
+
+ /*
+ * Read the DeviceID from the EEPROM, it's at offset 0x03,
+ * and do something depending on capabilities.
+ */
+ switch(ctlr->did = eepromdata(ctlr, 0x03)){
+ case 0x5157: /* 3C575 Cyclone */
+ case 0x6056:
+ /*FALLTHROUGH*/
+ case 0x4500: /* 3C450 HomePNA Tornado */
+ case 0x7646: /* 3CSOHO100-TX */
+ case 0x9055: /* 3C905B-TX */
+ case 0x9200: /* 3C905C-TX */
+ case 0x9201: /* 3C920 */
+ case 0x9805: /* 3C9805: 3C980-TX Python-T 10/100baseTX */
+ /*FALLTHROUGH*/
+ case 0x9000: /* 3C900-TPO */
+ case 0x9001: /* 3C900-COMBO */
+ case 0x9005: /* 3C900B-COMBO */
+ case 0x9050: /* 3C905-TX */
+ case 0x9051: /* 3C905-T4 */
+ if(BUSTYPE(ether->tbdf) != BusPCI)
+ goto buggery;
+ ctlr->busmaster = 2;
+ goto vortex;
+ case 0x5900: /* 3C590-[TP|COMBO|TPO] */
+ case 0x5920: /* 3C592-[TP|COMBO|TPO] */
+ case 0x5950: /* 3C595-TX */
+ case 0x5951: /* 3C595-T4 */
+ case 0x5952: /* 3C595-MII */
+ case 0x5970: /* 3C597-TX */
+ case 0x5971: /* 3C597-T4 */
+ case 0x5972: /* 3C597-MII */
+ ctlr->busmaster = 1;
+ vortex:
+ COMMAND(port, SelectRegisterWindow, Wfifo);
+ ctlr->xcvr = inl(port+InternalConfig) & (autoSelect|xcvrMask);
+ ctlr->rxearly = 8188;
+ ctlr->rxstatus9 = 0;
+ break;
+ buggery:
+ default:
+ ctlr->busmaster = 0;
+ COMMAND(port, SelectRegisterWindow, Wsetup);
+ x = ins(port+AddressConfig);
+ ctlr->xcvr = ((x & xcvrMask9)>>14)<<20;
+ if(x & autoSelect9)
+ ctlr->xcvr |= autoSelect;
+ ctlr->rxearly = 2044;
+ ctlr->rxstatus9 = 1;
+ break;
+ }
+ if(ctlr->rxearly >= 2048)
+ ctlr->ts = 2;
+
+ /*
+ * Check if the adapter's station address is to be overridden.
+ * If not, read it from the EEPROM and set in ether->ea prior to
+ * loading the station address in Wstation.
+ * The EEPROM returns 16-bits at a time.
+ */
+ memset(ea, 0, Eaddrlen);
+ if(memcmp(ea, ether->ea, Eaddrlen) == 0){
+ for(i = 0; i < Eaddrlen/2; i++){
+ x = eepromdata(ctlr, i);
+ ether->ea[2*i] = x>>8;
+ ether->ea[2*i+1] = x;
+ }
+ }
+
+ COMMAND(port, SelectRegisterWindow, Wstation);
+ for(i = 0; i < Eaddrlen; i++)
+ outb(port+i, ether->ea[i]);
+
+ /*
+ * Enable the transceiver if necessary and determine whether
+ * busmastering can be used. Due to bugs in the first revision
+ * of the 3C59[05], don't use busmastering at 10Mbps.
+ */
+ XCVRDEBUG("reset: xcvr %uX\n", ctlr->xcvr);
+
+ /*
+ * Allow user to specify desired media in plan9.ini
+ */
+ for(i = 0; i < ether->nopt; i++){
+ if(cistrncmp(ether->opt[i], "media=", 6) != 0)
+ continue;
+ p = ether->opt[i]+6;
+ for(j = 0; j < nelem(media); j++)
+ if(cistrcmp(p, media[j].name) == 0)
+ ctlr->xcvr = media[j].xcvr;
+ }
+
+ /*
+ * forgive me, but i am weak
+ */
+ switch(ctlr->did){
+ default:
+ if(ctlr->xcvr & autoSelect)
+ ctlr->xcvr = autoselect(ctlr);
+ break;
+ case 0x5157:
+ case 0x6056:
+ case 0x4500:
+ case 0x7646:
+ case 0x9055:
+ case 0x9200:
+ case 0x9201:
+ case 0x9805:
+ ctlr->xcvr = xcvrMii;
+ resetctlr(ctlr);
+ break;
+ }
+ XCVRDEBUG("xcvr selected: %uX, did 0x%uX\n", ctlr->xcvr, ctlr->did);
+
+ switch(ctlr->xcvr){
+ case xcvrMii:
+ /*
+ * Quick hack.
+ */
+ if(ctlr->did == 0x5157)
+ phyaddr = 0;
+ else if(ctlr->did == 0x6056)
+ phyaddr = scanphy(port);
+ else
+ phyaddr = 24;
+ for(i = 0; i < 7; i++)
+ XCVRDEBUG(" %2.2uX", miir(port, phyaddr, i));
+ XCVRDEBUG("\n");
+
+ for(timeo = 0; timeo < 30; timeo++){
+ phystat = miir(port, phyaddr, 0x01);
+ if(phystat & 0x20)
+ break;
+ XCVRDEBUG(" %2.2uX", phystat);
+ delay(100);
+ }
+ XCVRDEBUG(" %2.2uX", miir(port, phyaddr, 0x01));
+ XCVRDEBUG("\n");
+
+ anar = miir(port, phyaddr, 0x04);
+ anlpar = miir(port, phyaddr, 0x05) & 0x03E0;
+ anar &= anlpar;
+ miir(port, phyaddr, 0x00);
+ XCVRDEBUG("mii an: %uX anlp: %uX r0:%uX r1:%uX\n",
+ anar, anlpar, miir(port, phyaddr, 0x00),
+ miir(port, phyaddr, 0x01));
+ for(i = 0; i < ether->nopt; i++){
+ if(cistrcmp(ether->opt[i], "fullduplex") == 0)
+ anar |= 0x0100;
+ else if(cistrcmp(ether->opt[i], "100BASE-TXFD") == 0)
+ anar |= 0x0100;
+ else if(cistrcmp(ether->opt[i], "force100") == 0)
+ anar |= 0x0080;
+ }
+ XCVRDEBUG("mii anar: %uX\n", anar);
+ if(anar & 0x0100){ /* 100BASE-TXFD */
+ ether->mbps = 100;
+ setfullduplex(port);
+ }
+ else if(anar & 0x0200){ /* 100BASE-T4 */
+ /* nothing to do */
+ }
+ else if(anar & 0x0080) /* 100BASE-TX */
+ ether->mbps = 100;
+ else if(anar & 0x0040) /* 10BASE-TFD */
+ setfullduplex(port);
+ else{ /* 10BASE-T */
+ /* nothing to do */
+ }
+ break;
+ case xcvr100BaseTX:
+ case xcvr100BaseFX:
+ COMMAND(port, SelectRegisterWindow, Wfifo);
+ x = inl(port+InternalConfig) & ~ramPartitionMask;
+ outl(port+InternalConfig, x|ramPartition1to1);
+
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+MediaStatus) & ~(dcConverterEnabled|jabberGuardEnable);
+ x |= linkBeatEnable;
+ outs(port+MediaStatus, x);
+
+ if(x & dataRate100)
+ ether->mbps = 100;
+ break;
+ case xcvr10BaseT:
+ /*
+ * Enable Link Beat and Jabber to start the
+ * transceiver.
+ */
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+MediaStatus) & ~dcConverterEnabled;
+ x |= linkBeatEnable|jabberGuardEnable;
+ outs(port+MediaStatus, x);
+
+ if((ctlr->did & 0xFF00) == 0x5900)
+ ctlr->busmaster = 0;
+ break;
+ case xcvr10Base2:
+ COMMAND(port, SelectRegisterWindow, Wdiagnostic);
+ x = ins(port+MediaStatus) & ~(linkBeatEnable|jabberGuardEnable);
+ outs(port+MediaStatus, x);
+
+ /*
+ * Start the DC-DC converter.
+ * Wait > 800 microseconds.
+ */
+ COMMAND(port, EnableDcConverter, 0);
+ delay(1);
+ break;
+ }
+
+ /*
+ * Wop is the normal operating register set.
+ * The 3C59[0257] adapters allow access to more than one register window
+ * at a time, but there are situations where switching still needs to be
+ * done, so just do it.
+ * Clear out any lingering Tx status.
+ */
+ COMMAND(port, SelectRegisterWindow, Wop);
+ if(ctlr->busmaster == 2)
+ x = port+TxStatus905;
+ else
+ x = port+TxStatus;
+ while(inb(x))
+ outb(x, 0);
+
+ /*
+ * Clear out the
+ * adapter statistics, clear the statistics logged into ctlr
+ * and enable statistics collection.
+ */
+ ilock(&ctlr->wlock);
+ statistics(ether);
+ memset(ctlr->stats, 0, sizeof(ctlr->stats));
+
+ COMMAND(port, StatisticsEnable, 0);
+
+ /*
+ * Allocate any receive buffers.
+ */
+ switch(ctlr->busmaster){
+ case 2:
+ ctlr->dnenabled = 1;
+
+ /*
+ * 10MUpldBug.
+ * Disabling is too severe, can use receive busmastering at
+ * 100Mbps OK, but how to tell which rate is actually being used -
+ * the 3c905 always seems to have dataRate100 set?
+ * Believe the bug doesn't apply if upRxEarlyEnable is set
+ * and the threshold is set such that uploads won't start
+ * until the whole packet has been received.
+ */
+ ctlr->upenabled = 1;
+ x = eepromdata(ctlr, 0x0F);
+ if(!(x & 0x01))
+ outl(port+PktStatus, upRxEarlyEnable);
+
+ if(ctlr->upenabled || ctlr->dnenabled){
+ ctlr->nup = Nup;
+ ctlr->ndn = Ndn;
+ init905(ctlr);
+ }
+ else {
+ ctlr->rbp = rbpalloc(iallocb);
+ if(ctlr->rbp == nil)
+ panic("can't reset ethernet: out of memory");
+ }
+ outl(port+TxFreeThresh, HOWMANY(ETHERMAXTU, 256));
+ break;
+ default:
+ ctlr->rbp = rbpalloc(iallocb);
+ if(ctlr->rbp == nil)
+ panic("can't reset ethernet: out of memory");
+ break;
+ }
+
+ /*
+ * Set a base TxStartThresh which will be incremented
+ * if any txUnderrun errors occur and ensure no RxEarly
+ * interrupts happen.
+ */
+ ctlr->txthreshold = ETHERMAXTU/2;
+ COMMAND(port, SetTxStartThresh, ctlr->txthreshold>>ctlr->ts);
+ COMMAND(port, SetRxEarlyThresh, ctlr->rxearly>>ctlr->ts);
+
+ iunlock(&ctlr->wlock);
+
+ /*
+ * Linkage to the generic ethernet driver.
+ */
+ ether->attach = attach;
+ ether->transmit = transmit;
+ ether->interrupt = interrupt;
+ ether->ifstat = ifstat;
+
+ ether->promiscuous = promiscuous;
+ ether->multicast = multicast;
+ ether->shutdown = shutdown;
+ ether->arg = ether;
+
+ return 0;
+}
+
+void
+etherelnk3link(void)
+{
+ addethercard("elnk3", etherelnk3reset);
+ addethercard("3C509", etherelnk3reset);
+ addethercard("3C575", etherelnk3reset);
+}