/*
* Intel PRO/Wireless 2200BG/2225BG/2915ABG driver.
*
* Written using Damien Bergamini's OpenBSD iwi(4) driver for reference.
* Requires firmware in /lib/firmware/iwi-bss on attach.
*
* Notes:
* - Only BSS mode is supported.
* - From what I can tell, OpenBSD DELAY() is in microseconds,
* so I have used microdelay here but other ported drivers (e.g etheriwl.c)
* use delay. I'm not sure which is correct.
*/
#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"
#include "../port/wifi.h"
enum {
Ntx = 64,
Nrx = 32,
Ncmd = 16,
Rbufsize = 4*1024,
Rdscsize = 4,
Tdscsize = 138,
Tcmdsize = 128,
};
enum {
Intr = 0x0008,
Irx = 0x00000002,
Icmd = 0x00000800,
Itx1 = 0x00001000,
Itx2 = 0x00002000,
Itx3 = 0x00004000,
Itx4 = 0x00008000,
Ifwinit = 0x01000000,
Ifatal = 0x40000000,
Iparity = 0x80000000,
IntrMask = 0x00c,
Idefmask = Irx | Icmd | Itx1 | Itx2 | Itx3 | Itx4 | Ifwinit | Ifatal | Iparity,
IndirAddr = 0x0010,
IndirData = 0x0014,
AutoincAddr = 0x0018,
AutoincData = 0x001c,
Reset = 0x0020,
ResetPrinceton = 0x001,
ResetSW = 0x0080,
MasterDisabled = 0x0100,
StopMaster = 0x0200,
Ctl = 0x0024,
ClockReady = 0x0001,
AllowStandby = 0x0002,
Init = 0x0004,
Io = 0x0030,
RadioOn = 0x10000,
CmdBase = 0x200,
CmdSize = 0x204,
#define TxBase(i) (0x208 + (i) * 8)
#define TxSize(i) (0x20c + (i) * 8)
CmdRidx = 0x280,
#define TxRidx(i) (0x284 + (i) * 4)
RxRidx = 0x2a0,
#define RxBase(i) (0x500 + (i) * 4)
Tab0Base = 0x700,
Tab0Size = 0x704,
NodeBase = 0x0c0c,
CmdWidx = 0xf80,
#define TxWidx(i) (0xf84 + (i) * 4)
RxWidx = 0xfa0,
ReadInt = 0xff4,
InitHost = 0x20000000,
};
enum {
EEctlC = (1<<0),
EEctlS = (1<<1),
EEctlD = (1<<2),
EEctlQ = (1<<4),
EEctlshiftD = 2,
EEctlshiftQ = 4,
EEmac = 0x21,
};
typedef struct FWSect FWSect;
typedef struct FWImage FWImage;
typedef struct Ring Ring;
typedef struct TxRing TxRing;
typedef struct RxRing RxRing;
typedef struct CmdRing CmdRing;
typedef struct Ctlr Ctlr;
struct FWSect {
usize size;
uchar *data;
};
struct FWImage {
uint major, minor;
FWSect boot, ucode, main;
};
struct Ring {
QLock;
Rendez;
uint i, n;
u32int widx;
u32int ridx;
};
struct TxRing {
Ring;
uchar *d;
Block **b;
};
struct RxRing {
Ring;
u32int *p;
Block **b;
};
struct CmdRing {
Ring;
uchar *d;
};
struct Ctlr {
Lock;
QLock;
Ctlr *link;
uvlong port;
Pcidev *pdev;
Ether *edev;
Wifi *wifi;
int power;
int broken;
int attached;
uchar *mem;
int bcastnodeid;
int bssnodeid;
uchar bssid[Eaddrlen];
int channel;
int prom;
int aid;
TxRing tx[4];
RxRing rx;
CmdRing cmd;
struct {
Rendez;
u32int m, w;
} wait;
FWImage *fw;
};
#define csr32(c, r) (*(u32int *)&(c)->mem[r])
#define csr16(c, r) (*(u16int *)&(c)->mem[r])
#define csr8(c, r) (*(u8int *)&(c)->mem[r])
#define get32(p) (*((u32int*)p))
#define get16(p) (*((u16int*)p))
#define get8(p) (*((u8int*)p))
u32int
mem32r(Ctlr *ctlr, u32int addr)
{
csr32(ctlr, IndirAddr) = addr;
return csr32(ctlr, IndirData);
}
void
mem32w(Ctlr *ctlr, u32int addr, u32int val)
{
csr32(ctlr, IndirAddr) = addr;
csr32(ctlr, IndirData) = val;
}
void
mem16w(Ctlr *ctlr, u32int addr, u16int val)
{
csr32(ctlr, IndirAddr) = addr;
csr16(ctlr, IndirData) = val;
}
u8int
mem8r(Ctlr *ctlr, u32int addr)
{
csr32(ctlr, IndirAddr) = addr;
return csr8(ctlr, IndirData);
}
void
mem8w(Ctlr *ctlr, u32int addr, u8int val)
{
csr32(ctlr, IndirAddr) = addr;
csr8(ctlr, IndirData) = val;
}
static char*
stopmaster(Ctlr *ctlr)
{
int i;
/* wait for master to stop */
csr32(ctlr, Reset) = StopMaster;
for(i=0;i<5;i++) {
if(csr32(ctlr, Reset) & MasterDisabled)
break;
microdelay(10);
}
if(i==5)
return "timeout waiting for master";
csr32(ctlr, Reset) |= ResetPrinceton;
return nil;
}
static char*
clockwait(Ctlr *ctlr)
{
int i;
/* wait for clock stabilization */
for(i=0;i<1000;i++) {
if(csr32(ctlr, Ctl) & ClockReady)
break;
microdelay(200);
}
if(i==1000)
return "timeout waiting for clock stabilization";
return nil;
}
static char*
poweron(Ctlr *ctlr)
{
int i;
char *err;
if(ctlr->power)
return nil;
/* disable interrupts */
csr32(ctlr, IntrMask) = 0;
if(err = stopmaster(ctlr))
return err;
/* move adapter to d0 power state */
csr32(ctlr, Ctl) |= Init;
csr32(ctlr, ReadInt) |= InitHost;
if(err = clockwait(ctlr))
return err;
csr32(ctlr, Reset) |= ResetSW;
microdelay(10);
csr32(ctlr, Ctl) |= Init;
/* clear memory */
csr32(ctlr, AutoincAddr) = 0;
for(i=0;i<0xc000;i++)
csr32(ctlr, AutoincData) = 0;
ctlr->power = 1;
return nil;
}
static void
poweroff(Ctlr *ctlr)
{
/* disable interrupts and stop master */
csr32(ctlr, IntrMask) = 0;
stopmaster(ctlr);
/* software reset */
csr32(ctlr, Reset) = ResetSW;
ctlr->power = 0;
}
static char*
crackfw(FWImage *fw, uchar *data, uint size)
{
uchar *p, *e;
if(size < 4+4*3) {
Tooshort:
return "firmware image too short";
}
p = data + 2;
e = p+size;
fw->major = get8(p); p += 1;
fw->minor = get8(p); p += 1;
fw->boot.size = get32(p); p += 4;
fw->ucode.size = get32(p); p += 4;
fw->main.size = get32(p); p += 4;
fw->boot.data = p; p += fw->boot.size;
fw->ucode.data = p; p += fw->ucode.size;
fw->main.data = p; p += fw->main.size;
if(fw->major < 3)
return "firmware image invalid: need at least version 3.0\n";
if(fw->boot.size == 0 || fw->ucode.size == 0 || fw->main.size == 0)
return "firmware image invalid: section with zero size\n";
if(p > e)
goto Tooshort;
return nil;
}
static FWImage*
readfirmware(void)
{
uchar dirbuf[sizeof(Dir)+100], *data;
FWImage *fw;
int n, r;
char *err;
Chan *c;
Dir d;
if(!iseve())
error(Eperm);
if(!waserror()) {
c = namec("/boot/iwi-bss", Aopen, OREAD, 0);
poperror();
} else {
c = namec("/lib/firmware/iwi-bss", Aopen, OREAD, 0);
}
if(waserror()) {
cclose(c);
nexterror();
}
n = devtab[c->type]->stat(c, dirbuf, sizeof(dirbuf));
if(n <= 0)
error("can't stat firmware");
convM2D(dirbuf, n, &d, nil);
fw = malloc(sizeof(FWImage));
data = malloc(d.length);
if(waserror()) {
free(fw);
free(data);
nexterror();
}
r = 0;
while(r < d.length) {
n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
if(n <= 0)
break;
r += n;
}
if(err = crackfw(fw, data, r))
error(err);
poperror();
poperror();
cclose(c);
return fw;
}
static int
gotirq(void *arg)
{
Ctlr *ctlr = arg;
return (ctlr->wait.m & ctlr->wait.w) != 0;
}
static u32int
irqwait(Ctlr *ctlr, u32int mask, int timeout)
{
u32int r;
ilock(ctlr);
r = ctlr->wait.m & mask;
if(r == 0) {
ctlr->wait.w = mask;
iunlock(ctlr);
if(!waserror()) {
tsleep(&ctlr->wait, gotirq, ctlr, timeout);
poperror();
}
ilock(ctlr);
ctlr->wait.w = 0;
r = ctlr->wait.m & mask;
}
ctlr->wait.m &= ~r;
iunlock(ctlr);
return r;
}
static char*
loadsection(Ctlr *ctlr, FWSect *sect)
{
int i;
uchar *dma;
uchar *p, *end;
u32int sentinel, ctl, src, dst, sum, len, mlen;
/* allocate aligned dma memory */
dma = mallocalign(sect->size, BY2PG, 0, 0);
if(!dma)
return "no memory for dma";
memmove(dma, sect->data, sect->size);
coherence();
/* tell the adapter where we will store the command blocks */
mem32w(ctlr, 0x3000a0, 0x27000);
/*
* store command blocks in the adapter's internal memory.
* the adapter will use the information in them to load the
* firmware from dma memory
*/
p = dma;
end = p + sect->size;
src = PCIWADDR(p);
csr32(ctlr, AutoincAddr) = 0x27000;
while(p < end) {
dst = get32(p); p += 4; src += 4;
len = get32(p); p += 4; src += 4;
p += len;
while(len > 0) {
mlen = MIN(len, 8191);
ctl = 0x8cea0000 | mlen;
sum = ctl ^ src ^ dst;
/* write a command block */
csr32(ctlr, AutoincData) = ctl;
csr32(ctlr, AutoincData) = src;
csr32(ctlr, AutoincData) = dst;
csr32(ctlr, AutoincData) = sum;
src += mlen;
dst += mlen;
len -= mlen;
}
}
/* write sentinel block */
sentinel = csr32(ctlr, AutoincAddr);
csr32(ctlr, AutoincData) = 0;
/* reset */
csr32(ctlr, Reset) &= ~(MasterDisabled|StopMaster);
/* tell the adapter to start processing command blocks and wait */
mem32w(ctlr, 0x3000a4, 0x540100);
for(i=0;i<400;i++) {
if(mem32r(ctlr, 0x3000d0) >= sentinel)
break;
microdelay(100);
}
if(i==400) {
free(dma);
return "timeout processing command blocks";
}
/* all done */
mem32w(ctlr, 0x3000a4, 0x540c00);
/* enable interrupts and wait for the fw to init */
csr32(ctlr, Reset) = 0;
csr32(ctlr, Ctl) |= AllowStandby;
if(irqwait(ctlr, Ifwinit, 1000)) { /* 1 second timeout */
free(dma);
return "timeout waiting for firmware initialization";
}
free(dma);
return nil;
}
static char*
loaducode(Ctlr *ctlr, FWSect *sect)
{
char *err;
uchar *p, *end;
int i;
if(err = stopmaster(ctlr))
return err;
mem32w(ctlr, 0x3000e0, 0x80000000);
microdelay(5000);
csr32(ctlr, Reset) |= ~ResetPrinceton;
microdelay(5000);
mem32w(ctlr, 0x3000e0, 0);
microdelay(1000);
mem32w(ctlr, 0x00300004, 1);
microdelay(1000);
mem32w(ctlr, 0x00300004, 0);
microdelay(1000);
mem8w(ctlr, 0x200000, 0x00);
mem8w(ctlr, 0x200000, 0x40);
delay(1000);
p = sect->data;
end = sect->data + sect->size;
while(p < end) {
mem16w(ctlr, 0x200010, get16(p));
p += 2;
}
mem32w(ctlr, 0x200000, 0x00);
mem32w(ctlr, 0x200000, 0x80);
for(i=0;i<100;i++) {
if(mem8r(ctlr, 0x200000) & 1)
break;
microdelay(100);
}
if(i==100)
return "timeout waiting for ucode intialization";
/* read answer or firmware will not initialize properly */
for(i=0;i<7;i++)
mem32r(ctlr, 0x200004);
mem8w(ctlr, 0x200000, 0x00);
return nil;
}
static int
rbplant(Ctlr *ctlr, uint i)
{
Block *b;
assert(i < Nrx);
b = iallocb(Rbufsize*2);
if(b == nil)
return -1;
b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, Rbufsize);
memset(b->rp, 0, Rdscsize);
ctlr->rx.b[i] = b;
get32(ctlr->rx.p + (i<<2)) = PCIWADDR(b->rp);
return 0;
}
static char*
initrings(Ctlr *ctlr)
{
int q, i;
TxRing *tx;
RxRing *rx;
CmdRing *cmd;
cmd = &ctlr->cmd;
if(cmd->d == nil)
cmd->d = mallocalign(Tcmdsize * Ncmd, 4096, 0, 0);
if(cmd->d == nil)
return "no memory for cmd ring";
cmd->i = 0;
cmd->n = 0;
cmd->widx = CmdWidx;
cmd->ridx = CmdRidx;
rx = &ctlr->rx;
if(rx->p == nil)
rx->p = mallocalign(4 * Nrx, 4096, 0, 0);
if(rx->b == nil)
rx->b = malloc(sizeof(Block*) * Nrx);
if(rx->p == nil || rx->b == nil)
return "no memory for rx ring";
for(i=0;i<Nrx;i++) {
if(rx->b[i] != nil) {
freeb(rx->b[i]);
rx->b[i] = nil;
if(rbplant(ctlr, i) < 0)
return "no memory for rx descriptors";
}
}
rx->i = 0;
rx->widx = RxWidx;
rx->ridx = RxRidx;
for(q=0;q<nelem(ctlr->tx);q++) {
tx = &ctlr->tx[q];
if(tx->d == nil)
tx->d = mallocalign(Tdscsize * Ntx, 4096, 0, 0);
if(tx->b == nil)
tx->b = malloc(sizeof(Block*) * Ntx);
if(tx->d == nil || tx->b == nil)
return "no memory for tx ring";
for(i=0;i<Ntx;i++) {
if(tx->b[i] != nil) {
freeb(tx->b[i]);
tx->b[i] = nil;
}
}
tx->i = 0;
tx->n = 0;
tx->ridx = TxRidx(q);
tx->widx = TxWidx(q);
}
return nil;
}
static char*
reset(Ctlr *ctlr)
{
char *err;
if(ctlr->power)
poweroff(ctlr);
if(err = initrings(ctlr))
return err;
if(err = poweron(ctlr))
return err;
return nil;
}
static char*
boot(Ctlr *ctlr)
{
int i;
char *err;
FWImage *fw;
fw = ctlr->fw;
/* load boot firmware and microcode */
if(err = loadsection(ctlr, &fw->boot))
return err;
if(err = loaducode(ctlr, &fw->ucode))
return err;
/* stop and setup the ring registers */
csr32(ctlr, IntrMask) = 0;
if(err = stopmaster(ctlr))
return err;
/* setup cmd ring registers */
csr32(ctlr, CmdBase) = PCIWADDR(ctlr->cmd.d);
csr32(ctlr, CmdSize) = Ncmd;
csr32(ctlr, CmdWidx) = ctlr->cmd.i;
/* setup tx ring registers */
for(i=0;i<sizeof(ctlr->tx);i++) {
csr32(ctlr, TxBase(i)) = PCIWADDR(ctlr->tx[i].d);
csr32(ctlr, TxSize(i)) = Ntx;
csr32(ctlr, TxWidx(i)) = ctlr->tx[i].i;
}
/* setup rx ring registers */
for(i=0;i<Nrx;i++)
csr32(ctlr, RxBase(i)) = (u32int) ctlr->rx.p + (i<<2);
csr32(ctlr, RxWidx) = Nrx - 1;
/* now load the main firmware */
if(err = loadsection(ctlr, &fw->main))
return err;
return nil;
}
static long
iwictl(Ether *edev, void *buf, long n)
{
Ctlr *ctlr;
ctlr = edev->ctlr;
if(n >= 5 && memcmp(buf, "reset", 5) == 0) {
ctlr->broken = 1;
return n;
}
if(ctlr->wifi != nil)
return wifictl(ctlr->wifi, buf, n);
return 0;
}
static long
iwiifstat(Ether *edev, void *buf, long n, ulong off)
{
Ctlr *ctlr;
ctlr = edev->ctlr;
if(ctlr->wifi != nil)
return wifistat(ctlr->wifi, buf, n, off);
return 0;
}
static void
iwitransmit(Wifi *wifi, Wnode *, Block *b)
{
Ether *edev;
Ctlr *ctlr;
edev = wifi->ether;
ctlr = edev->ctlr;
qlock(ctlr);
if(!ctlr->attached || ctlr->broken) {
qunlock(ctlr);
freeb(b);
return;
}
/* TODO: push this block into a tx queue */
qunlock(ctlr);
freeb(b);
}
static void
iwipromiscuous(void *arg, int on)
{
Ether *edev;
Ctlr *ctlr;
edev = arg;
ctlr = edev->ctlr;
qlock(ctlr);
ctlr->prom = on;
qunlock(ctlr);
}
static void
iwimulticast(void *, uchar *, int)
{
}
static void
iwirecover(void *arg)
{
Ether *edev;
Ctlr *ctlr;
edev = arg;
ctlr = edev->ctlr;
while(waserror()) ;
for(;;) {
tsleep(&up->sleep, return0, 0, 4000);
qlock(ctlr);
for(;;) {
if(ctlr->broken == 0)
break;
if(ctlr->power)
poweroff(ctlr);
if(reset(ctlr) != nil)
break;
if(boot(ctlr) != nil)
break;
ctlr->bcastnodeid = -1;
ctlr->bssnodeid = -1;
ctlr->aid = 0;
}
qunlock(ctlr);
}
}
static void
iwiattach(Ether *edev)
{
FWImage *fw;
Ctlr *ctlr;
char *err;
int i;
ctlr = edev->ctlr;
eqlock(ctlr);
if(waserror()) {
print("#l%d: %s\n", edev->ctlrno, up->errstr);
qunlock(ctlr);
nexterror();
}
if(!ctlr->attached) {
if(ctlr->fw == nil) {
fw = readfirmware();
print("#l%d: firmware: major %d minor %d size %lux+%lux+%lux\n",
edev->ctlrno,
fw->major, fw->minor,
fw->boot.size, fw->ucode.size, fw->main.size);
ctlr->fw = fw;
if(err = reset(ctlr))
error(err);
if(err = boot(ctlr))
error(err);
}
/* attach to the wifi system */
if(ctlr->wifi == nil)
ctlr->wifi = wifiattach(edev, iwitransmit);
/* TODO: possibly set different rates here? */
ctlr->bcastnodeid = -1;
ctlr->bssnodeid = -1;
ctlr->channel = 1;
ctlr->aid = 0;
/* setup wifi options */
for(i=0;i<edev->nopt;i++)
wificfg(ctlr->wifi, edev->opt[i]);
ctlr->attached = 1;
kproc("iwirecover", iwirecover, edev);
}
qunlock(ctlr);
poperror();
}
static void
iwiinterrupt(Ureg*, void *arg)
{
Ether *edev;
Ctlr *ctlr;
u32int r;
edev = arg;
ctlr = edev->ctlr;
ilock(ctlr);
r = csr32(ctlr, Intr);
if(r == 0 || r == 0xffffffff) {
iunlock(ctlr);
return;
}
/* disable interrupts and acknowledge */
csr32(ctlr, IntrMask) = 0;
csr32(ctlr, Intr) = r;
if(r & Ifatal) {
ctlr->broken = 1;
print("#l%d: fatal firmware error\n", edev->ctlrno);
goto done;
}
ctlr->wait.m |= r;
if(ctlr->wait.m & ctlr->wait.w)
wakeup(&ctlr->wait);
done:
csr32(ctlr, IntrMask) = Idefmask;
iunlock(ctlr);
}
static void
eepromctl(Ctlr *ctlr, u32int val)
{
mem32w(ctlr, 0x00300040, val); /* eeprom ctl */
microdelay(1); /* minimum hold time */
}
static u16int
eepromread(Ctlr *ctlr, u8int addr)
{
int n;
u16int val;
u32int tmp;
val = 0;
/* clock c once before the first command */
eepromctl(ctlr, 0);
eepromctl(ctlr, EEctlS);
eepromctl(ctlr, EEctlS | EEctlC);
eepromctl(ctlr, EEctlS);
/* write start bit (1) */
eepromctl(ctlr, EEctlS | EEctlD);
eepromctl(ctlr, EEctlS | EEctlD | EEctlC);
/* write read opcode (10) */
eepromctl(ctlr, EEctlS | EEctlD);
eepromctl(ctlr, EEctlS | EEctlD | EEctlC);
eepromctl(ctlr, EEctlS);
eepromctl(ctlr, EEctlS | EEctlC);
/* write address bits */
for(n=7;n>=0;n--) {
eepromctl(ctlr, EEctlS |
(((addr >> n) & 1) << EEctlshiftD));
eepromctl(ctlr, EEctlS |
(((addr >> n) & 1) << EEctlshiftD) | EEctlC);
}
eepromctl(ctlr, EEctlS);
/* read data bits */
for(n=15;n>=0;n--) {
eepromctl(ctlr, EEctlS | EEctlC);
eepromctl(ctlr, EEctlS);
tmp = mem32r(ctlr, 0x00300040); /* eeprom ctl */
val |= ((tmp & EEctlQ) >> EEctlshiftQ) << n;
}
eepromctl(ctlr, 0);
/* clear chip select and clock c */
eepromctl(ctlr, EEctlS);
eepromctl(ctlr, 0);
eepromctl(ctlr, EEctlC);
return val;
}
static int
iwiinit(Ether *edev)
{
Ctlr *ctlr;
char *err;
u32int val;
ctlr = edev->ctlr;
/* clear device-specific pci retry timeout register */
if(pcicfgr8(ctlr->pdev, 0x41) != 0)
pcicfgw8(ctlr->pdev, 0x41, 0);
/* restart and initialize */
poweroff(ctlr);
if(err = initrings(ctlr))
goto error;
if(err = poweron(ctlr))
goto error;
/* read our mac address from eeprom */
val = eepromread(ctlr, EEmac);
edev->ea[0] = val & 0xff;
edev->ea[1] = val >> 8;
val = eepromread(ctlr, EEmac + 1);
edev->ea[2] = val & 0xff;
edev->ea[3] = val >> 8;
val = eepromread(ctlr, EEmac + 2);
edev->ea[4] = val & 0xff;
edev->ea[5] = val >> 8;
return 0;
error:
print("iwi: %s\n", err);
return -1;
}
static void
iwishutdown(Ether *edev)
{
Ctlr *ctlr;
ctlr = edev->ctlr;
if(ctlr->power)
poweroff(ctlr);
ctlr->broken = 0;
}
static Ctlr *iwihead, *iwitail;
void
iwipci(void)
{
Pcidev *pdev;
Ctlr *ctlr;
pdev = nil;
while(pdev = pcimatch(pdev, 0, 0)) {
if(pdev->ccrb != 2 || pdev->ccru != 0x80)
continue;
if(pdev->vid != 0x8086) /* intel */
continue;
if(pdev->mem[0].bar & 1)
continue;
switch(pdev->did) {
case 0x4220: /* PRO/Wireless 2200BG */
case 0x4221: /* PRO/Wireless 2225BG */
case 0x4223: /* PRO/Wireless 2915ABG */
case 0x4224: /* PRO/Wireless 2915ABG */
break;
default:
continue;
}
ctlr = malloc(sizeof(Ctlr));
if(ctlr == nil) {
print("iwi: unable to alloc Ctlr\n");
continue;
}
ctlr->pdev = pdev;
ctlr->port = pdev->mem[0].bar & ~0xf;
ctlr->mem = vmap(ctlr->port, pdev->mem[0].size);
if(ctlr->mem == nil) {
print("iwi: couldn't map %llux\n", ctlr->port);
free(ctlr);
continue;
}
if(iwihead != nil)
iwitail->link = ctlr;
else
iwihead = ctlr;
iwitail = ctlr;
}
}
int
iwipnp(Ether *edev)
{
Ctlr *ctlr;
if(iwihead == nil)
iwipci();
again:
for(ctlr = iwihead;ctlr;ctlr=ctlr->link) {
if(ctlr->edev)
continue;
if(edev->port == 0 || edev->port == ctlr->port) {
ctlr->edev = edev;
break;
}
}
if(ctlr == nil)
return -1;
edev->ctlr = ctlr;
edev->port = ctlr->port;
edev->irq = ctlr->pdev->intl;
edev->tbdf = ctlr->pdev->tbdf;
edev->arg = edev;
edev->attach = iwiattach;
edev->ifstat = iwiifstat;
edev->ctl = iwictl;
edev->shutdown = iwishutdown;
edev->promiscuous = iwipromiscuous;
edev->multicast = iwimulticast;
edev->mbps = 54;
pcienable(ctlr->pdev);
if(iwiinit(edev) < 0) {
pcidisable(ctlr->pdev);
ctlr->edev = nil;
edev->ctlr = nil;
goto again;
}
pcisetbme(ctlr->pdev);
intrenable(edev->irq, iwiinterrupt, edev, edev->tbdf, edev->name);
return 0;
}
void
etheriwilink(void)
{
addethercard("iwi", iwipnp);
}