plan9fox/sys/src/9/pc/etherx550.c
cinap_lenrek 4f85115526 kernel: massive pci code rewrite
The new pci code is moved to port/pci.[hc] and shared by
all ports.

Each port has its own PCI controller implementation,
providing the pcicfgrw*() functions for low level pci
config space access. The locking for pcicfgrw*() is now
done by the caller (only port/pci.c).

Device drivers now need to include "../port/pci.h" in
addition to "io.h".

The new code now checks bridge windows and membars,
while enumerating the bus, giving the pc driver a chance
to re-assign them. This is needed because some UEFI
implementations fail to assign the bars for some devices,
so we need to do it outselfs. (See pcireservemem()).

While working on this, it was discovered that the pci
code assimed the smallest I/O bar size is 16 (pcibarsize()),
which is wrong. I/O bars can be as small as 4 bytes.
Bit 1 in an I/O bar is also reserved and should be masked off,
making the port mask: port = bar & ~3;
2020-09-13 20:33:17 +02:00

919 lines
17 KiB
C

/*
* intel 10GB ethernet pci-express driver
* 6.0.0: net 02.00.00 8086/15c8 11 0:dfc0000c 2097152 4:dfe0400c 16384
* Intel Corporation Ethernet Connection X553/X550-AT 10GBASE-T
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/pci.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"
enum {
/* general */
Ctrl = 0x00000/4, /* Device Control */
Status = 0x00008/4, /* Device Status */
Ctrlext = 0x00018/4, /* Extended Device Control */
Tcptimer = 0x0004c/4, /* tcp timer */
/* nvm */
Eec = 0x10010/4, /* eeprom/flash control */
Eemngctl = 0x10110/4, /* Manageability EEPROM-Mode Control */
/* interrupt */
Icr = 0x00800/4, /* interrupt cause read */
Ics = 0x00808/4, /* " set */
Ims = 0x00880/4, /* " mask read/set */
Imc = 0x00888/4, /* " mask clear */
Iac = 0x00810/4, /* " auto clear */
Iam = 0x00890/4, /* " auto mask enable */
Itr = 0x00820/4, /* " throttling rate (0-19) */
Ivar = 0x00900/4, /* " vector allocation regs. */
/* rx dma */
Rdbal = 0x01000/4, /* rx desc base low (0-63) +0x40n */
Rdbah = 0x01004/4, /* " high */
Rdlen = 0x01008/4, /* " length */
Rdh = 0x01010/4, /* " head */
Rdt = 0x01018/4, /* " tail */
Rxdctl = 0x01028/4, /* " control */
Srrctl = 0x02100/4, /* split and replication rx ctl. */
Rdrxctl = 0x02f00/4, /* rx dma control */
Rxpbsize = 0x03c00/4, /* rx packet buffer size */
Rxctrl = 0x03000/4, /* rx control */
/* rx */
Rxcsum = 0x05000/4, /* rx checksum control */
Mcstctrl = 0x05090/4, /* multicast control register */
Mta = 0x05200/4, /* multicast table array (0-127) */
Ral = 0x05400/4, /* rx address low */
Rah = 0x05404/4,
Vfta = 0x0a000/4, /* vlan filter table array. */
Fctrl = 0x05080/4, /* filter control */
/* tx */
Tdbal = 0x06000/4, /* tx desc base low +0x40n */
Tdbah = 0x06004/4, /* " high */
Tdlen = 0x06008/4, /* " len */
Tdh = 0x06010/4, /* " head */
Tdt = 0x06018/4, /* " tail */
Txdctl = 0x06028/4, /* " control */
Dmatxctl = 0x04a80/4,
/* mac */
Hlreg0 = 0x04240/4, /* highlander control reg 0 */
Hlreg1 = 0x04244/4, /* highlander control reg 1 (ro) */
Maxfrs = 0x04268/4, /* max frame size */
Links = 0x042a4/4, /* link status */
};
enum {
/* Ctrl */
Rst = 1<<26, /* full nic reset */
/* Ctrlext */
Drvload = 1<<28, /* Driver Load */
/* Eec */
AutoRd = 1<<9, /* NVM auto read done */
/* Eemngctl */
CfgDone0 = 1<<18, /* Configuration Done Port 0 */
CfgDone1 = 1<<19, /* Configuration Done Port 1 */
/* Txdctl */
Pthresh = 0, /* prefresh threshold shift in bits */
Hthresh = 8, /* host buffer minimum threshold */
Wthresh = 16, /* writeback threshold */
Ten = 1<<25,
/* Fctrl */
Bam = 1<<10, /* broadcast accept mode */
Upe = 1<<9, /* unicast promiscuous */
Mpe = 1<<8, /* multicast promiscuous */
/* Rxdctl */
Renable = 1<<25,
/* Dmatxctl */
Txen = 1<<0,
/* Rxctl */
Rxen = 1<<0,
/* Rdrxctl */
Dmaidone = 1<<3,
/* Rxcsum */
Ippcse = 1<<12, /* ip payload checksum enable */
/* Mcstctrl */
Mo = 0, /* multicast offset 47:36 */
Mfe = 1<<2, /* multicast filter enable */
/* Rah */
Av = 1<<31,
/* interrupts */
Irx0 = 1<<0, /* driver defined - rx interrupt */
Itx0 = 1<<1, /* driver defined - tx interrupt */
Lsc = 1<<20, /* link status change */
/* Ivar Interrupt Vector Allocation Register */
Intalloc0 = 0, /* Map the 0th queue Rx interrupt to the 0th bit of EICR register */
Intallocval0 = 1<<7,
intalloc1 = 1<<8, /* Map the 0th queue Tx interrupt to the 1st bit of EICR register */
Intallocval1 = 1<<15,
/* Links */
Lnkup = 1<<30,
Lnkspd = 1<<29,
/* Hlreg0 */
Jumboen = 1<<2,
};
typedef struct {
uint reg;
char *name;
} Stat;
static
Stat stattab[] = {
0x4000, "crc error",
0x4004, "illegal byte",
0x4008, "short packet",
0x3fa0, "missed pkt0",
0x4034, "mac local flt",
0x4038, "mac rmt flt",
0x4040, "rx length err",
0x405c, "rx 040",
0x4060, "rx 07f",
0x4064, "rx 100",
0x4068, "rx 200",
0x406c, "rx 3ff",
0x4070, "rx big",
0x4074, "rx ok",
0x4078, "rx bcast",
0x407c, "rx mcast",
0x4080, "tx ok",
0x40a4, "rx runt",
0x40a8, "rx frag",
0x40ac, "rx ovrsz",
0x40b0, "rx jab",
0x40d0, "rx pkt",
0x40d4, "tx pkt",
0x40d8, "tx 040",
0x40dc, "tx 07f",
0x40e0, "tx 100",
0x40e4, "tx 200",
0x40e8, "tx 3ff",
0x40ec, "tx big",
0x40f0, "tx mcast",
0x40f4, "tx bcast",
0x4120, "xsum err",
};
/* status */
enum {
Pif = 1<<7, /* past exact filter (sic) */
Ipcs = 1<<6, /* ip checksum calcuated */
L4cs = 1<<5, /* layer 2 */
Udpcs = 1<<4, /* udp checksum calcuated */
Vp = 1<<3, /* 802.1q packet matched vet */
Reop = 1<<1, /* end of packet */
Rdd = 1<<0, /* descriptor done */
};
typedef struct {
u32int addr[2];
ushort length;
ushort cksum;
uchar status;
uchar errors;
ushort vlan;
} Rd;
enum {
/* Td cmd */
Rs = 1<<3,
Ic = 1<<2,
Ifcs = 1<<1,
Teop = 1<<0,
/* Td status */
Tdd = 1<<0,
};
typedef struct {
u32int addr[2];
u16int length;
uchar cso;
uchar cmd;
uchar status;
uchar css;
ushort vlan;
} Td;
enum {
Factive = 1<<0,
Fstarted = 1<<1,
};
typedef struct {
Pcidev *p;
Ether *edev;
uintptr io;
u32int *reg;
u32int *regmsi;
uchar flag;
int nrd;
int ntd;
int rbsz;
Lock slock;
Lock alock;
QLock tlock;
Rendez lrendez;
Rendez trendez;
Rendez rrendez;
uint im;
uint lim;
uint rim;
uint tim;
Lock imlock;
char *alloc;
Rd *rdba;
Block **rb;
uint rdt;
uint rdfree;
Td *tdba;
uint tdh;
uint tdt;
Block **tb;
uchar ra[Eaddrlen];
u32int mta[128];
ulong stats[nelem(stattab)];
uint speeds[3];
} Ctlr;
/* tweakable paramaters */
enum {
Mtu = 12*1024,
Nrd = 256,
Ntd = 256,
Nrb = 256,
};
static Ctlr *ctlrtab[4];
static int nctlr;
static void
readstats(Ctlr *c)
{
int i;
lock(&c->slock);
for(i = 0; i < nelem(c->stats); i++)
c->stats[i] += c->reg[stattab[i].reg >> 2];
unlock(&c->slock);
}
static int speedtab[] = {
0,
1000,
10000,
};
static long
ifstat(Ether *e, void *a, long n, ulong offset)
{
uint i, *t;
char *s, *p, *q;
Ctlr *c;
p = s = smalloc(READSTR);
q = p + READSTR;
c = e->ctlr;
readstats(c);
for(i = 0; i < nelem(stattab); i++)
if(c->stats[i] > 0)
p = seprint(p, q, "%.10s %uld\n", stattab[i].name,
c->stats[i]);
t = c->speeds;
p = seprint(p, q, "speeds: 0:%d 1000:%d 10000:%d\n", t[0], t[1], t[2]);
seprint(p, q, "rdfree %d rdh %d rdt %d\n", c->rdfree, c->reg[Rdt],
c->reg[Rdh]);
n = readstr(offset, a, n, s);
free(s);
return n;
}
static void
im(Ctlr *c, int i)
{
ilock(&c->imlock);
c->im |= i;
c->reg[Ims] = c->im;
iunlock(&c->imlock);
}
static int
lim(void *v)
{
return ((Ctlr*)v)->lim != 0;
}
static void
lproc(void *v)
{
int r, i;
Ctlr *c;
Ether *e;
e = v;
c = e->ctlr;
while(waserror())
;
for (;;) {
r = c->reg[Links];
e->link = (r & Lnkup) != 0;
i = 0;
if(e->link)
i = 1 + ((r & Lnkspd) != 0);
c->speeds[i]++;
e->mbps = speedtab[i];
c->lim = 0;
im(c, Lsc);
sleep(&c->lrendez, lim, c);
c->lim = 0;
}
}
static long
ctl(Ether *, void *, long)
{
error(Ebadarg);
return -1;
}
#define Next(x, m) (((x)+1) & (m))
static int
cleanup(Ctlr *c, int tdh)
{
Block *b;
uint m, n;
m = c->ntd - 1;
while(c->tdba[n = Next(tdh, m)].status & Tdd){
tdh = n;
b = c->tb[tdh];
c->tb[tdh] = 0;
freeb(b);
c->tdba[tdh].status = 0;
}
return tdh;
}
static void
transmit(Ether *e)
{
uint i, m, tdt, tdh;
Ctlr *c;
Block *b;
Td *t;
c = e->ctlr;
if(!canqlock(&c->tlock)){
im(c, Itx0);
return;
}
tdh = c->tdh = cleanup(c, c->tdh);
tdt = c->tdt;
m = c->ntd - 1;
for(i = 0; i < 8; i++){
if(Next(tdt, m) == tdh){
im(c, Itx0);
break;
}
if(!(b = qget(e->oq)))
break;
t = c->tdba + tdt;
t->addr[0] = PCIWADDR(b->rp);
t->length = BLEN(b);
t->cmd = Rs | Ifcs | Teop;
c->tb[tdt] = b;
tdt = Next(tdt, m);
}
if(i){
c->tdt = tdt;
c->reg[Tdt] = tdt;
}
qunlock(&c->tlock);
}
static int
tim(void *c)
{
return ((Ctlr*)c)->tim != 0;
}
static void
tproc(void *v)
{
Ctlr *c;
Ether *e;
e = v;
c = e->ctlr;
while(waserror())
;
for (;;) {
sleep(&c->trendez, tim, c); /* transmit kicks us */
c->tim = 0;
transmit(e);
}
}
static void
rxinit(Ctlr *c)
{
int i;
Block *b;
c->reg[Rxctrl] &= ~Rxen;
/* Pg 144 Step 2
Receive buffers of appropriate size should be allocated
and pointers to these buffers should be stored in the
descriptor ring - replinish() does this? */
for(i = 0; i < c->nrd; i++){
b = c->rb[i];
c->rb[i] = 0;
if(b)
freeb(b);
}
c->rdfree = 0;
c->reg[Fctrl] |= Bam;
c->reg[Rxcsum] |= Ippcse;
c->reg[Srrctl] = c->rbsz / 1024;
c->reg[Maxfrs] = c->rbsz << 16;
c->reg[Hlreg0] |= Jumboen;
c->reg[Rdbal] = PCIWADDR(c->rdba);
c->reg[Rdbah] = 0;
c->reg[Rdlen] = c->nrd*sizeof(Rd);
c->reg[Rdh] = 0;
c->reg[Rdt] = c->rdt = 0;
c->reg[Rxdctl] = Renable;
while((c->reg[Rxdctl] & Renable) == 0)
;
/* TODO? bump the tail pointer RDT to enable descriptors
fetching by setting it to the ring length minus 1. Pg 145 */
c->reg[Rxctrl] |= Rxen;
}
static void
replenish(Ctlr *c, uint rdh)
{
int rdt, m, i;
Block *b;
Rd *r;
m = c->nrd - 1;
i = 0;
for(rdt = c->rdt; Next(rdt, m) != rdh; rdt = Next(rdt, m)){
b = allocb(c->rbsz+BY2PG);
b->rp = (uchar*)PGROUND((uintptr)b->base);
b->wp = b->rp;
c->rb[rdt] = b;
r = c->rdba + rdt;
r->addr[0] = PCIWADDR(b->rp);
r->status = 0;
c->rdfree++;
i++;
}
if(i)
c->reg[Rdt] = c->rdt = rdt;
}
static int
rim(void *v)
{
return ((Ctlr*)v)->rim != 0;
}
static uchar zeroea[Eaddrlen];
static void
rproc(void *v)
{
uint m, rdh;
Block *b;
Ctlr *c;
Ether *e;
Rd *r;
e = v;
c = e->ctlr;
m = c->nrd - 1;
rdh = 0;
while(waserror())
;
loop:
replenish(c, rdh);
im(c, Irx0);
sleep(&c->rrendez, rim, c);
loop1:
c->rim = 0;
if(c->nrd - c->rdfree >= 16)
replenish(c, rdh);
r = c->rdba + rdh;
if(!(r->status & Rdd))
goto loop; /* UGH */
b = c->rb[rdh];
c->rb[rdh] = 0;
b->wp += r->length;
if((r->status & 1)){
if(r->status & Ipcs)
b->flag |= Bipck;
b->checksum = r->cksum;
}
// r->status = 0;
etheriq(e, b);
c->rdfree--;
rdh = Next(rdh, m);
goto loop1; /* UGH */
}
static void
promiscuous(void *a, int on)
{
Ctlr *c;
Ether *e;
e = a;
c = e->ctlr;
if(on)
c->reg[Fctrl] |= Upe | Mpe;
else
c->reg[Fctrl] &= ~(Upe | Mpe);
}
static void
multicast(void *a, uchar *ea, int on)
{
int b, i;
Ctlr *c;
Ether *e;
e = a;
c = e->ctlr;
/*
* multiple ether addresses can hash to the same filter bit,
* so it's never safe to clear a filter bit.
* if we want to clear filter bits, we need to keep track of
* all the multicast addresses in use, clear all the filter bits,
* then set the ones corresponding to in-use addresses.
*
* Extracts the 12 bits, from a multicast address, to determine which
* bit-vector to set in the multicast table. The hardware uses 12 bits, from
* incoming rx multicast addresses, to determine the bit-vector to check in
* the MTA. Which of the 4 combination, of 12-bits, the hardware uses is set
* by the MO field of the MCSTCTRL. The MO field is set during initialization
* to mc_filter_type.
*
* The MTA is a register array of 128 32-bit registers. It is treated
* like an array of 4096 bits. We want to set bit
* BitArray[vector_value]. So we figure out what register the bit is
* in, read it, OR in the new bit, then write back the new value. The
* register is determined by the upper 7 bits of the vector value and
* the bit within that register are determined by the lower 5 bits of
* the value.
*
* when Mcstctrl.Mo == 0, use bits [47:36] of the address
* register index = bits [47:41]
* which bit in the above register = bits [40:36]
*/
i = ea[5] >> 1; /* register index = 47:41 (7 bits) */
b = (ea[5]&1)<<4 | ea[4]>>4; /* which bit in the above register = 40:36 (5 bits) */
b = 1 << b;
if(on)
c->mta[i] |= b;
// else
// c->mta[i] &= ~b;
c->reg[Mta+i] = c->mta[i];
c->reg[Mcstctrl] = Mfe;
/* for(i = 0; i < 128; i++) c->reg[Mta + i] = -1; brute force it to work for testing */
}
static int
detach(Ctlr *c)
{
int i;
u32int l, h;
l = c->reg[Ral];
h = c->reg[Rah];
if (h & Av) {
c->ra[0] = l & 0xFF;
c->ra[1] = l>>8 & 0xFF;
c->ra[2] = l>>16 & 0xFF;
c->ra[3] = l>>24 & 0xFF;
c->ra[4] = h & 0xFF;
c->ra[5] = h>>8 & 0xFF;
}
c->reg[Imc] = ~0;
c->reg[Ctrl] |= Rst;
for(i = 0; i < 100; i++){
delay(1);
if((c->reg[Ctrl] & Rst) == 0)
break;
}
if (i >= 100)
return -1;
delay(10);
/* not cleared by reset; kill it manually. */
for(i = 1; i < 16; i++)
c->reg[Rah + i] &= ~(1 << 31);
for(i = 0; i < 128; i++)
c->reg[Mta + i] = 0;
for(i = 1; i < 640; i++)
c->reg[Vfta + i] = 0;
c->reg[Ctrlext] &= ~Drvload; /* driver works without this */
return 0;
}
static void
shutdown(Ether *e)
{
detach(e->ctlr);
}
static int
reset(Ctlr *c)
{
int i;
while((c->reg[Eec] & AutoRd) == 0)
;
while((c->reg[Eemngctl] & CfgDone0) == 0)
;
while((c->reg[Eemngctl] & CfgDone1) == 0)
;
while((c->reg[Rdrxctl] & Dmaidone) == 0)
;
if(detach(c)){
print("iX550: reset timeout\n");
return -1;
}
while((c->reg[Eec] & AutoRd) == 0)
;
while((c->reg[Eemngctl] & CfgDone0) == 0)
;
while((c->reg[Eemngctl] & CfgDone1) == 0)
;
while((c->reg[Rdrxctl] & Dmaidone) == 0)
;
readstats(c);
for(i = 0; i<nelem(c->stats); i++)
c->stats[i] = 0;
/* configure interrupt mapping */
c->reg[Ivar] = Intalloc0 | Intallocval0 | intalloc1 | Intallocval1;
/* interrupt throttling goes here. */
for(i = Itr; i < Itr + 20; i++)
c->reg[i] = 1<<3; /* 1 interval */
return 0;
}
static void
txinit(Ctlr *c)
{
Block *b;
int i;
c->reg[Txdctl] = 16<<Wthresh | 16<<Pthresh;
for(i = 0; i < c->ntd; i++){
b = c->tb[i];
c->tb[i] = 0;
if(b)
freeb(b);
}
memset(c->tdba, 0, c->ntd * sizeof(Td));
c->reg[Tdbal] = PCIWADDR(c->tdba);
c->reg[Tdbah] = 0;
c->reg[Tdlen] = c->ntd*sizeof(Td);
c->reg[Tdh] = 0;
c->reg[Tdt] = 0;
c->tdh = c->ntd - 1;
c->tdt = 0;
c->reg[Txdctl] |= Ten;
c->reg[Dmatxctl] |= Txen;
}
static void
attach(Ether *e)
{
Ctlr *c;
int t;
char buf[KNAMELEN];
c = e->ctlr;
c->edev = e; /* point back to Ether* */
lock(&c->alock);
if(c->alloc){
unlock(&c->alock);
return;
}
c->nrd = Nrd;
c->ntd = Ntd;
t = c->nrd * sizeof *c->rdba + 255;
t += c->ntd * sizeof *c->tdba + 255;
t += (c->ntd + c->nrd) * sizeof(Block*);
c->alloc = malloc(t);
unlock(&c->alock);
if(c->alloc == nil)
error(Enomem);
c->rdba = (Rd*)ROUNDUP((uintptr)c->alloc, 256);
c->tdba = (Td*)ROUNDUP((uintptr)(c->rdba + c->nrd), 256);
c->rb = (Block**)(c->tdba + c->ntd);
c->tb = (Block**)(c->rb + c->nrd);
rxinit(c);
txinit(c);
c->reg[Ctrlext] |= Drvload; /* driver works without this */
snprint(buf, sizeof buf, "#l%dl", e->ctlrno);
kproc(buf, lproc, e);
snprint(buf, sizeof buf, "#l%dr", e->ctlrno);
kproc(buf, rproc, e);
snprint(buf, sizeof buf, "#l%dt", e->ctlrno);
kproc(buf, tproc, e);
}
static void
interrupt(Ureg*, void *v)
{
int icr, im;
Ctlr *c;
Ether *e;
e = v;
c = e->ctlr;
ilock(&c->imlock);
c->reg[Imc] = ~0;
im = c->im;
while((icr = c->reg[Icr] & c->im) != 0){
if(icr & Lsc){
im &= ~Lsc;
c->lim = icr & Lsc;
wakeup(&c->lrendez);
}
if(icr & Irx0){
im &= ~Irx0;
c->rim = icr & Irx0;
wakeup(&c->rrendez);
}
if(icr & Itx0){
im &= ~Itx0;
c->tim = icr & Itx0;
wakeup(&c->trendez);
}
}
c->reg[Ims] = c->im = im;
iunlock(&c->imlock);
}
static void
scan(void)
{
uvlong io, iomsi;
void *mem, *memmsi;
int pciregs, pcimsix;
Ctlr *c;
Pcidev *p;
p = 0;
while(p = pcimatch(p, 0x8086, 0x15c8)){ /* X553/X550-AT 10GBASE-T */
pcimsix = 4;
pciregs = 0;
if((p->mem[pciregs].bar & 1) != 0
|| (p->mem[pcimsix].bar & 1) != 0)
continue;
if(nctlr == nelem(ctlrtab)){
print("iX550: too many controllers\n");
return;
}
c = malloc(sizeof *c);
if(c == nil){
print("iX550: can't allocate memory\n");
continue;
}
io = p->mem[pciregs].bar & ~0xF;
mem = vmap(io, p->mem[pciregs].size);
if(mem == nil){
print("iX550: can't map regs %llux\n", io);
free(c);
continue;
}
iomsi = p->mem[pcimsix].bar & ~0xF;
memmsi = vmap(iomsi, p->mem[pcimsix].size);
if(memmsi == nil){
print("iX550: can't map msi-x regs %llux\n", iomsi);
vunmap(mem, p->mem[pciregs].size);
free(c);
continue;
}
pcienable(p);
c->p = p;
c->io = io;
c->reg = (u32int*)mem;
c->regmsi = (u32int*)memmsi;
c->rbsz = ROUND(Mtu, 1024);
if(reset(c)){
print("iX550: can't reset\n");
vunmap(mem, p->mem[pciregs].size);
vunmap(memmsi, p->mem[pcimsix].size);
free(c);
continue;
}
pcisetbme(p);
ctlrtab[nctlr++] = c;
}
}
static int
pnp(Ether *e)
{
static uchar zeros[Eaddrlen];
int i;
Ctlr *c = nil;
uchar *p;
if(nctlr == 0)
scan();
for(i = 0; i < nctlr; i++){
c = ctlrtab[i];
if(c == nil || c->flag & Factive)
continue;
if(e->port == 0 || e->port == c->io)
break;
}
if (i >= nctlr)
return -1;
if(memcmp(c->ra, zeros, Eaddrlen) != 0)
memmove(e->ea, c->ra, Eaddrlen);
p = e->ea;
c->reg[Ral] = p[3]<<24 | p[2]<<16 | p[1]<<8 | p[0];
c->reg[Rah] = p[5]<<8 | p[4] | 1<<31;
c->flag |= Factive;
e->ctlr = c;
e->port = (uintptr)c->reg;
e->irq = c->p->intl;
e->tbdf = c->p->tbdf;
e->mbps = 10000;
e->maxmtu = Mtu;
e->arg = e;
e->attach = attach;
e->ctl = ctl;
e->ifstat = ifstat;
e->multicast = multicast;
e->promiscuous = promiscuous;
e->shutdown = shutdown;
e->transmit = transmit;
intrenable(e->irq, interrupt, e, e->tbdf, e->name);
return 0;
}
void
etherx550link(void)
{
addethercard("iX550", pnp);
}