/* * 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;ib[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;qtx);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;ib[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;itx);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;irx.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;inopt;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); }