diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2015-06-28 18:32:54 +0200 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2015-06-28 18:32:54 +0200 |
commit | ed238e7ef822261efe3b4406e0b5c5e84ddf07fe (patch) | |
tree | d4561be5e92e22c034b84da32eb2d863c77794a7 /sys/src | |
parent | 00328b5759f66400aed0e71ccc687250113e61ee (diff) |
etherwpi: Intel PRO Wireless 3945abg driver based on openbsd's if_wpi (thanks aap)
Diffstat (limited to 'sys/src')
-rw-r--r-- | sys/src/9/pc/etherwpi.c | 1860 | ||||
-rw-r--r-- | sys/src/9/pc/pccpuf | 1 | ||||
-rw-r--r-- | sys/src/9/pc/pcf | 1 | ||||
-rw-r--r-- | sys/src/9/pc64/pc64 | 1 |
4 files changed, 1863 insertions, 0 deletions
diff --git a/sys/src/9/pc/etherwpi.c b/sys/src/9/pc/etherwpi.c new file mode 100644 index 000000000..32dbdf0d3 --- /dev/null +++ b/sys/src/9/pc/etherwpi.c @@ -0,0 +1,1860 @@ +#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" +#include "wifi.h" + +enum { + Nrxlog = 6, + Nrx = 1<<Nrxlog, + Ntx = 256, + + Rbufsize = 3*1024, + Rdscsize = 8, + + Tdscsize = 64, + Tcmdsize = 128, +}; + +/* registers */ +enum { + Cfg = 0x000, + AlmMb = 1<<8, + AlmMm = 1<<9, + SkuMrc = 1<<10, + RevD = 1<<11, + TypeB = 1<<12, + Isr = 0x008, + Imr = 0x00c, + Ialive = 1<<0, + Iwakeup = 1<<1, + Iswrx = 1<<3, + Irftoggled = 1<<7, + Iswerr = 1<<25, + Ifhtx = 1<<27, + Ihwerr = 1<<29, + Ifhrx = 1<<31, + Ierr = Iswerr | Ihwerr, + Idefmask = Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Irftoggled, + FhIsr = 0x010, + GpioIn = 0x018, + Reset = 0x020, + Nevo = 1<<0, + SW = 1<<7, + MasterDisabled = 1<<8, + StopMaster = 1<<9, + + Gpc = 0x024, + MacAccessEna = 1<<0, + MacClockReady = 1<<0, + InitDone = 1<<2, + MacAccessReq = 1<<3, + NicSleep = 1<<4, + RfKill = 1<<27, + Eeprom = 0x02c, + EepromGp = 0x030, + + UcodeGp1Clr = 0x05c, + UcodeGp1RfKill = 1<<1, + UcodeGp1CmdBlocked = 1<<2, + UcodeGp2 = 0x060, + + GioChicken = 0x100, + L1AnoL0Srx = 1<<23, + AnaPll = 0x20c, + Init = 1<<24, + + PrphWaddr = 0x444, + PrphRaddr = 0x448, + PrphWdata = 0x44c, + PrphRdata = 0x450, + HbusTargWptr = 0x460, +}; + +/* + * Flow-Handler registers. + */ +enum { + FhCbbcCtrl = 0x940, + FhCbbcBase = 0x944, + FhRxConfig = 0xc00, + FhRxConfigDmaEna = 1<<31, + FhRxConfigRdrbdEna = 1<<29, + FhRxConfigWrstatusEna = 1<<27, + FhRxConfigMaxfrag = 1<<24, + FhRxConfigIrqDstHost = 1<<12, + + FhRxConfigNrdbShift = 20, + FhRxConfigIrqRbthShift = 4, + FhRxBase = 0xc04, + FhRxWptr = 0xc20, + FhRxRptrAddr = 0xc24, + FhRssrTbl = 0xcc0, + FhRxStatus = 0xcc4, + FhTxConfig = 0xd00, // +q*32 + FhTxBase = 0xe80, + FhMsgConfig = 0xe88, + FhTxStatus = 0xe90, +}; + +/* + * NIC internal memory offsets. + */ +enum { + AlmSchedMode = 0x2e00, + AlmSchedArastat = 0x2e04, + AlmSchedTxfact = 0x2e10, + AlmSchedTxf4mf = 0x2e14, + AlmSchedTxf5mf = 0x2e20, + AlmSchedBP1 = 0x2e2c, + AlmSchedBP2 = 0x2e30, + ApmgClkEna = 0x3004, + ApmgClkDis = 0x3008, + DmaClkRqt = 1<<9, + BsmClkRqt = 1<<11, + ApmgPs = 0x300c, + PwrSrcVMain = 0<<24, + PwrSrcMask = 3<<24, + + ApmgPciStt = 0x3010, + + BsmWrCtrl = 0x3400, + BsmWrMemSrc = 0x3404, + BsmWrMemDst = 0x3408, + BsmWrDwCount = 0x340c, + BsmDramTextAddr = 0x3490, + BsmDramTextSize = 0x3494, + BsmDramDataAddr = 0x3498, + BsmDramDataSize = 0x349c, + BsmSramBase = 0x3800, +}; + +enum { + FilterPromisc = 1<<0, + FilterCtl = 1<<1, + FilterMulticast = 1<<2, + FilterNoDecrypt = 1<<3, + FilterBSS = 1<<5, +}; + +enum { + RFlag24Ghz = 1<<0, + RFlagCCK = 1<<1, + RFlagAuto = 1<<2, + RFlagShSlot = 1<<4, + RFlagShPreamble = 1<<5, + RFlagNoDiversity = 1<<7, + RFlagAntennaA = 1<<8, + RFlagAntennaB = 1<<9, + RFlagTSF = 1<<15, +}; + +typedef struct FWSect FWSect; +typedef struct FWImage FWImage; + +typedef struct TXQ TXQ; +typedef struct RXQ RXQ; + +typedef struct Shared Shared; +typedef struct Sample Sample; +typedef struct Powergrp Powergrp; + +typedef struct Ctlr Ctlr; + +struct FWSect +{ + uchar *data; + uint size; +}; + +struct FWImage +{ + struct { + FWSect text; + FWSect data; + } init, main, boot; + + uint version; + uchar data[]; +}; + +struct TXQ +{ + uint n; + uint i; + Block **b; + uchar *d; + uchar *c; + + uint lastcmd; + + Rendez; + QLock; +}; + +struct RXQ +{ + uint i; + Block **b; + u32int *p; +}; + +struct Shared +{ + u32int txbase[8]; + u32int next; + u32int reserved[2]; +}; + +struct Sample +{ + uchar index; + char power; +}; + +struct Powergrp +{ + uchar chan; + char maxpwr; + short temp; + Sample samples[5]; +}; + +struct Ctlr { + Lock; + QLock; + + Ctlr *link; + Pcidev *pdev; + Wifi *wifi; + + int port; + int power; + int active; + int broken; + int attached; + + int temp; + u32int ie; + u32int *nic; + + /* assigned node ids in hardware node table or -1 if unassigned */ + int bcastnodeid; + int bssnodeid; + + /* current receiver settings */ + uchar bssid[Eaddrlen]; + int channel; + int prom; + int aid; + + RXQ rx; + TXQ tx[8]; + + struct { + Rendez; + u32int m; + u32int w; + } wait; + + struct { + uchar cap; + u16int rev; + uchar type; + + char regdom[4+1]; + + Powergrp pwrgrps[5]; + } eeprom; + + char maxpwr[255]; + + Shared *shared; + + FWImage *fw; +}; + +static void setled(Ctlr *ctlr, int which, int on, int off); + +#define csr32r(c, r) (*((c)->nic+((r)/4))) +#define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v)) + +static uint +get32(uchar *p){ + return *((u32int*)p); +} +static uint +get16(uchar *p) +{ + return *((u16int*)p); +} +static void +put32(uchar *p, uint v){ + *((u32int*)p) = v; +} +static void +put16(uchar *p, uint v){ + *((u16int*)p) = v; +}; + +static char* +niclock(Ctlr *ctlr) +{ + int i; + + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | MacAccessReq); + for(i=0; i<1000; i++){ + if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna) + return 0; + delay(10); + } + return "niclock: timeout"; +} + +static void +nicunlock(Ctlr *ctlr) +{ + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq); +} + +static u32int +prphread(Ctlr *ctlr, uint off) +{ + csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off); + coherence(); + return csr32r(ctlr, PrphRdata); +} +static void +prphwrite(Ctlr *ctlr, uint off, u32int data) +{ + csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off); + coherence(); + csr32w(ctlr, PrphWdata, data); +} + +static char* +eepromread(Ctlr *ctlr, void *data, int count, uint off) +{ + uchar *out = data; + char *err; + u32int w = 0; + int i; + + if((err = niclock(ctlr)) != nil) + return err; + + for(; count > 0; count -= 2, off++){ + csr32w(ctlr, Eeprom, off << 2); + csr32w(ctlr, Eeprom, csr32r(ctlr, Eeprom) & ~(1<<1)); + + for(i = 0; i < 10; i++){ + w = csr32r(ctlr, Eeprom); + if(w & 1) + break; + delay(5); + } + if(i == 10) + break; + *out++ = w >> 16; + if(count > 1) + *out++ = w >> 24; + } + nicunlock(ctlr); + + if(count > 0) + return "eeprompread: timeout"; + return nil; +} + +static char* +clockwait(Ctlr *ctlr) +{ + int i; + + /* Set "initialization complete" bit. */ + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | InitDone); + for(i=0; i<2500; i++){ + if(csr32r(ctlr, Gpc) & MacClockReady) + return nil; + delay(10); + } + return "clockwait: timeout"; +} + +static char* +poweron(Ctlr *ctlr) +{ + char *err; + + if(ctlr->power) + return nil; + + csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | Init); + /* Disable L0s. */ + csr32w(ctlr, GioChicken, csr32r(ctlr, GioChicken) | L1AnoL0Srx); + + if((err = clockwait(ctlr)) != nil) + return err; + + if((err = niclock(ctlr)) != nil) + return err; + + prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt); + delay(20); + + /* Disable L1. */ + prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11)); + + nicunlock(ctlr); + + ctlr->power = 1; + + return nil; +} + +static void +poweroff(Ctlr *ctlr) +{ + int i, j; + + csr32w(ctlr, Reset, Nevo); + + /* Disable interrupts. */ + csr32w(ctlr, Imr, 0); + csr32w(ctlr, Isr, ~0); + csr32w(ctlr, FhIsr, ~0); + + if(niclock(ctlr) == nil){ + /* Stop TX scheduler. */ + prphwrite(ctlr, AlmSchedMode, 0); + prphwrite(ctlr, AlmSchedTxfact, 0); + + /* Stop all DMA channels */ + for(i = 0; i < 6; i++){ + csr32w(ctlr, FhTxConfig + i*32, 0); + for(j = 0; j < 100; j++){ + if((csr32r(ctlr, FhTxStatus) & (0x1010000<<i)) == (0x1010000<<i)) + break; + delay(10); + } + } + nicunlock(ctlr); + } + + /* Stop RX ring. */ + if(niclock(ctlr) == nil){ + csr32w(ctlr, FhRxConfig, 0); + for(j = 0; j < 100; j++){ + if(csr32r(ctlr, FhRxStatus) & (1<<24)) + break; + delay(10); + } + nicunlock(ctlr); + } + + if(niclock(ctlr) == nil){ + prphwrite(ctlr, ApmgClkDis, DmaClkRqt); + nicunlock(ctlr); + } + delay(5); + + csr32w(ctlr, Reset, csr32r(ctlr, Reset) | StopMaster); + + if((csr32r(ctlr, Gpc) & (7<<24)) != (4<<24)){ + for(j = 0; j < 100; j++){ + if(csr32r(ctlr, Reset) & MasterDisabled) + break; + delay(10); + } + } + + csr32w(ctlr, Reset, csr32r(ctlr, Reset) | SW); + + ctlr->power = 0; +} + +static struct { + u32int addr; /* offset in EEPROM */ + u8int nchan; + u8int chan[14]; +} bands[5] = { + { 0x63, 14, + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } }, + { 0x72, 13, + { 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 } }, + { 0x80, 12, + { 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 } }, + { 0x8d, 11, + { 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 } }, + { 0x99, 6, + { 145, 149, 153, 157, 161, 165 } } +}; + +static int +wpiinit(Ether *edev) +{ + Ctlr *ctlr; + char *err; + uchar b[64]; + int i, j; + Powergrp *g; + + ctlr = edev->ctlr; + if((err = poweron(ctlr)) != nil) + goto Err; + if((csr32r(ctlr, EepromGp) & 0x6) == 0){ + err = "bad rom signature"; + goto Err; + } + /* Clear HW ownership of EEPROM. */ + csr32w(ctlr, EepromGp, csr32r(ctlr, EepromGp) & ~0x180); + + if((err = eepromread(ctlr, b, 1, 0x45)) != nil) + goto Err; + ctlr->eeprom.cap = b[0]; + if((err = eepromread(ctlr, b, 2, 0x35)) != nil) + goto Err; + ctlr->eeprom.rev = get16(b); + if((err = eepromread(ctlr, b, 1, 0x4a)) != nil) + goto Err; + ctlr->eeprom.type = b[0]; + if((err = eepromread(ctlr, b, 4, 0x60)) != nil) + goto Err; + strncpy(ctlr->eeprom.regdom, (char*)b, 4); + ctlr->eeprom.regdom[4] = '\0'; + + print("wpi: %X %X %X %s\n", ctlr->eeprom.cap, ctlr->eeprom.rev, ctlr->eeprom.type, ctlr->eeprom.regdom); + + if((err = eepromread(ctlr, b, 6, 0x15)) != nil) + goto Err; + memmove(edev->ea, b, Eaddrlen); + + for(i = 0; i < nelem(bands); i++){ + if((err = eepromread(ctlr, b, 2*bands[i].nchan, bands[i].addr)) != nil) + goto Err; + for(j = 0; j < bands[i].nchan; j++){ + if(!(b[j*2] & 1)) + continue; + ctlr->maxpwr[bands[i].chan[j]] = b[j*2+1]; + } + } + + for(i = 0; i < nelem(ctlr->eeprom.pwrgrps); i++){ + if((err = eepromread(ctlr, b, 64, 0x100 + i*32)) != nil) + goto Err; + g = &ctlr->eeprom.pwrgrps[i]; + g->maxpwr = b[60]; + g->chan = b[61]; + g->temp = get16(b+62); + print("pwrgroup %d: %d %d %d\n", i, g->chan, g->maxpwr, g->temp); + print("samples"); + for(j = 0; j < 5; j++){ + g->samples[j].index = b[j*4]; + g->samples[j].power = b[j*4+1]; + print(", %d %d", g->samples[j].index, g->samples[j].power); + } + print("\n"); + } + + poweroff(ctlr); + return 0; +Err: + print("wpiinit: %s\n", err); + poweroff(ctlr); + return -1; +} + +static char* +crackfw(FWImage *i, uchar *data, uint size) +{ + uchar *p, *e; + + memset(i, 0, sizeof(*i)); + if(size < 4*6){ +Tooshort: + return "firmware image too short"; + } + p = data; + e = p + size; + i->version = get32(p); p += 4; + i->main.text.size = get32(p); p += 4; + i->main.data.size = get32(p); p += 4; + i->init.text.size = get32(p); p += 4; + i->init.data.size = get32(p); p += 4; + i->boot.text.size = get32(p); p += 4; + i->main.text.data = p; p += i->main.text.size; + i->main.data.data = p; p += i->main.data.size; + i->init.text.data = p; p += i->init.text.size; + i->init.data.data = p; p += i->init.data.size; + i->boot.text.data = p; p += i->boot.text.size; + if(p > e) + goto Tooshort; + return nil; +} + +static FWImage* +readfirmware(void) +{ + uchar dirbuf[sizeof(Dir)+100], *data; + char *err; + FWImage *fw; + int n, r; + Chan *c; + Dir d; + + if(!iseve()) + error(Eperm); + if(!waserror()){ + c = namec("/boot/wpi-3945abg", Aopen, OREAD, 0); + poperror(); + }else + c = namec("/lib/firmware/wpi-3945abg", 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 = smalloc(sizeof(*fw) + 16 + d.length); + data = (uchar*)(fw+1); + if(waserror()){ + free(fw); + 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)) != nil) + 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 int +rbplant(Ctlr *ctlr, int i) +{ + Block *b; + + b = iallocb(Rbufsize+127); + if(b == nil) + return -1; + b->rp = b->wp = (uchar*)((((uintptr)b->base+127)&~127)); + memset(b->rp, 0, Rdscsize); + coherence(); + ctlr->rx.b[i] = b; + ctlr->rx.p[i] = PCIWADDR(b->rp); + return 0; +} + +static char* +initring(Ctlr *ctlr) +{ + RXQ *rx; + TXQ *tx; + int i, q; + + rx = &ctlr->rx; + if(rx->b == nil) + rx->b = malloc(sizeof(Block*) * Nrx); + if(rx->p == nil) + rx->p = mallocalign(sizeof(u32int) * Nrx, 16 * 1024, 0, 0); + if(rx->b == nil || rx->p == nil) + return "no memory for rx ring"; + for(i = 0; i<Nrx; i++){ + rx->p[i] = 0; + 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; + + if(ctlr->shared == nil) + ctlr->shared = mallocalign(4096, 4096, 0, 0); + if(ctlr->shared == nil) + return "no memory for shared buffer"; + memset(ctlr->shared, 0, 4096); + + for(q=0; q<nelem(ctlr->tx); q++){ + tx = &ctlr->tx[q]; + if(tx->b == nil) + tx->b = malloc(sizeof(Block*) * Ntx); + if(tx->d == nil) + tx->d = mallocalign(Tdscsize * Ntx, 16 * 1024, 0, 0); + if(tx->c == nil) + tx->c = mallocalign(Tcmdsize * Ntx, 4, 0, 0); + if(tx->b == nil || tx->d == nil || tx->c == nil) + return "no memory for tx ring"; + memset(tx->d, 0, Tdscsize * Ntx); + memset(tx->c, 0, Tcmdsize * Ntx); + for(i=0; i<Ntx; i++){ + if(tx->b[i] != nil){ + freeb(tx->b[i]); + tx->b[i] = nil; + } + } + ctlr->shared->txbase[q] = PCIWADDR(tx->d); + tx->i = 0; + tx->n = 0; + tx->lastcmd = 0; + } + return nil; +} + +static char* +reset(Ctlr *ctlr) +{ + uchar rev; + char *err; + int i; + + if(ctlr->power) + poweroff(ctlr); + if((err = initring(ctlr)) != nil) + return err; + if((err = poweron(ctlr)) != nil) + return err; + + /* Select VMAIN power source. */ + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain); + nicunlock(ctlr); + /* Spin until VMAIN gets selected. */ + for(i = 0; i < 5000; i++){ + if(csr32r(ctlr, GpioIn) & (1 << 9)) + break; + delay(10); + } + + /* Perform adapter initialization. */ + rev = ctlr->pdev->rid; + if((rev & 0xc0) == 0x40) + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | AlmMb); + else if(!(rev & 0x80)) + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | AlmMm); + + if(ctlr->eeprom.cap == 0x80) + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | SkuMrc); + + if((ctlr->eeprom.rev & 0xf0) == 0xd0) + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RevD); + else + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~RevD); + + if(ctlr->eeprom.type > 1) + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | TypeB); + + /* Initialize RX ring. */ + if((err = niclock(ctlr)) != nil) + return err; + + coherence(); + csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p)); + csr32w(ctlr, FhRxRptrAddr, PCIWADDR(&ctlr->shared->next)); + csr32w(ctlr, FhRxWptr, 0); + csr32w(ctlr, FhRxConfig, + FhRxConfigDmaEna | + FhRxConfigRdrbdEna | + FhRxConfigWrstatusEna | + FhRxConfigMaxfrag | + (Nrxlog << FhRxConfigNrdbShift) | + FhRxConfigIrqDstHost | + (1 << FhRxConfigIrqRbthShift)); + USED(csr32r(ctlr, FhRssrTbl)); + csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7); + nicunlock(ctlr); + + /* Initialize TX rings. */ + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, AlmSchedMode, 2); + prphwrite(ctlr, AlmSchedArastat, 1); + prphwrite(ctlr, AlmSchedTxfact, 0x3f); + prphwrite(ctlr, AlmSchedBP1, 0x10000); + prphwrite(ctlr, AlmSchedBP2, 0x30002); + prphwrite(ctlr, AlmSchedTxf4mf, 4); + prphwrite(ctlr, AlmSchedTxf5mf, 5); + csr32w(ctlr, FhTxBase, PCIWADDR(ctlr->shared)); + csr32w(ctlr, FhMsgConfig, 0xffff05a5); + for(i = 0; i < 6; i++){ + csr32w(ctlr, FhCbbcCtrl+i*8, 0); + csr32w(ctlr, FhCbbcBase+i*8, 0); + csr32w(ctlr, FhTxConfig+i*32, 0x80200008); + } + nicunlock(ctlr); + USED(csr32r(ctlr, FhTxBase)); + + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked); + + ctlr->broken = 0; + ctlr->wait.m = 0; + ctlr->wait.w = 0; + + ctlr->ie = Idefmask; + csr32w(ctlr, Imr, ctlr->ie); + csr32w(ctlr, Isr, ~0); + + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + + return nil; +} + +static char* +postboot(Ctlr *); + +static char* +boot(Ctlr *ctlr) +{ + int i, n, size; + uchar *dma, *p; + FWImage *fw; + char *err; + + fw = ctlr->fw; + /* 16 byte padding may not be necessary. */ + size = ROUND(fw->init.data.size, 16) + ROUND(fw->init.text.size, 16); + dma = mallocalign(size, 16, 0, 0); + if(dma == nil) + return "no memory for dma"; + + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + + p = dma; + memmove(p, fw->init.data.data, fw->init.data.size); + coherence(); + prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p)); + prphwrite(ctlr, BsmDramDataSize, fw->init.data.size); + p += ROUND(fw->init.data.size, 16); + memmove(p, fw->init.text.data, fw->init.text.size); + coherence(); + prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p)); + prphwrite(ctlr, BsmDramTextSize, fw->init.text.size); + + nicunlock(ctlr); + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + + /* Copy microcode image into NIC memory. */ + p = fw->boot.text.data; + n = fw->boot.text.size/4; + for(i=0; i<n; i++, p += 4) + prphwrite(ctlr, BsmSramBase+i*4, get32(p)); + + prphwrite(ctlr, BsmWrMemSrc, 0); + prphwrite(ctlr, BsmWrMemDst, 0); + prphwrite(ctlr, BsmWrDwCount, n); + + /* Start boot load now. */ + prphwrite(ctlr, BsmWrCtrl, 1<<31); + + /* Wait for transfer to complete. */ + for(i=0; i<1000; i++){ + if((prphread(ctlr, BsmWrCtrl) & (1<<31)) == 0) + break; + delay(10); + } + if(i == 1000){ + nicunlock(ctlr); + free(dma); + return "bootcode timeout"; + } + + /* Enable boot after power up. */ + prphwrite(ctlr, BsmWrCtrl, 1<<30); + nicunlock(ctlr); + + /* Now press "execute". */ + csr32w(ctlr, Reset, 0); + + /* Wait at most one second for first alive notification. */ + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){ + free(dma); + return "init firmware boot failed"; + } + free(dma); + + size = ROUND(fw->main.data.size, 16) + ROUND(fw->main.text.size, 16); + dma = mallocalign(size, 16, 0, 0); + if(dma == nil) + return "no memory for dma"; + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + p = dma; + memmove(p, fw->main.data.data, fw->main.data.size); + coherence(); + prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p)); + prphwrite(ctlr, BsmDramDataSize, fw->main.data.size); + p += ROUND(fw->main.data.size, 16); + memmove(p, fw->main.text.data, fw->main.text.size); + coherence(); + prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p)); + prphwrite(ctlr, BsmDramTextSize, fw->main.text.size | (1<<31)); + nicunlock(ctlr); + + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){ + free(dma); + return "main firmware boot failed"; + } + free(dma); + return postboot(ctlr); +} + +static int +txqready(void *arg) +{ + TXQ *q = arg; + return q->n < Ntx; +} + +static char* +qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block) +{ + uchar *d, *c; + int pad; + TXQ *q; + + assert(qid < nelem(ctlr->tx)); + assert(size <= Tcmdsize-4); + + ilock(ctlr); + q = &ctlr->tx[qid]; + while(q->n >= Ntx && !ctlr->broken){ + iunlock(ctlr); + qlock(q); + if(!waserror()){ + tsleep(q, txqready, q, 10); + poperror(); + } + qunlock(q); + ilock(ctlr); + } + if(ctlr->broken){ + iunlock(ctlr); + return "qcmd: broken"; + } + q->n++; + + q->lastcmd = code; + q->b[q->i] = block; + c = q->c + q->i * Tcmdsize; + d = q->d + q->i * Tdscsize; + + /* build command */ + c[0] = code; + c[1] = 0; /* flags */ + c[2] = q->i; + c[3] = qid; + + if(size > 0) + memmove(c+4, data, size); + size += 4; + + memset(d, 0, Tdscsize); + + pad = size - 4; + if(block != nil) + pad += BLEN(block); + pad = ((pad + 3) & ~3) - pad; + + put32(d, (pad << 28) | ((1 + (block != nil)) << 24)), d += 4; + put32(d, PCIWADDR(c)), d += 4; + put32(d, size), d += 4; + + if(block != nil){ + size = BLEN(block); + put32(d, PCIWADDR(block->rp)), d += 4; + put32(d, size), d += 4; + } + + USED(d); + + coherence(); + + q->i = (q->i+1) % Ntx; + csr32w(ctlr, HbusTargWptr, (qid<<8) | q->i); + + iunlock(ctlr); + + return nil; +} + +static int +txqempty(void *arg) +{ + TXQ *q = arg; + return q->n == 0; +} + +static char* +flushq(Ctlr *ctlr, uint qid) +{ + TXQ *q; + int i; + + q = &ctlr->tx[qid]; + qlock(q); + for(i = 0; i < 200 && !ctlr->broken; i++){ + if(txqempty(q)){ + qunlock(q); + return nil; + } + if(islo() && !waserror()){ + tsleep(q, txqempty, q, 10); + poperror(); + } + } + qunlock(q); + if(ctlr->broken) + return "flushq: broken"; + return "flushq: timeout"; +} + +static char* +cmd(Ctlr *ctlr, uint code, uchar *data, int size) +{ + char *err; + + if((err = qcmd(ctlr, 4, code, data, size, nil)) != nil) + return err; + return flushq(ctlr, 4); +} + +static void +setled(Ctlr *ctlr, int which, int on, int off) +{ + uchar c[8]; + + memset(c, 0, sizeof(c)); + put32(c, 10000); + c[4] = which; + c[5] = on; + c[6] = off; + cmd(ctlr, 72, c, sizeof(c)); +} + +static char* +btcoex(Ctlr *ctlr) +{ + uchar c[Tcmdsize], *p; + + /* configure bluetooth coexistance. */ + p = c; + *p++ = 3; /* flags WPI_BT_COEX_MODE_4WIRE */ + *p++ = 30; /* lead time */ + *p++ = 5; /* max kill */ + *p++ = 0; /* reserved */ + put32(p, 0), p += 4; /* kill_ack */ + put32(p, 0), p += 4; /* kill_cts */ + return cmd(ctlr, 155, c, p-c); +} + +static char* +powermode(Ctlr *ctlr) +{ + uchar c[Tcmdsize]; + int capoff, reg; + + memset(c, 0, sizeof(c)); + capoff = pcicap(ctlr->pdev, PciCapPCIe); + if(capoff >= 0){ + reg = pcicfgr8(ctlr->pdev, capoff+1); + if((reg & 1) == 0) /* PCI_PCIE_LCR_ASPM_L0S */ + c[0] |= 1<<3; /* WPI_PS_PCI_PMGT */ + } + return cmd(ctlr, 119, c, 4*(3+5)); +} + +static char* +postboot(Ctlr *ctlr) +{ + while((ctlr->temp = (int)csr32r(ctlr, UcodeGp2)) == 0) + delay(10); + +if(0){ + char *err; + + if((err = btcoex(ctlr)) != nil) + print("btcoex: %s\n", err); + if((err = powermode(ctlr)) != nil) + print("powermode: %s\n", err); +} + + return nil; +} + +static uchar wpirates[] = { + 0x80 | 12, + 0x80 | 18, + 0x80 | 24, + 0x80 | 36, + 0x80 | 48, + 0x80 | 72, + 0x80 | 96, + 0x80 | 108, + 0x80 | 2, + 0x80 | 4, + 0x80 | 11, + 0x80 | 22, + + 0 +}; + +static struct { + uchar rate; + uchar plcp; +} ratetab[] = { + { 12, 0xd }, + { 18, 0xf }, + { 24, 0x5 }, + { 36, 0x7 }, + { 48, 0x9 }, + { 72, 0xb }, + { 96, 0x1 }, + { 108, 0x3 }, + { 2, 10 }, + { 4, 20 }, + { 11, 55 }, + { 22, 110 }, +}; + +static u8int rfgain_2ghz[] = { + 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xd3, 0xd3, 0xb3, 0xb3, 0xb3, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x73, 0xeb, 0xeb, 0xeb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xab, 0xab, 0xab, 0x8b, + 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xc3, 0xc3, 0xc3, 0xc3, 0xa3, + 0xa3, 0xa3, 0xa3, 0x83, 0x83, 0x83, 0x83, 0x63, 0x63, 0x63, 0x63, + 0x43, 0x43, 0x43, 0x43, 0x23, 0x23, 0x23, 0x23, 0x03, 0x03, 0x03, + 0x03 +}; + +static u8int dspgain_2ghz[] = { + 0x7f, 0x7f, 0x7f, 0x7f, 0x7d, 0x6e, 0x69, 0x62, 0x7d, 0x73, 0x6c, + 0x63, 0x77, 0x6f, 0x69, 0x61, 0x5c, 0x6a, 0x64, 0x78, 0x71, 0x6b, + 0x7d, 0x77, 0x70, 0x6a, 0x65, 0x61, 0x5b, 0x6b, 0x79, 0x73, 0x6d, + 0x7f, 0x79, 0x73, 0x6c, 0x66, 0x60, 0x5c, 0x6e, 0x68, 0x62, 0x74, + 0x7d, 0x77, 0x71, 0x6b, 0x65, 0x60, 0x71, 0x6a, 0x66, 0x5f, 0x71, + 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f, + 0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, + 0x5f +}; + +static int +pwridx(Ctlr *ctlr, Powergrp *pwgr, int chan, int rate) +{ +/* Fixed-point arithmetic division using a n-bit fractional part. */ +#define fdivround(a, b, n) \ + ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) + +/* Linear interpolation. */ +#define interpolate(x, x1, y1, x2, y2, n) \ + ((y1) + fdivround(((x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) + + int pwr; + Sample *sample; + int idx; + + /* Default TX power is group maximum TX power minus 3dB. */ + pwr = pwgr->maxpwr / 2; + + /* Decrease TX power for highest OFDM rates to reduce distortion. */ + switch(rate){ + case 5: /* WPI_RIDX_OFDM36 */ + pwr -= 0; + break; + case 6: /* WPI_RIDX_OFDM48 */ + pwr -=7; + break; + case 7: /* WPI_RIDX_OFDM54 */ + pwr -= 9; + break; + } + + /* Never exceed the channel maximum allowed TX power. */ + pwr = MIN(pwr, ctlr->maxpwr[chan]); + + /* Retrieve TX power index into gain tables from samples. */ + for(sample = pwgr->samples; sample < &pwgr->samples[3]; sample++) + if(pwr > sample[1].power) + break; + /* Fixed-point linear interpolation using a 19-bit fractional part. */ + idx = interpolate(pwr, sample[0].power, sample[0].index, + sample[1].power, sample[1].index, 19); + + /*- + * Adjust power index based on current temperature: + * - if cooler than factory-calibrated: decrease output power + * - if warmer than factory-calibrated: increase output power + */ + idx -= (ctlr->temp - pwgr->temp) * 11 / 100; + + /* Decrease TX power for CCK rates (-5dB). */ + if (rate >= 8) + idx += 10; + + /* Make sure idx stays in a valid range. */ + if (idx < 0) + idx = 0; + else if (idx >= nelem(rfgain_2ghz)) + idx = nelem(rfgain_2ghz)-1; + return idx; +#undef fdivround +#undef interpolate +} + +static void +addnode(Ctlr *ctlr, uchar id, uchar *addr, int plcp, int antenna) +{ + uchar c[Tcmdsize], *p; + + memset(p = c, 0, sizeof(c)); + *p++ = 0; /* control (1 = update) */ + p += 3; /* reserved */ + memmove(p, addr, 6); + p += 6; + p += 2; /* reserved */ + *p++ = id; /* node id */ + p++; /* flags */ + p += 2; /* reserved */ + p += 2; /* kflags */ + p++; /* tcs2 */ + p++; /* reserved */ + p += 5*2; /* ttak */ + p += 2; /* reserved */ + p += 16; /* key */ + put32(p, 4); /* action (4 = set_rate) */ + p += 4; + p += 4; /* mask */ + p += 2; /* tid */ + *p++ = plcp; /* plcp */ + *p++ = antenna; /* antenna */ + p++; /* add_imm */ + p++; /* del_imm */ + p++; /* add_imm_start */ + cmd(ctlr, 24, c, p - c); +} + +static void +rxon(Ether *edev, Wnode *bss) +{ + uchar c[Tcmdsize], *p; + int filter, flags, rate; + Ctlr *ctlr; + char *err; + int idx; + + ctlr = edev->ctlr; + filter = FilterNoDecrypt | FilterMulticast; + if(ctlr->prom){ + filter |= FilterPromisc; + if(bss != nil) + ctlr->channel = bss->channel; + bss = nil; + } + if(bss != nil){ + ctlr->channel = bss->channel; + memmove(ctlr->bssid, bss->bssid, Eaddrlen); + ctlr->aid = bss->aid; + if(ctlr->aid != 0){ + filter |= FilterBSS; + ctlr->bssnodeid = -1; + }else + ctlr->bcastnodeid = -1; + }else{ + memmove(ctlr->bssid, edev->bcast, Eaddrlen); + ctlr->aid = 0; + ctlr->bcastnodeid = -1; + ctlr->bssnodeid = -1; + } + flags = RFlagTSF | RFlag24Ghz | RFlagAuto; + + if(ctlr->aid != 0) + setled(ctlr, 2, 0, 1); /* on when associated */ + else if(memcmp(ctlr->bssid, edev->bcast, Eaddrlen) != 0) + setled(ctlr, 2, 10, 10); /* slow blink when connecting */ + else + setled(ctlr, 2, 5, 5); /* fast blink when scanning */ + + memset(p = c, 0, sizeof(c)); + memmove(p, edev->ea, 6); p += 8; /* myaddr */ + memmove(p, ctlr->bssid, 6); p += 16; /* bssid */ + *p++ = 3; /* mode (STA) */ + p += 3; + *p++ = 0xff; /* ofdm mask (not yet negotiated) */ + *p++ = 0x0f; /* cck mask (not yet negotiated) */ + put16(p, ctlr->aid & 0x3fff); /* associd */ + p += 2; + put32(p, flags); + p += 4; + put32(p, filter); + p += 4; + *p++ = ctlr->channel; + p += 3; + + if((err = cmd(ctlr, 16, c, p - c)) != nil){ + print("rxon: %s\n", err); + return; + } + + /* tx power */ + memset(p = c, 0, sizeof(c)); + *p++ = 1; /* band (0 = 5ghz) */ + p++; /* reserved */ + put16(p, ctlr->channel), p += 2; + for(rate = 0; rate < nelem(ratetab); rate++){ + idx = pwridx(ctlr, &ctlr->eeprom.pwrgrps[0], ctlr->channel, rate); + *p++ = ratetab[rate].plcp; + *p++ = rfgain_2ghz[idx]; /* rf_gain */ + *p++ = dspgain_2ghz[idx]; /* dsp_gain */ + p++; /* reservd */ + } + cmd(ctlr, 151, c, p - c); + + if(ctlr->bcastnodeid == -1){ + ctlr->bcastnodeid = 24; + addnode(ctlr, ctlr->bcastnodeid, edev->bcast, ratetab[0].plcp, 3<<6); + } + if(ctlr->bssnodeid == -1 && bss != nil && ctlr->aid != 0){ + ctlr->bssnodeid = 0; + addnode(ctlr, ctlr->bssnodeid, bss->bssid, ratetab[0].plcp, 3<<6); + } +} + +enum { + TFlagNeedRTS = 1<<1, + TFlagNeedCTS = 1<<2, + TFlagNeedACK = 1<<3, + TFlagFullTxOp = 1<<7, + TFlagBtDis = 1<<12, + TFlagAutoSeq = 1<<13, + TFlagInsertTs = 1<<16, +}; + +static void +transmit(Wifi *wifi, Wnode *wn, Block *b) +{ + uchar c[Tcmdsize], *p; + Ether *edev; + Ctlr *ctlr; + Wifipkt *w; + int flags, nodeid, rate, timeout; + char *err; + + edev = wifi->ether; + ctlr = edev->ctlr; + + qlock(ctlr); + if(ctlr->attached == 0 || ctlr->broken){ + qunlock(ctlr); + freeb(b); + return; + } + + if((wn->channel != ctlr->channel) + || (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0))) + rxon(edev, wn); + + if(b == nil){ + /* association note has no data to transmit */ + qunlock(ctlr); + return; + } + + flags = 0; + timeout = 3; + nodeid = ctlr->bcastnodeid; + p = wn->minrate; + w = (Wifipkt*)b->rp; + if((w->a1[0] & 1) == 0){ + flags |= TFlagNeedACK; + + if(BLEN(b) > 512-4) + flags |= TFlagNeedRTS|TFlagFullTxOp; + + if((w->fc[0] & 0x0c) == 0x08 && ctlr->bssnodeid != -1){ + timeout = 0; + nodeid = ctlr->bssnodeid; + p = wn->maxrate; + } + } + qunlock(ctlr); + + rate = 0; + if(p >= wpirates && p < &wpirates[nelem(ratetab)]) + rate = p - wpirates; + + memset(p = c, 0, sizeof(c)); + put16(p, BLEN(b)), p += 2; + put16(p, 0), p += 2; /* lnext */ + put32(p, flags), p += 4; + *p++ = ratetab[rate].plcp; + *p++ = nodeid; + *p++ = 0; /* tid */ + *p++ = 0; /* security */ + p += 16+8; /* key/iv */ + put32(p, 0), p += 4; /* fnext */ + put32(p, 0xffffffff), p += 4; /* livetime infinite */ + *p++ = 0xff; + *p++ = 0x0f; + *p++ = 7; + *p++ = 15; + put16(p, timeout), p += 2; + put16(p, 0), p += 2; /* txop */ + + if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){ + print("transmit: %s\n", err); + freeb(b); + } +} + +static long +wpictl(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) + return wifictl(ctlr->wifi, buf, n); + return 0; +} + +static long +wpiifstat(Ether *edev, void *buf, long n, ulong off) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(ctlr->wifi) + return wifistat(ctlr->wifi, buf, n, off); + return 0; +} + +static void +setoptions(Ether *edev) +{ + char buf[64], *p; + Ctlr *ctlr; + int i; + + ctlr = edev->ctlr; + for(i = 0; i < edev->nopt; i++){ + snprint(buf, sizeof(buf), "%s", edev->opt[i]); + p = strchr(buf, '='); + if(p != nil) + *p = 0; + if(strcmp(buf, "debug") == 0 + || strcmp(buf, "essid") == 0 + || strcmp(buf, "bssid") == 0){ + if(p != nil) + *p = ' '; + if(!waserror()){ + wifictl(ctlr->wifi, buf, strlen(buf)); + poperror(); + } + } + } +} + +static void +wpipromiscuous(void *arg, int on) +{ + Ether *edev; + Ctlr *ctlr; + + edev = arg; + ctlr = edev->ctlr; + qlock(ctlr); + ctlr->prom = on; + rxon(edev, ctlr->wifi->bss); + qunlock(ctlr); +} + +static void +wpirecover(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((csr32r(ctlr, Gpc) & RfKill) == 0) + break; + + if(reset(ctlr) != nil) + break; + if(boot(ctlr) != nil) + break; + + ctlr->bcastnodeid = -1; + ctlr->bssnodeid = -1; + ctlr->aid = 0; + rxon(edev, ctlr->wifi->bss); + break; + } + qunlock(ctlr); + } +} + +static void +wpiattach(Ether *edev) +{ + FWImage *fw; + Ctlr *ctlr; + char *err; + + ctlr = edev->ctlr; + eqlock(ctlr); + if(waserror()){ + print("#l%d: %s\n", edev->ctlrno, up->errstr); + if(ctlr->power) + poweroff(ctlr); + qunlock(ctlr); + nexterror(); + } + if(ctlr->attached == 0){ + if((csr32r(ctlr, Gpc) & RfKill) == 0) + error("wifi disabled by switch"); + + if(ctlr->wifi == nil){ + ctlr->wifi = wifiattach(edev, transmit); + ctlr->wifi->rates = wpirates; + } + + if(ctlr->fw == nil){ + fw = readfirmware(); + print("#l%d: firmware: %ux, size: %ux+%ux+%ux+%ux+%ux\n", + edev->ctlrno, fw->version, + fw->main.text.size, fw->main.data.size, + fw->init.text.size, fw->init.data.size, + fw->boot.text.size); + ctlr->fw = fw; + } + + if((err = reset(ctlr)) != nil) + error(err); + if((err = boot(ctlr)) != nil) + error(err); + + ctlr->bcastnodeid = -1; + ctlr->bssnodeid = -1; + ctlr->channel = 1; + ctlr->aid = 0; + + setoptions(edev); + + ctlr->attached = 1; + + kproc("wpirecover", wpirecover, edev); + } + qunlock(ctlr); + poperror(); +} + +static void +receive(Ctlr *ctlr) +{ + Block *b, *bb; + uchar *d; + RXQ *rx; + TXQ *tx; + u32int hw; + + rx = &ctlr->rx; + if(ctlr->broken || ctlr->shared == nil || rx->b == nil) + return; + for(hw = ctlr->shared->next % Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){ + uchar type, flags, idx, qid; + u32int len; + + b = rx->b[rx->i]; + if(b == nil) + continue; + + d = b->rp; + len = get32(d); d += 4; + type = *d++; + flags = *d++; + idx = *d++; + qid = *d++; + + USED(len); + USED(flags); + +if(0) iprint("rxdesc[%d] type=%d len=%d idx=%d qid=%d\n", rx->i, type, len, idx, qid); + + if((qid & 0x80) == 0 && qid < nelem(ctlr->tx)){ + tx = &ctlr->tx[qid]; + if(tx->n > 0){ + bb = tx->b[idx]; + if(bb != nil){ + tx->b[idx] = nil; + freeb(bb); + } + tx->n--; + wakeup(tx); + } + } + + switch(type){ + case 1: /* uc ready */ + break; + + case 24: /* add node done */ + break; + + case 27: /* rx done */ + if(d + 1 > b->lim) + break; + d += d[0]; + d += 8; + if(d + 6 + 2 > b->lim){ + break; + } + len = get16(d+6); + d += 8; + if(d + len + 4 > b->lim){ + break; + } + if((get32(d + len) & 3) != 3){ + break; + } + if(ctlr->wifi == nil) + break; + if(rbplant(ctlr, rx->i) < 0) + break; + b->rp = d; + b->wp = d + len; + wifiiq(ctlr->wifi, b); + continue; + + case 28: /* tx done */ + break; + + case 130: /* start scan */ + break; + + case 132: /* stop scan */ + break; + + case 161: /* state change */ + break; + } + } + csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7); +} + +static void +wpiinterrupt(Ureg*, void *arg) +{ + u32int isr, fhisr; + Ether *edev; + Ctlr *ctlr; + + edev = arg; + ctlr = edev->ctlr; + ilock(ctlr); + csr32w(ctlr, Imr, 0); + isr = csr32r(ctlr, Isr); + fhisr = csr32r(ctlr, FhIsr); + if(isr == 0xffffffff || (isr & 0xfffffff0) == 0xa5a5a5a0){ + iunlock(ctlr); + return; + } + if(isr == 0 && fhisr == 0) + goto done; + csr32w(ctlr, Isr, isr); + csr32w(ctlr, FhIsr, fhisr); + if((isr & (Iswrx | Ifhrx)) || (fhisr & Ifhrx)) + receive(ctlr); + if(isr & Ierr){ + ctlr->broken = 1; + print("#l%d: fatal firmware error, lastcmd %ud\n", edev->ctlrno, ctlr->tx[4].lastcmd); + } + ctlr->wait.m |= isr; + if(ctlr->wait.m & ctlr->wait.w) + wakeup(&ctlr->wait); +done: + csr32w(ctlr, Imr, ctlr->ie); + iunlock(ctlr); +} + +static void +wpishutdown(Ether *edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(ctlr->power) + poweroff(ctlr); + ctlr->broken = 0; +} + +static Ctlr *wpihead, *wpitail; + +static void +wpipci(void) +{ + Pcidev *pdev; + + pdev = nil; + while(pdev = pcimatch(pdev, 0x8086, 0)){ + Ctlr *ctlr; + void *mem; + switch(pdev->did){ + default: + continue; + case 0x4227: + break; + } + + /* Clear device-specific "PCI retry timeout" register (41h). */ + if(pcicfgr8(pdev, 0x41) != 0) + pcicfgw8(pdev, 0x41, 0); + + pcisetbme(pdev); + pcisetpms(pdev, 0); + + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil) { + print("wpi: unable to alloc Ctlr\n"); + continue; + } + ctlr->port = pdev->mem[0].bar & ~0x0F; + mem = vmap(pdev->mem[0].bar & ~0x0F, pdev->mem[0].size); + if(mem == nil) { + print("wpi: can't map %8.8luX\n", pdev->mem[0].bar); + free(ctlr); + continue; + } + ctlr->nic = mem; + ctlr->pdev = pdev; + + if(wpihead != nil) + wpitail->link = ctlr; + else + wpihead = ctlr; + wpitail = ctlr; + } +} + +static int +wpipnp(Ether *edev) +{ + Ctlr *ctlr; + + if(wpihead == nil) + wpipci(); + +again: + for(ctlr = wpihead; ctlr != nil; ctlr = ctlr->link){ + if(ctlr->active) + continue; + if(edev->port == 0 || edev->port == ctlr->port){ + ctlr->active = 1; + 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->interrupt = wpiinterrupt; + edev->attach = wpiattach; + edev->ifstat = wpiifstat; + edev->ctl = wpictl; + edev->shutdown = wpishutdown; + edev->promiscuous = wpipromiscuous; + edev->multicast = nil; + edev->mbps = 54; + + if(wpiinit(edev) < 0){ + edev->ctlr = nil; + goto again; + } + + return 0; +} + +void +etherwpilink(void) +{ + addethercard("wpi", wpipnp); +} diff --git a/sys/src/9/pc/pccpuf b/sys/src/9/pc/pccpuf index 0b06f4192..72a412f00 100644 --- a/sys/src/9/pc/pccpuf +++ b/sys/src/9/pc/pccpuf @@ -69,6 +69,7 @@ link etheryuk pci etherwavelan wavelan devi82365 cis pci etheriwl pci wifi + etherwpi pci wifi etherrt2860 pci wifi ethervirtio pci ethermedium diff --git a/sys/src/9/pc/pcf b/sys/src/9/pc/pcf index b05bf0cd6..2ea4b9ec5 100644 --- a/sys/src/9/pc/pcf +++ b/sys/src/9/pc/pcf @@ -70,6 +70,7 @@ link etheryuk pci etherwavelan wavelan devi82365 cis pci etheriwl pci wifi + etherwpi pci wifi etherrt2860 pci wifi ethervirtio pci ethermedium diff --git a/sys/src/9/pc64/pc64 b/sys/src/9/pc64/pc64 index 44b4d3c3b..67fae1818 100644 --- a/sys/src/9/pc64/pc64 +++ b/sys/src/9/pc64/pc64 @@ -68,6 +68,7 @@ link # etheryuk pci # etherwavelan wavelan devi82365 cis pci etheriwl pci wifi + etherwpi pci wifi # etherrt2860 pci wifi ethervirtio pci ethermedium |