plan9fox/sys/src/9/pc/ethersmc.c
2011-03-30 19:35:09 +03:00

782 lines
15 KiB
C

/*
* SMC EtherEZ (SMC91cXX chip) PCMCIA card support.
*/
#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"
enum {
IoSize = 0x10, /* port pool size */
TxTimeout = 150,
};
enum { /* PCMCIA related */
TupleFunce = 0x22,
TfNodeId = 0x04,
};
enum { /* bank 0 registers */
Tcr = 0x0000, /* transmit control */
Eph = 0x0002, /* ethernet protocol handler */
Rcr = 0x0004, /* receiver control */
Counter = 0x0006, /* statistics counter */
MemInfo = 0x0008,
MemCfg = 0x000A,
};
enum { /* bank 1 registers */
Config = 0x0000,
BaseAddr = 0x0002,
Addr0 = 0x0004, /* ethernet address */
Addr1 = 0x0006,
Addr2 = 0x0008,
General = 0x000A,
Control = 0x000C,
};
enum { /* bank 2 registers */
MmuCmd = 0x0000,
PktNo = 0x0002,
AllocRes = 0x0003,
FifoPorts = 0x0004,
Pointer = 0x0006,
Data1 = 0x0008,
Interrupt = 0x000C,
IntrMask = 0x000D,
};
enum { /* bank 3 registers */
Mcast0 = 0x0000,
Mcast2 = 0x0002,
Mcast4 = 0x0004,
Mcast6 = 0x0006,
Revision = 0x000A,
};
enum {
BankSelect = 0x000E /* bank select register */
};
enum {
BsrMask = 0xFF00, /* mask for chip identification */
BsrId = 0x3300,
};
enum { /* Tcr values */
TcrClear = 0x0000,
TcrEnable = 0x0001, /* enable transmit */
TcrLoop = 0x0002, /* enable internal analogue loopback */
TcrForceCol = 0x0004, /* force collision on next tx */
TcrPadEn = 0x0080, /* pad short packets to 64 bytes */
TcrNoCrc = 0x0100, /* do not append CRC */
TcrMonCns = 0x0400, /* monitor carrier status */
TcrFduplx = 0x0800,
TcrStpSqet = 0x1000,
TcrEphLoop = 0x2000,
TcrNormal = TcrEnable,
};
enum { /* Eph values */
EphTxOk = 0x0001,
Eph1Col = 0x0002, /* single collision */
EphMCol = 0x0004, /* multiple collisions */
EphTxMcast = 0x0008, /* multicast transmit */
Eph16Col = 0x0010, /* 16 collisions, tx disabled */
EphSqet = 0x0020, /* SQE test failed, tx disabled */
EphTxBcast = 0x0040, /* broadcast tx */
EphDefr = 0x0080, /* deffered tx */
EphLatCol = 0x0200, /* late collision, tx disabled */
EphLostCarr = 0x0400, /* lost carrier, tx disabled */
EphExcDefr = 0x0800, /* excessive defferals */
EphCntRol = 0x1000, /* ECR counter(s) rolled over */
EphRxOvrn = 0x2000, /* receiver overrun, packets dropped */
EphLinkOk = 0x4000,
EphTxUnrn = 0x8000, /* tx underrun */
};
enum { /* Rcr values */
RcrClear = 0x0000,
RcrPromisc = 0x0002,
RcrAllMcast = 0x0004,
RcrEnable = 0x0100,
RcrStripCrc = 0x0200,
RcrSoftReset = 0x8000,
RcrNormal = RcrStripCrc | RcrEnable,
};
enum { /* Counter value masks */
CntColMask = 0x000F, /* collisions */
CntMColMask = 0x00F0, /* multiple collisions */
CntDtxMask = 0x0F00, /* deferred transmits */
CntExDtxMask = 0xF000, /* excessively deferred transmits */
CntColShr = 1,
CntMColShr = 4,
CntDtxShr = 8,
};
enum { /* MemInfo value masks */
MirTotalMask = 0x00FF,
MirFreeMask = 0xFF00,
};
enum { /* Config values */
CfgIrqSel0 = 0x0002,
CfgIrqSel1 = 0x0004,
CfgDisLink = 0x0040, /* disable 10BaseT link test */
Cfg16Bit = 0x0080,
CfgAuiSelect = 0x0100,
CfgSetSqlch = 0x0200,
CfgFullStep = 0x0400,
CfgNoWait = 0x1000,
CfgMiiSelect = 0x8000,
};
enum { /* Control values */
CtlStore = 0x0001, /* store to EEPROM */
CtlReload = 0x0002, /* reload EEPROM into registers */
CtlEeSelect = 0x0004, /* select registers for reload/store */
CtlTeEnable = 0x0020, /* tx error detection via eph irq */
CtlCrEnable = 0x0040, /* counter rollover via eph irq */
CtlLeEnable = 0x0080, /* link error detection via eph irq*/
CtlAutoRls = 0x0800, /* auto release mode */
CtlPowerDn = 0x2000,
};
enum { /* MmuCmd values */
McBusy = 0x0001,
McAlloc = 0x0020, /* | with number of 256 byte packets - 1 */
McReset = 0x0040,
McRelease = 0x0080, /* dequeue (but not free) current rx packet */
McFreePkt = 0x00A0, /* dequeue and free current rx packet */
McEnqueue = 0x00C0, /* enqueue the packet for tx */
McTxReset = 0x00E0, /* reset transmit queues */
};
enum { /* AllocRes values */
ArFailed = 0x80,
};
enum { /* FifoPorts values */
FpTxEmpty = 0x0080,
FpRxEmpty = 0x8000,
FpTxMask = 0x007F,
FpRxMask = 0x7F00,
};
enum { /* Pointer values */
PtrRead = 0x2000,
PtrAutoInc = 0x4000,
PtrRcv = 0x8000,
};
enum { /* Interrupt values */
IntRcv = 0x0001,
IntTxError = 0x0002,
IntTxEmpty = 0x0004,
IntAlloc = 0x0008,
IntRxOvrn = 0x0010,
IntEph = 0x0020,
};
enum { /* transmit status bits */
TsSuccess = 0x0001,
Ts16Col = 0x00A0,
TsLatCol = 0x0200,
TsLostCar = 0x0400,
};
enum { /* receive status bits */
RsMcast = 0x0001,
RsTooShort = 0x0400,
RsTooLong = 0x0800,
RsOddFrame = 0x1000,
RsBadCrc = 0x2000,
RsAlgnErr = 0x8000,
RsError = RsAlgnErr | RsBadCrc | RsTooLong | RsTooShort,
};
enum {
RxLenMask = 0x07FF, /* significant rx len bits */
HdrSize = 6, /* packet header length */
PageSize = 256, /* page length */
};
typedef struct Smc91xx Smc91xx;
struct Smc91xx {
Lock;
ushort rev;
int attached;
Block *txbp;
ulong txtime;
ulong rovrn;
ulong lcar;
ulong col;
ulong scol;
ulong mcol;
ulong lcol;
ulong dfr;
};
#define SELECT_BANK(x) outs(port + BankSelect, x)
static int
readnodeid(int slot, Ether* ether)
{
uchar data[Eaddrlen + 1];
int len;
len = sizeof(data);
if (pcmcistuple(slot, TupleFunce, TfNodeId, data, len) != len)
return -1;
if (data[0] != Eaddrlen)
return -1;
memmove(ether->ea, &data[1], Eaddrlen);
return 0;
}
static void
chipreset(Ether* ether)
{
int port;
int i;
port = ether->port;
/* reset the chip */
SELECT_BANK(0);
outs(port + Rcr, RcrSoftReset);
delay(1);
outs(port + Rcr, RcrClear);
outs(port + Tcr, TcrClear);
SELECT_BANK(1);
outs(port + Control, CtlAutoRls | CtlTeEnable |
CtlCrEnable);
for(i = 0; i < 6; i++) {
outb(port + Addr0 + i, ether->ea[i]);
}
SELECT_BANK(2);
outs(port + MmuCmd, McReset);
}
static void
chipenable(Ether* ether)
{
int port;
port = ether->port;
SELECT_BANK(0);
outs(port + Tcr, TcrNormal);
outs(port + Rcr, RcrNormal);
SELECT_BANK(2);
outb(port + IntrMask, IntEph | IntRxOvrn | IntRcv);
}
static void
attach(Ether *ether)
{
Smc91xx* ctlr;
ctlr = ether->ctlr;
ilock(ctlr);
if (ctlr->attached) {
iunlock(ctlr);
return;
}
chipenable(ether);
ctlr->attached = 1;
iunlock(ctlr);
}
static void
txstart(Ether* ether)
{
int port;
Smc91xx* ctlr;
Block* bp;
int len, npages;
int pno;
/* assumes ctlr is locked and bank 2 is selected */
/* leaves bank 2 selected on return */
port = ether->port;
ctlr = ether->ctlr;
if (ctlr->txbp) {
bp = ctlr->txbp;
ctlr->txbp = 0;
} else {
bp = qget(ether->oq);
if (bp == 0)
return;
len = BLEN(bp);
npages = (len + HdrSize) / PageSize;
outs(port + MmuCmd, McAlloc | npages);
}
pno = inb(port + AllocRes);
if (pno & ArFailed) {
outb(port + IntrMask, inb(port + IntrMask) | IntAlloc);
ctlr->txbp = bp;
ctlr->txtime = MACHP(0)->ticks;
return;
}
outb(port + PktNo, pno);
outs(port + Pointer, PtrAutoInc);
len = BLEN(bp);
outs(port + Data1, 0);
outb(port + Data1, (len + HdrSize) & 0xFF);
outb(port + Data1, (len + HdrSize) >> 8);
outss(port + Data1, bp->rp, len / 2);
if ((len & 1) == 0) {
outs(port + Data1, 0);
} else {
outb(port + Data1, bp->rp[len - 1]);
outb(port + Data1, 0x20); /* no info what 0x20 means */
}
outb(port + IntrMask, inb(port + IntrMask) |
IntTxError | IntTxEmpty);
outs(port + MmuCmd, McEnqueue);
freeb(bp);
}
static void
receive(Ether* ether)
{
int port;
Block* bp;
int pktno, status, len;
/* assumes ctlr is locked and bank 2 is selected */
/* leaves bank 2 selected on return */
port = ether->port;
pktno = ins(port + FifoPorts);
if (pktno & FpRxEmpty) {
return;
}
outs(port + Pointer, PtrRead | PtrRcv | PtrAutoInc);
status = ins(port + Data1);
len = ins(port + Data1) & RxLenMask - HdrSize;
if (status & RsOddFrame)
len++;
if ((status & RsError) || (bp = iallocb(len)) == 0) {
if (status & RsAlgnErr)
ether->frames++;
if (status & (RsTooShort | RsTooLong))
ether->buffs++;
if (status & RsBadCrc)
ether->crcs++;
outs(port + MmuCmd, McRelease);
return;
}
/* packet length is padded to word */
inss(port + Data1, bp->rp, len / 2);
bp->wp = bp->rp + (len & ~1);
if (len & 1) {
*bp->wp = inb(port + Data1);
bp->wp++;
}
etheriq(ether, bp, 1);
ether->inpackets++;
outs(port + MmuCmd, McRelease);
}
static void
txerror(Ether* ether)
{
int port;
Smc91xx* ctlr;
int save_pkt;
int pktno, status;
/* assumes ctlr is locked and bank 2 is selected */
/* leaves bank 2 selected on return */
port = ether->port;
ctlr = ether->ctlr;
save_pkt = inb(port + PktNo);
pktno = ins(port + FifoPorts) & FpTxMask;
outb(port + PktNo, pktno);
outs(port + Pointer, PtrAutoInc | PtrRead);
status = ins(port + Data1);
if (status & TsLostCar)
ctlr->lcar++;
if (status & TsLatCol)
ctlr->lcol++;
if (status & Ts16Col)
ctlr->scol++;
ether->oerrs++;
SELECT_BANK(0);
outs(port + Tcr, ins(port + Tcr) | TcrEnable);
SELECT_BANK(2);
outs(port + MmuCmd, McFreePkt);
outb(port + PktNo, save_pkt);
}
static void
eph_irq(Ether* ether)
{
int port;
Smc91xx* ctlr;
ushort status;
int n;
/* assumes ctlr is locked and bank 2 is selected */
/* leaves bank 2 selected on return */
port = ether->port;
ctlr = ether->ctlr;
SELECT_BANK(0);
status = ins(port + Eph);
if (status & EphCntRol) {
/* read the counter register even if we don't need it */
/* otherwise we will keep getting this interrupt */
n = ins(port + Counter);
ctlr->col += (n & CntColMask) >> CntColShr;
ctlr->mcol += (n & CntMColMask) >> CntMColShr;
ctlr->dfr += (n & CntDtxMask) >> CntDtxShr;
}
/* if there was a transmit error, Tcr is disabled */
outs(port + Tcr, ins(port + Tcr) | TcrEnable);
/* clear a link error interrupt */
SELECT_BANK(1);
outs(port + Control, CtlAutoRls);
outs(port + Control, CtlAutoRls | CtlTeEnable | CtlCrEnable);
SELECT_BANK(2);
}
static void
transmit(Ether* ether)
{
Smc91xx* ctlr;
int port, n;
ctlr = ether->ctlr;
port = ether->port;
ilock(ctlr);
if (ctlr->txbp) {
n = TK2MS(MACHP(0)->ticks - ctlr->txtime);
if (n > TxTimeout) {
chipreset(ether);
chipenable(ether);
freeb(ctlr->txbp);
ctlr->txbp = 0;
}
iunlock(ctlr);
return;
}
SELECT_BANK(2);
txstart(ether);
iunlock(ctlr);
}
static void
interrupt(Ureg*, void *arg)
{
int port;
Smc91xx* ctlr;
Ether* ether;
int save_bank;
int save_pointer;
int mask, status;
ether = arg;
port = ether->port;
ctlr = ether->ctlr;
ilock(ctlr);
save_bank = ins(port + BankSelect);
SELECT_BANK(2);
save_pointer = ins(port + Pointer);
mask = inb(port + IntrMask);
outb(port + IntrMask, 0);
while ((status = inb(port + Interrupt) & mask) != 0) {
if (status & IntRcv) {
receive(ether);
}
if (status & IntTxError) {
txerror(ether);
}
if (status & IntTxEmpty) {
outb(port + Interrupt, IntTxEmpty);
outb(port + IntrMask, mask & ~IntTxEmpty);
txstart(ether);
mask = inb(port + IntrMask);
}
if (status & IntAlloc) {
outb(port + IntrMask, mask & ~IntAlloc);
txstart(ether);;
mask = inb(port + IntrMask);
}
if (status & IntRxOvrn) {
ctlr->rovrn++;
ether->misses++;
outb(port + Interrupt,IntRxOvrn);
}
if (status & IntEph)
eph_irq(ether);
}
outb(port + IntrMask, mask);
outs(port + Pointer, save_pointer);
outs(port + BankSelect, save_bank);
iunlock(ctlr);
}
static void
promiscuous(void* arg, int on)
{
int port;
Smc91xx *ctlr;
Ether* ether;
ushort x;
ether = arg;
port = ether->port;
ctlr = ether->ctlr;
ilock(ctlr);
SELECT_BANK(0);
x = ins(port + Rcr);
if (on)
x |= RcrPromisc;
else
x &= ~RcrPromisc;
outs(port + Rcr, x);
iunlock(ctlr);
}
static void
multicast(void* arg, uchar *addr, int on)
{
int port;
Smc91xx*ctlr;
Ether *ether;
ushort x;
USED(addr, on);
ether = arg;
port = ether->port;
ctlr = ether->ctlr;
ilock(ctlr);
SELECT_BANK(0);
x = ins(port + Rcr);
if (ether->nmaddr)
x |= RcrAllMcast;
else
x &= ~RcrAllMcast;
outs(port + Rcr, x);
iunlock(ctlr);
}
static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
static char *chiprev[] = {
[3] "92",
[5] "95",
[7] "100",
[8] "100-FD",
[9] "110",
};
Smc91xx* ctlr;
char* p;
int r, len;
char* s;
if (n == 0)
return 0;
ctlr = ether->ctlr;
p = malloc(READSTR);
s = 0;
if (ctlr->rev > 0) {
r = ctlr->rev >> 4;
if (r < nelem(chiprev))
s = chiprev[r];
if (r == 4) {
if ((ctlr->rev & 0x0F) >= 6)
s = "96";
else
s = "94";
}
}
len = snprint(p, READSTR, "rev: 91c%s\n", (s) ? s : "???");
len += snprint(p + len, READSTR - len, "rxovrn: %uld\n", ctlr->rovrn);
len += snprint(p + len, READSTR - len, "lcar: %uld\n", ctlr->lcar);
len += snprint(p + len, READSTR - len, "col: %uld\n", ctlr->col);
len += snprint(p + len, READSTR - len, "16col: %uld\n", ctlr->scol);
len += snprint(p + len, READSTR - len, "mcol: %uld\n", ctlr->mcol);
len += snprint(p + len, READSTR - len, "lcol: %uld\n", ctlr->lcol);
len += snprint(p + len, READSTR - len, "dfr: %uld\n", ctlr->dfr);
USED(len);
n = readstr(offset, a, n, p);
free(p);
return n;
}
static int
reset(Ether* ether)
{
int port;
int i, x;
char* type;
Smc91xx* ctlr;
int slot;
uchar ea[Eaddrlen];
if (ether->irq == 0)
ether->irq = 9;
if (ether->port == 0)
ether->port = 0x100;
type = "8020";
for(i = 0; i < ether->nopt; i++) {
if (cistrncmp(ether->opt[i], "id=", 3))
continue;
type = &ether->opt[i][3];
break;
}
if ((slot = pcmspecial(type, ether)) < 0)
return -1;
if (ioalloc(ether->port, IoSize, 0, "smc91cXX") < 0) {
pcmspecialclose(slot);
return -1;
}
ether->ctlr = malloc(sizeof(Smc91xx));
ctlr = ether->ctlr;
if (ctlr == 0) {
iofree(ether->port);
pcmspecialclose(slot);
return -1;
}
ilock(ctlr);
ctlr->rev = 0;
ctlr->txbp = nil;
ctlr->attached = 0;
ctlr->rovrn = 0;
ctlr->lcar = 0;
ctlr->col = 0;
ctlr->scol = 0;
ctlr->mcol = 0;
ctlr->lcol = 0;
ctlr->dfr = 0;
port = ether->port;
SELECT_BANK(1);
if ((ins(port + BankSelect) & BsrMask) != BsrId) {
outs(port + Control, 0); /* try powering up the chip */
delay(55);
}
outs(port + Config, ins(port + Config) | Cfg16Bit);
x = ins(port + BaseAddr);
if (((ins(port + BankSelect) & BsrMask) != BsrId) ||
((x >> 8) == (x & 0xFF))) {
iunlock(ctlr);
iofree(port);
pcmspecialclose(slot);
return -1;
}
SELECT_BANK(3);
ctlr->rev = ins(port + Revision) & 0xFF;
memset(ea, 0, Eaddrlen);
if (memcmp(ea, ether->ea, Eaddrlen) == 0) {
if (readnodeid(slot, ether) < 0) {
print("Smc91cXX: cannot find ethernet address\n");
iunlock(ctlr);
iofree(port);
pcmspecialclose(slot);
return -1;
}
}
chipreset(ether);
ether->attach = attach;
ether->transmit = transmit;
ether->interrupt = interrupt;
ether->ifstat = ifstat;
ether->promiscuous = promiscuous;
ether->multicast = multicast;
ether->arg = ether;
iunlock(ctlr);
return 0;
}
void
ethersmclink(void)
{
addethercard("smc91cXX", reset);
}