diff options
author | cinap_lenrek <cinap_lenrek@gmx.de> | 2013-02-09 03:19:50 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@gmx.de> | 2013-02-09 03:19:50 +0100 |
commit | 09a58258328d34edd14b8c361909635b9d2a1390 (patch) | |
tree | 076871e34cd238e6319dda09c0191691d0c7d03b /sys/src | |
parent | f1727a674232f1a91dda8f18d7d7e4a1b486e5cf (diff) |
etheriwl: experimental intel wifi link driver
Diffstat (limited to 'sys/src')
-rw-r--r-- | sys/src/9/pc/etheriwl.c | 1570 | ||||
-rw-r--r-- | sys/src/9/pc/mkfile | 1 | ||||
-rw-r--r-- | sys/src/9/pc/pccpuf | 1 | ||||
-rw-r--r-- | sys/src/9/pc/pcf | 1 | ||||
-rw-r--r-- | sys/src/9/pc/wifi.c | 424 | ||||
-rw-r--r-- | sys/src/9/pc/wifi.h | 52 |
6 files changed, 2049 insertions, 0 deletions
diff --git a/sys/src/9/pc/etheriwl.c b/sys/src/9/pc/etheriwl.c new file mode 100644 index 000000000..0e4b16eba --- /dev/null +++ b/sys/src/9/pc/etheriwl.c @@ -0,0 +1,1570 @@ +/* + * Intel WiFi Link driver. + * + * Written without any documentation but Damien Bergaminis + * OpenBSD iwn(4) driver sources. Requires intel firmware + * to be present in /lib/firmware/iwn-* on attach. + */ + +#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 { + + Ntxlog = 8, + Ntx = 1<<Ntxlog, + Nrxlog = 8, + Nrx = 1<<Nrxlog, + + Rstatsize = 16, + Rbufsize = 4*1024, + Rdscsize = 8, + + Tdscsize = 128, + Tcmdsize = 140, +}; + +/* registers */ +enum { + Cfg = 0x000, /* config register */ + MacSi = 1<<8, + RadioSi = 1<<9, + EepromLocked = 1<<21, + NicReady = 1<<22, + HapwakeL1A = 1<<23, + PrepareDone = 1<<25, + Prepare = 1<<27, + + Isr = 0x008, /* interrupt status */ + Imr = 0x00c, /* interrupt mask */ + Ialive = 1<<0, + Iwakeup = 1<<1, + Iswrx = 1<<3, + Ictreached = 1<<6, + Irftoggled = 1<<7, + Iswerr = 1<<25, + Isched = 1<<26, + Ifhtx = 1<<27, + Irxperiodic = 1<<28, + Ihwerr = 1<<29, + Ifhrx = 1<<31, + + Ierr = Iswerr | Ihwerr, + Idefmask = Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Ictreached | Irftoggled, + + FhIsr = 0x010, /* second interrupt status */ + + Reset = 0x020, + + Rev = 0x028, /* hardware revision */ + + EepromIo = 0x02c, /* EEPROM i/o register */ + EepromGp = 0x030, + OtpromGp = 0x034, + DevSelOtp = 1<<16, + RelativeAccess = 1<<17, + EccCorrStts = 1<<20, + EccUncorrStts = 1<<21, + + Gpc = 0x024, /* gp cntrl */ + MacAccessEna = 1<<0, + MacClockReady = 1<<0, + InitDone = 1<<2, + MacAccessReq = 1<<3, + NicSleep = 1<<4, + RfKill = 1<<27, + + Gio = 0x03c, + EnaL0S = 1<<1, + + Led = 0x094, + LedBsmCtrl = 1<<5, + LedOn = 0x38, + LedOff = 0x78, + + UcodeGp1Clr = 0x05c, + UcodeGp1RfKill = 1<<1, + UcodeGp1CmdBlocked = 1<<2, + UcodeGp1CtempStopRf = 1<<3, + + ShadowRegCtrl = 0x0a8, + + Giochicken = 0x100, + L1AnoL0Srx = 1<<23, + DisL0Stimer = 1<<29, + + AnaPll = 0x20c, + + Dbghpetmem = 0x240, + + MemRaddr = 0x40c, + MemWaddr = 0x410, + MemWdata = 0x418, + MemRdata = 0x41c, + + PrphWaddr = 0x444, + PrphRaddr = 0x448, + PrphWdata = 0x44c, + PrphRdata = 0x450, + + HbusTargWptr = 0x460, +}; + +/* + * Flow-Handler registers. + */ +enum { + FhTfbdCtrl0 = 0x1900, // +q*8 + FhTfbdCtrl1 = 0x1904, // +q*8 + + FhKwAddr = 0x197c, + + FhSramAddr = 0x19a4, // +q*4 + FhCbbcQueue = 0x19d0, // +q*4 + FhStatusWptr = 0x1bc0, + FhRxBase = 0x1bc4, + FhRxWptr = 0x1bc8, + FhRxConfig = 0x1c00, + FhRxConfigEna = 1<<31, + FhRxConfigRbSize8K = 1<<16, + FhRxConfigSingleFrame = 1<<15, + FhRxConfigIrqDstHost = 1<<12, + FhRxConfigIgnRxfEmpty = 1<<2, + + FhRxConfigNrbdShift = 20, + FhRxConfigRbTimeoutShift= 4, + + FhRxStatus = 0x1c44, + + FhTxConfig = 0x1d00, // +q*32 + FhTxConfigDmaCreditEna = 1<<3, + FhTxConfigDmaEna = 1<<31, + FhTxConfigCirqHostEndTfd= 1<<20, + + FhTxBufStatus = 0x1d08, // +q*32 + FhTxBufStatusTbNumShift = 20, + FhTxBufStatusTbIdxShift = 12, + FhTxBufStatusTfbdValid = 3, + + FhTxChicken = 0x1e98, + FhTxStatus = 0x1eb0, +}; + +/* + * NIC internal memory offsets. + */ +enum { + ApmgClkCtrl = 0x3000, + ApmgClkEna = 0x3004, + ApmgClkDis = 0x3008, + DmaClkRqt = 1<<9, + BsmClkRqt = 1<<11, + + ApmgPs = 0x300c, + EarlyPwroffDis = 1<<22, + PwrSrcVMain = 0<<24, + PwrSrcVAux = 2<<24, + PwrSrcMask = 3<<24, + ResetReq = 1<<26, + + ApmgDigitalSvr = 0x3058, + ApmgAnalogSvr = 0x306c, + ApmgPciStt = 0x3010, + BsmWrCtrl = 0x3400, + BsmWrMemSrc = 0x3404, + BsmWrMemDst = 0x3408, + BsmWrDwCount = 0x340c, + BsmDramTextAddr = 0x3490, + BsmDramTextSize = 0x3494, + BsmDramDataAddr = 0x3498, + BsmDramDataSize = 0x349c, + BsmSramBase = 0x3800, +}; + +/* + * TX scheduler registers. + */ +enum { + SchedBase = 0xa02c00, + SchedSramAddr = SchedBase, + SchedDramAddr5000 = SchedBase+0x008, + SchedDramAddr4965 = SchedBase+0x010, + SchedTxFact5000 = SchedBase+0x010, + SchedTxFact4965 = SchedBase+0x01c, + SchedQueueRdptr4965 = SchedBase+0x064, // +q*4 + SchedQueueRdptr5000 = SchedBase+0x068, // +q*4 + SchedQChainSel4965 = SchedBase+0x0d0, + SchedIntrMask4965 = SchedBase+0x0e4, + SchedQChainSel5000 = SchedBase+0x0e8, + SchedQueueStatus4965 = SchedBase+0x104, // +q*4 + SchedIntrMask5000 = SchedBase+0x108, + SchedQueueStatus5000 = SchedBase+0x10c, // +q*4 + SchedAggrSel5000 = SchedBase+0x248, +}; + +enum { + SchedCtxOff4965 = 0x380, + SchedCtxLen4965 = 416, + SchedTransTblOff4965 = 0x500, + + SchedCtxOff5000 = 0x600, + SchedCtxLen5000 = 512, + SchedTransTblOff5000 = 0x7e0, +}; + +/* controller types */ +enum { + Type4965 = 0, + Type5300 = 2, + Type5350 = 3, + Type5150 = 4, + Type5100 = 5, + Type1000 = 6, + Type6000 = 7, + Type6050 = 8, + Type6005 = 11, +}; + +typedef struct FWInfo FWInfo; +typedef struct FWImage FWImage; +typedef struct FWSect FWSect; + +typedef struct TXQ TXQ; +typedef struct RXQ RXQ; + +typedef struct Ctlr Ctlr; + +struct FWSect +{ + uchar *data; + uint size; +}; + +struct FWImage +{ + struct { + FWSect text; + FWSect data; + } init, main, boot; + + uint rev; + uint build; + char descr[64+1]; + uchar data[]; +}; + +struct FWInfo +{ + uchar major; + uchar minjor; + uchar type; + uchar subtype; + + u32int logptr; + u32int errptr; + u32int tstamp; + u32int valid; +}; + +struct TXQ +{ + uint n; + uint i; + Block **b; + uchar *d; + uchar *c; + + Rendez; + QLock; +}; + +struct RXQ +{ + uint i; + Block **b; + u32int *p; + uchar *s; +}; + +struct Ctlr { + Lock; + QLock; + + Ctlr *link; + Pcidev *pdev; + Wifi *wifi; + + int type; + int port; + int active; + int attached; + + u32int ie; + + u32int *nic; + uchar *kwpage; + + int channel; + + RXQ rx; + TXQ tx[20]; + + struct { + Rendez; + u32int m; + u32int w; + u32int r; + } wait; + + struct { + uchar type; + uchar step; + uchar dash; + uchar txantmask; + uchar rxantmask; + } rfcfg; + + struct { + u32int crystal; + } eeprom; + + struct { + u32int base; + uchar *s; + } sched; + + FWInfo fwinfo; + FWImage *fw; +}; + +#define csr32r(c, r) (*((c)->nic+((r)/4))) +#define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v)) + +static uint +get16(uchar *p){ + return *((u16int*)p); +} +static uint +get32(uchar *p){ + return *((u32int*)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 u32int +memread(Ctlr *ctlr, uint off) +{ + csr32w(ctlr, MemRaddr, off); + coherence(); + return csr32r(ctlr, MemRdata); +} +static void +memwrite(Ctlr *ctlr, uint off, u32int data) +{ + csr32w(ctlr, MemWaddr, off); + coherence(); + csr32w(ctlr, MemWdata, data); +} + +static void +setfwinfo(Ctlr *ctlr, uchar *d, int len) +{ + FWInfo *i; + + if(len < 32) + return; + i = &ctlr->fwinfo; + i->minjor = *d++; + i->major = *d++; + d += 2+8; + i->type = *d++; + i->subtype = *d++; + d += 2; + i->logptr = get32(d); d += 4; + i->errptr = get32(d); d += 4; + i->tstamp = get32(d); d += 4; + i->valid = get32(d); +}; + +static void +dumpctlr(Ctlr *ctlr) +{ + u32int dump[13]; + int i; + + if(ctlr->fwinfo.errptr == 0){ + print("no error pointer\n"); + return; + } + for(i=0; i<nelem(dump); i++) + dump[i] = memread(ctlr, ctlr->fwinfo.errptr + i*4); + print( "error:\tid %ux, pc %ux,\n" + "\tbranchlink %.8ux %.8ux, interruptlink %.8ux %.8ux,\n" + "\terrordata %.8ux %.8ux, srcline %ud, tsf %ux, time %ux\n", + dump[1], dump[2], + dump[4], dump[3], dump[6], dump[5], + dump[7], dump[8], dump[9], dump[10], dump[11]); +} + +static char* +eepromlock(Ctlr *ctlr) +{ + int i, j; + + for(i=0; i<100; i++){ + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | EepromLocked); + for(j=0; j<100; j++){ + if(csr32r(ctlr, Cfg) & EepromLocked) + return 0; + delay(10); + } + } + return "eepromlock: timeout"; +} +static void +eepromunlock(Ctlr *ctlr) +{ + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked); +} +static char* +eepromread(Ctlr *ctlr, void *data, int count, uint off) +{ + uchar *out = data; + u32int w; + int i; + + w = 0; + for(; count > 0; count -= 2, off++){ + csr32w(ctlr, EepromIo, off << 2); + for(i=0; i<10; i++){ + w = csr32r(ctlr, EepromIo); + if(w & 1) + break; + delay(5); + } + if(i == 10) + return "eepromread: timeout"; + *out++ = w >> 16; + if(count > 1) + *out++ = w >> 24; + } + return 0; +} + +static char* +handover(Ctlr *ctlr) +{ + int i; + + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady); + for(i=0; i<5; i++){ + if(csr32r(ctlr, Cfg) & NicReady) + return 0; + delay(10); + } + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare); + for(i=0; i<15000; i++){ + if((csr32r(ctlr, Cfg) & PrepareDone) == 0) + break; + delay(10); + } + if(i >= 15000) + return "handover: timeout"; + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady); + for(i=0; i<5; i++){ + if(csr32r(ctlr, Cfg) & NicReady) + return 0; + delay(10); + } + return "handover: timeout"; +} + +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 0; + delay(10); + } + return "clockwait: timeout"; +} + +static char* +poweron(Ctlr *ctlr) +{ + int capoff; + char *err; + + /* Disable L0s exit timer (NMI bug workaround). */ + csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | DisL0Stimer); + + /* Don't wait for ICH L0s (ICH bug workaround). */ + csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | L1AnoL0Srx); + + /* Set FH wait threshold to max (HW bug under stress workaround). */ + csr32w(ctlr, Dbghpetmem, csr32r(ctlr, Dbghpetmem) | 0xffff0000); + + /* Enable HAP INTA to move adapter from L1a to L0s. */ + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | HapwakeL1A); + + capoff = pcicap(ctlr->pdev, PciCapPCIe); + if(capoff != -1){ + /* Workaround for HW instability in PCIe L0->L0s->L1 transition. */ + if(pcicfgr16(ctlr->pdev, capoff + 0x10) & 0x2) /* LCSR -> L1 Entry enabled. */ + csr32w(ctlr, Gio, csr32r(ctlr, Gio) | EnaL0S); + else + csr32w(ctlr, Gio, csr32r(ctlr, Gio) & ~EnaL0S); + } + + if(ctlr->type != Type4965 && ctlr->type <= Type1000) + csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | 0x00880300); + + /* Wait for clock stabilization before accessing prph. */ + if((err = clockwait(ctlr)) != nil) + return err; + + if((err = niclock(ctlr)) != nil) + return err; + + /* Enable DMA and BSM (Bootstrap State Machine). */ + if(ctlr->type == Type4965) + prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt); + else + prphwrite(ctlr, ApmgClkEna, DmaClkRqt); + delay(20); + + /* Disable L1-Active. */ + prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11)); + + nicunlock(ctlr); + return 0; +} + +static int +iwlinit(Ether *edev) +{ + Ctlr *ctlr; + char *err; + uchar b[2]; + uint u; + + ctlr = edev->ctlr; + if((err = handover(ctlr)) != nil) + goto Err; + if((err = poweron(ctlr)) != nil) + goto Err; + if((csr32r(ctlr, EepromGp) & 0x7) == 0){ + err = "bad rom signature"; + goto Err; + } + if((err = eepromlock(ctlr)) != nil) + goto Err; + if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){ + eepromunlock(ctlr); + goto Err; + } + if((err = eepromread(ctlr, b, 2, 0x048)) != nil){ + eepromunlock(ctlr); + goto Err; + } + u = get16(b); + ctlr->rfcfg.type = u & 3; u >>= 2; + ctlr->rfcfg.step = u & 3; u >>= 2; + ctlr->rfcfg.dash = u & 3; u >>= 4; + ctlr->rfcfg.txantmask = u & 15; u >>= 4; + ctlr->rfcfg.rxantmask = u & 15; + if((err = eepromread(ctlr, b, 4, 0x128)) != nil){ + eepromunlock(ctlr); + goto Err; + } + ctlr->eeprom.crystal = get32(b); + eepromunlock(ctlr); + + ctlr->ie = 0; + csr32w(ctlr, Isr, ~0); /* clear pending interrupts */ + csr32w(ctlr, Imr, 0); /* no interrupts for now */ + + return 0; +Err: + print("iwlinit: %s\n", err); + return -1; +} + +static char* +crackfw(FWImage *i, uchar *data, uint size, int alt) +{ + uchar *p, *e; + FWSect *s; + + memset(i, 0, sizeof(*i)); + if(size < 4){ +Tooshort: + return "firmware image too short"; + } + p = data; + e = p + size; + i->rev = get32(p); p += 4; + if(i->rev == 0){ + uvlong altmask; + + if(size < (4+64+4+4+8)) + goto Tooshort; + if(memcmp(p, "IWL\n", 4) != 0) + return "bad firmware signature"; + p += 4; + strncpy(i->descr, (char*)p, 64); + i->descr[sizeof(i->descr)-1] = 0; + p += 64; + i->rev = get32(p); p += 4; + i->build = get32(p); p += 4; + altmask = get32(p); p += 4; + altmask |= (uvlong)get32(p) << 32; p += 4; + while(alt > 0 && (altmask & (1ULL<<alt)) == 0) + alt--; + while(p < e){ + FWSect dummy; + + if((p + 2+2+4) > e) + goto Tooshort; + switch(get16(p)){ + case 1: s = &i->main.text; break; + case 2: s = &i->main.data; break; + case 3: s = &i->init.text; break; + case 4: s = &i->init.data; break; + case 5: s = &i->boot.text; break; + default:s = &dummy; + } + p += 2; + if(get16(p) != alt) + s = &dummy; + p += 2; + s->size = get32(p); p += 4; + s->data = p; + if((p + s->size) > e) + goto Tooshort; + p += (s->size + 3) & ~3; + } + } else { + if(((i->rev>>8) & 0xFF) < 2) + return "need firmware api >= 2"; + if(((i->rev>>8) & 0xFF) >= 3){ + i->build = get32(p); p += 4; + } + if((p + 5*4) > e) + goto Tooshort; + 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 0; +} + +static FWImage* +readfirmware(char *name) +{ + uchar dirbuf[sizeof(Dir)+100], *data; + char buf[128], *err; + FWImage *fw; + int n, r; + Chan *c; + Dir d; + + if(!iseve()) + error(Eperm); + if(!waserror()){ + snprint(buf, sizeof buf, "/boot/%s", name); + c = namec(buf, Aopen, OREAD, 0); + poperror(); + } else { + snprint(buf, sizeof buf, "/lib/firmware/%s", name); + c = namec(buf, 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, 1)) != nil) + error(err); + poperror(); + poperror(); + cclose(c); + return fw; +} + +typedef struct Irqwait Irqwait; +struct Irqwait { + Ctlr *ctlr; + u32int mask; +}; + +static int +gotirq(void *arg) +{ + Irqwait *w; + Ctlr *ctlr; + + w = arg; + ctlr = w->ctlr; + ctlr->wait.r = ctlr->wait.m & w->mask; + if(ctlr->wait.r){ + ctlr->wait.m &= ~ctlr->wait.r; + return 1; + } + ctlr->wait.w = w->mask; + return 0; +} + +static u32int +irqwait(Ctlr *ctlr, u32int mask, int timeout) +{ + Irqwait w; + + w.ctlr = ctlr; + w.mask = mask; + tsleep(&ctlr->wait, gotirq, &w, timeout); + ctlr->wait.w = 0; + return ctlr->wait.r & mask; +} + +static char* +loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size) +{ + uchar *dma; + char *err; + + dma = mallocalign(size, 16, 0, 0); + if(dma == nil) + return "no memory for dma"; + memmove(dma, data, size); + coherence(); + if((err = niclock(ctlr)) != 0){ + free(dma); + return err; + } + csr32w(ctlr, FhTxConfig + 9*32, 0); + csr32w(ctlr, FhSramAddr + 9*4, dst); + csr32w(ctlr, FhTfbdCtrl0 + 9*8, PCIWADDR(dma)); + csr32w(ctlr, FhTfbdCtrl1 + 9*8, size); + csr32w(ctlr, FhTxBufStatus + 9*32, + (1<<FhTxBufStatusTbNumShift) | + (1<<FhTxBufStatusTbIdxShift) | + FhTxBufStatusTfbdValid); + csr32w(ctlr, FhTxConfig + 9*32, FhTxConfigDmaEna | FhTxConfigCirqHostEndTfd); + nicunlock(ctlr); + if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx){ + free(dma); + return "dma error / timeout"; + } + free(dma); + return 0; +} + +static int +txqready(void *arg) +{ + TXQ *q = arg; + return q->n < Ntx-8; +} + +static void +qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block) +{ + uchar *d, *c; + TXQ *q; + + ilock(ctlr); + q = &ctlr->tx[qid]; + while(q->n >= Ntx){ + iunlock(ctlr); + eqlock(q); + if(waserror()){ + qunlock(q); + nexterror(); + } + tsleep(q, txqready, q, 10); + qunlock(q); + ilock(ctlr); + } + q->n++; + + 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; + + assert(size <= Tcmdsize-4); + memmove(c+4, data, size); + + size += 4; + + /* build descriptor */ + *d++ = 0; + *d++ = 0; + *d++ = 0; + *d++ = 1 + (block != nil); /* nsegs */ + put32(d, PCIWADDR(c)); d += 4; + put16(d, size << 4); d += 2; + if(block != nil){ + put32(d, PCIWADDR(block->rp)); d += 4; + put16(d, BLEN(block) << 4); + } + + coherence(); + + q->i = (q->i+1) % Ntx; + csr32w(ctlr, HbusTargWptr, (qid<<8) | q->i); + + iunlock(ctlr); +} + +static void +cmd(Ctlr *ctlr, uint code, uchar *data, int size) +{ + qcmd(ctlr, 4, code, data, size, nil); +} + +static void +setled(Ctlr *ctlr, int which, int on, int off) +{ + uchar c[8]; + + csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl); + + memset(c, 0, sizeof(c)); + put32(c, 10000); + c[4] = which; + c[5] = on; + c[6] = off; + cmd(ctlr, 72, c, sizeof(c)); +} + +/* + * initialization which runs after the firmware has been booted up + */ +static void +postboot(Ctlr *ctlr) +{ + uchar c[8]; + char *err; + int i, q; + + /* main led turn on! (verify that firmware processes commands) */ + setled(ctlr, 2, 0, 1); + + if((err = niclock(ctlr)) != nil) + error(err); + ctlr->sched.base = prphread(ctlr, SchedSramAddr); + for(i=0; i < SchedCtxLen5000/4; i++) + memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + i*4, 0); + + prphwrite(ctlr, SchedDramAddr5000, PCIWADDR(ctlr->sched.s)>>10); + csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2); + + /* Enable chain mode for all queues, except command queue. */ + prphwrite(ctlr, SchedQChainSel5000, 0xfffef); + prphwrite(ctlr, SchedAggrSel5000, 0); + + for(q=0; q<nelem(ctlr->tx); q++){ + prphwrite(ctlr, SchedQueueRdptr5000 + q*4, 0); + csr32w(ctlr, HbusTargWptr, q << 8); + memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8, 0); + /* Set scheduler window size and frame limit. */ + memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8 + 4, 64<<16 | 64); + } + + /* Enable interrupts for all our 20 queues. */ + prphwrite(ctlr, SchedIntrMask5000, 0xfffff); + /* Identify TX FIFO rings (0-7). */ + prphwrite(ctlr, SchedTxFact5000, 0xff); + /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */ + for(q=0; q<7; q++){ + static uchar qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 }; + prphwrite(ctlr, SchedQueueStatus5000 + q*4, 0x00ff0018 | qid2fifo[q]); + } + nicunlock(ctlr); + + if(ctlr->type != Type5150){ + c[0] = 15; /* code */ + c[1] = 0; /* grup */ + c[2] = 1; /* ngroup */ + c[3] = 1; /* isvalid */ + put16(c+4, ctlr->eeprom.crystal); + cmd(ctlr, 176, c, 8); + } + + if(ctlr->type != Type4965){ + put32(c, ctlr->rfcfg.txantmask & 7); + cmd(ctlr, 152, c, 4); + } +} + +static void +addnode(Ctlr *ctlr, uchar id, uchar *addr) +{ + 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++; /* kid */ + p++; /* reserved */ + p += 16; /* key */ + if(ctlr->type != Type4965){ + p += 8; /* tcs */ + p += 8; /* rxmic */ + p += 8; /* txmic */ + p += 4; /* htflags */ + p += 4; /* mask */ + p += 2; /* disable tid */ + p += 2; /* reserved */ + p++; /* add ba tid */ + p++; /* del ba tid */ + p += 2; /* add ba ssn */ + p += 4; /* reserved */ + } + cmd(ctlr, 24, c, p - c); +} + +void +rxon(Ether *edev) +{ + Ctlr *ctlr; + uchar b[128-4], *p; + + ctlr = edev->ctlr; + memset(p = b, 0, sizeof(b)); + memmove(p, edev->ea, 6); p += 8; /* myaddr */ + p += 8; /* bssid */ + memmove(p, edev->ea, 6); p += 8; /* wlap */ + *p++ = 3; /* mode */ + *p++ = 0; /* air (?) */ + /* rxchain */ + put16(p, ((ctlr->rfcfg.rxantmask & 7)<<1) | (2<<10) | (2<<12)); + p += 2; + *p++ = 0xff; /* ofdm mask (not yet negotiated) */ + *p++ = 0x0f; /* cck mask (not yet negotiated) */ + p += 2; /* associd (?) */ + put32(p, (1<<15)|(1<<30)|(1<<0)); /* flags (TSF | CTS_TO_SELF | 24GHZ) */ + p += 4; + put32(p, 4|1); /* filter (MULTICAST|PROMISC) */ + p += 4; + *p++ = ctlr->channel; /* chan */ + p++; /* reserved */ + *p++ = 0xff; /* ht single mask */ + *p++ = 0xff; /* ht dual mask */ + if(ctlr->type != Type4965){ + *p++ = 0xff; /* ht triple mask */ + p++; /* reserved */ + put16(p, 0); p += 2; /* acquisition */ + p += 2; /* reserved */ + } + cmd(ctlr, 16, b, p - b); +} + +static struct ratetab { + uchar rate; + uchar plcp; + uchar flags; +} ratetab[] = { + { 2, 10, 1<<1 }, + { 4, 20, 1<<1 }, + { 11, 55, 1<<1 }, + { 22, 110, 1<<1 }, + { 12, 0xd, 0 }, + { 18, 0xf, 0 }, + { 24, 0x5, 0 }, + { 36, 0x7, 0 }, + { 48, 0x9, 0 }, + { 72, 0xb, 0 }, + { 96, 0x1, 0 }, + { 108, 0x3, 0 }, + { 120, 0x3, 0 } +}; + +static void +transmit(Wifi *wifi, Wnode *, Block *b) +{ + uchar c[Tcmdsize], *p; + Ctlr *ctlr; + + ctlr = wifi->ether->ctlr; + + memset(p = c, 0, sizeof(c)); + put16(p, BLEN(b)); + p += 2; + p += 2; /* lnext */ + put32(p, 0); /* flags */ + p += 4; + put32(p, 0); + p += 4; /* scratch */ + *p++ = ratetab[2].plcp; /* plcp */ + *p++ = ratetab[2].flags | (1<<6); /* rflags */ + p += 2; /* xflags */ + *p++ = 15; /* id (5000 only) */ + *p++ = 0; /* security */ + *p++ = 0; /* linkq */ + p++; /* reserved */ + p += 16; /* key */ + p += 2; /* fnext */ + p += 2; /* reserved */ + put32(p, ~0); /* lifetime */ + p += 4; + /* scratch ptr? not clear what this is for */ + put32(p, PCIWADDR(ctlr->kwpage)); + p += 5; + *p++ = 60; /* rts ntries */ + *p++ = 15; /* data ntries */ + *p++ = 0; /* tid */ + put16(p, 0); /* timeout */ + p += 2; + p += 2; /* txop */ + qcmd(ctlr, 0, 28, c, p - c, b); +} + +static int +rbplant(Ctlr *ctlr, int i) +{ + Block *b; + + b = iallocb(Rbufsize + 256); + if(b == nil) + return -1; + b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, 256); + memset(b->rp, 0, Rdscsize); + ctlr->rx.b[i] = b; + ctlr->rx.p[i] = PCIWADDR(b->rp) >> 8; + return 0; +} + +static long +iwlctl(Ether *edev, void *buf, long n) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(ctlr->wifi) + return wifictl(ctlr->wifi, buf, n); + return 0; +} + +static long +iwlifstat(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) +{ + Ctlr *ctlr; + char buf[64]; + int i; + + ctlr = edev->ctlr; + ctlr->channel = 3; + for(i = 0; i < edev->nopt; i++){ + if(strncmp(edev->opt[i], "channel=", 8) == 0) + ctlr->channel = atoi(edev->opt[i]+8); + else + if(strncmp(edev->opt[i], "essid=", 6) == 0){ + snprint(buf, sizeof(buf), "essid %s", edev->opt[i]+6); + if(!waserror()){ + wifictl(ctlr->wifi, buf, strlen(buf)); + poperror(); + } + } + } +} + +static void +iwlattach(Ether *edev) +{ + FWImage *fw; + Ctlr *ctlr; + char *err; + RXQ *rx; + TXQ *tx; + int i, q; + + ctlr = edev->ctlr; + eqlock(ctlr); + if(waserror()){ + qunlock(ctlr); + nexterror(); + } + if(ctlr->attached == 0){ + if(ctlr->wifi == nil) + ctlr->wifi = wifiattach(edev, transmit); + + if(ctlr->fw == nil){ + fw = readfirmware("iwn-5000"); + print("#l%d: firmware: rev %ux, build %ud, size %ux+%ux+%ux+%ux+%ux\n", + edev->ctlrno, + fw->rev, fw->build, + fw->main.text.size, fw->main.data.size, + fw->init.text.size, fw->init.data.size, + fw->boot.text.size); + ctlr->fw = fw; + } + + rx = &ctlr->rx; + rx->i = 0; + if(rx->b == nil) + rx->b = malloc(sizeof(Block*) * Nrx); + if(rx->p == nil) + rx->p = mallocalign(sizeof(u32int) * Nrx, 256, 0, 0); + if(rx->s == nil) + rx->s = mallocalign(Rstatsize, 16, 0, 0); + if(rx->b == nil || rx->p == nil || rx->s == nil) + error("no memory for rx ring"); + memset(rx->s, 0, Rstatsize); + 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) + error("no memory for rx descriptors"); + } + + for(q=0; q<nelem(ctlr->tx); q++){ + tx = &ctlr->tx[q]; + tx->i = 0; + tx->n = 0; + if(tx->b == nil) + tx->b = malloc(sizeof(Block*) * Ntx); + if(tx->d == nil) + tx->d = mallocalign(Tdscsize * Ntx, 256, 0, 0); + if(tx->c == nil) + tx->c = mallocalign(Tcmdsize * Ntx, 4, 0, 0); + if(tx->b == nil || tx->d == nil || tx->c == nil) + error("no memory for tx ring"); + memset(tx->d, 0, Tdscsize * Ntx); + } + + if(ctlr->sched.s == nil) + ctlr->sched.s = mallocalign(512 * nelem(ctlr->tx) * 2, 1024, 0, 0); + if(ctlr->kwpage == nil) + ctlr->kwpage = mallocalign(4096, 4096, 0, 0); + + if((err = niclock(ctlr)) != nil) + error(err); + prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain); + nicunlock(ctlr); + + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi); + + if((err = niclock(ctlr)) != nil) + error(err); + prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis); + nicunlock(ctlr); + + if((err = niclock(ctlr)) != nil) + error(err); + csr32w(ctlr, FhRxConfig, 0); + csr32w(ctlr, FhRxWptr, 0); + csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p) >> 8); + csr32w(ctlr, FhStatusWptr, PCIWADDR(ctlr->rx.s) >> 4); + csr32w(ctlr, FhRxConfig, + FhRxConfigEna | + FhRxConfigIgnRxfEmpty | + FhRxConfigIrqDstHost | + FhRxConfigSingleFrame | + (Nrxlog << FhRxConfigNrbdShift)); + csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7); + nicunlock(ctlr); + + if((err = niclock(ctlr)) != nil) + error(err); + prphwrite(ctlr, SchedTxFact5000, 0); + csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4); + for(q=0; q<nelem(ctlr->tx); q++) + csr32w(ctlr, FhCbbcQueue + q*4, PCIWADDR(ctlr->tx[q].d) >> 8); + nicunlock(ctlr); + for(i=0; i<8; i++) + csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked); + + ctlr->ie = Idefmask; + csr32w(ctlr, Imr, ctlr->ie); + csr32w(ctlr, Isr, ~0); + + if(ctlr->type >= Type6000) + csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff); + + if((err = loadfirmware1(ctlr, 0x00000000, ctlr->fw->main.text.data, ctlr->fw->main.text.size)) != nil) + error(err); + if((err = loadfirmware1(ctlr, 0x00800000, ctlr->fw->main.data.data, ctlr->fw->main.data.size)) != nil) + error(err); + + csr32w(ctlr, Reset, 0); + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive) + error("firmware boot failed"); + + postboot(ctlr); + + setoptions(edev); + + rxon(edev); + addnode(ctlr, 15, edev->bcast); + + edev->prom = 1; + edev->link = 1; + ctlr->attached = 1; + } + qunlock(ctlr); + poperror(); +} + +static void +receive(Ctlr *ctlr) +{ + Block *b, *bb; + uchar *d; + RXQ *rx; + TXQ *tx; + uint hw; + + rx = &ctlr->rx; + if(rx->s == nil || rx->b == nil) + return; + for(hw = get16(rx->s) % 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++; + USED(flags); + idx = *d++; + qid = *d++; + + len &= 0x3fff; + if(len < 4 || type == 0) + continue; + + len -= 4; + + switch(type){ + case 1: /* microcontroller ready */ + setfwinfo(ctlr, d, len); + break; + case 24: /* add node done */ + break; + case 28: /* tx done */ + if(qid >= nelem(ctlr->tx)) + break; + tx = &ctlr->tx[qid]; + if(tx->n == 0) + break; + bb = tx->b[idx]; + if(bb != nil){ + tx->b[idx] = nil; + freeb(bb); + } + tx->n--; + break; + case 102: /* calibration result (Type5000 only) + break; + case 103: /* calibration done (Type5000 only) + break; + case 130: /* start scan */ + break; + case 132: /* stop scan */ + break; + case 156: /* rx statistics */ + break; + case 157: /* beacon statistics */ + break; + case 161: /* state changed */ + break; + case 162: /* beacon missed */ + break; + case 192: /* rx phy */ + break; + case 195: /* rx done */ + if(d + 60 > b->lim) + break; + d += 60; + case 193: /* mpdu rx done */ + if(d + 4 > b->lim) + break; + len = get16(d); d += 4; + 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 197: /* rx compressed ba */ + break; + } + /* paranoia: clear the descriptor */ + memset(b->rp, 0, Rdscsize); + } + csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7); +} + +static void +iwlinterrupt(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 | Irxperiodic)) || (fhisr & Ifhrx)) + receive(ctlr); + if(isr & Ierr){ + iprint("#l%d: fatal firmware error\n", edev->ctlrno); + dumpctlr(ctlr); + } + ctlr->wait.m |= isr; + if(ctlr->wait.m & ctlr->wait.w){ + ctlr->wait.r = ctlr->wait.m & ctlr->wait.w; + ctlr->wait.m &= ~ctlr->wait.r; + wakeup(&ctlr->wait); + } +done: + csr32w(ctlr, Imr, ctlr->ie); + iunlock(ctlr); +} + +static Ctlr *iwlhead, *iwltail; + +static void +iwlpci(void) +{ + Pcidev *pdev; + + pdev = nil; + while(pdev = pcimatch(pdev, 0, 0)) { + Ctlr *ctlr; + void *mem; + + if(pdev->ccrb != 2 || pdev->ccru != 0x80) + continue; + if(pdev->vid != 0x8086) + continue; + + switch(pdev->did){ + default: + continue; + case 0x4236: /* WiFi Link 5300 AGN */ + break; + } + + /* Clear device-specific "PCI retry timeout" register (41h). */ + if(pcicfgr8(pdev, 0x41) != 0) + pcicfgw8(pdev, 0x41, 0); + + /* Clear interrupt disable bit. Hardware bug workaround. */ + if(pdev->pcr & 0x400){ + pdev->pcr &= ~0x400; + pcicfgw16(pdev, PciPCR, pdev->pcr); + } + + pcisetbme(pdev); + pcisetpms(pdev, 0); + + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil) { + print("iwl: 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("iwl: can't map %8.8luX\n", pdev->mem[0].bar); + free(ctlr); + continue; + } + ctlr->nic = mem; + ctlr->pdev = pdev; + ctlr->type = (csr32r(ctlr, Rev) >> 4) & 0xF; + + if(iwlhead != nil) + iwltail->link = ctlr; + else + iwlhead = ctlr; + iwltail = ctlr; + } +} + +static int +iwlpnp(Ether* edev) +{ + Ctlr *ctlr; + + if(iwlhead == nil) + iwlpci(); +again: + for(ctlr = iwlhead; 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 = iwlinterrupt; + edev->attach = iwlattach; + edev->ifstat = iwlifstat; + edev->ctl = iwlctl; + edev->promiscuous = nil; + edev->multicast = nil; + edev->mbps = 10; + + if(iwlinit(edev) < 0){ + edev->ctlr = nil; + goto again; + } + + return 0; +} + +void +etheriwllink(void) +{ + addethercard("iwl", iwlpnp); +} diff --git a/sys/src/9/pc/mkfile b/sys/src/9/pc/mkfile index dc9d95d54..6965ffc7c 100644 --- a/sys/src/9/pc/mkfile +++ b/sys/src/9/pc/mkfile @@ -120,6 +120,7 @@ devusb.$O usbuhci.$O usbohci.$O usbehci.$O: ../port/usb.h trap.$O: /sys/include/tos.h uartaxp.$O: uartaxp.i etherm10g.$O: etherm10g2k.i etherm10g4k.i +etheriwl.$O: wifi.h init.h:D: ../port/initcode.c init9.c $CC ../port/initcode.c diff --git a/sys/src/9/pc/pccpuf b/sys/src/9/pc/pccpuf index b3873db50..b34e46c2d 100644 --- a/sys/src/9/pc/pccpuf +++ b/sys/src/9/pc/pccpuf @@ -67,6 +67,7 @@ link ethersink ethersmc devi82365 cis etherwavelan wavelan devi82365 cis pci + etheriwl pci wifi ethermedium netdevmedium loopbackmedium diff --git a/sys/src/9/pc/pcf b/sys/src/9/pc/pcf index 4c9465ae0..308faf7b9 100644 --- a/sys/src/9/pc/pcf +++ b/sys/src/9/pc/pcf @@ -68,6 +68,7 @@ link ethersink ethersmc devi82365 cis etherwavelan wavelan devi82365 cis pci + etheriwl pci wifi ethermedium pcmciamodem netdevmedium diff --git a/sys/src/9/pc/wifi.c b/sys/src/9/pc/wifi.c new file mode 100644 index 000000000..3864c6b14 --- /dev/null +++ b/sys/src/9/pc/wifi.c @@ -0,0 +1,424 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "pool.h" +#include "ureg.h" +#include "../port/error.h" +#include "../port/netif.h" + +#include "etherif.h" +#include "wifi.h" + +typedef struct SNAP SNAP; +struct SNAP +{ + uchar dsap; + uchar ssap; + uchar control; + uchar orgcode[3]; + uchar type[2]; +}; + +enum { + SNAPHDRSIZE = 8, +}; + +static char Snone[] = "new"; +static char Sconn[] = "connecting"; +static char Sauth[] = "authenticated"; +static char Sunauth[] = "unauthentictaed"; +static char Sassoc[] = "associated"; +static char Sunassoc[] = "unassociated"; + +void +wifiiq(Wifi *wifi, Block *b) +{ + SNAP s; + Wifipkt w; + Etherpkt *e; + + if(BLEN(b) < WIFIHDRSIZE) + goto drop; + memmove(&w, b->rp, WIFIHDRSIZE); + switch(w.fc[0] & 0x0c){ + case 0x00: /* management */ + if((w.fc[1] & 3) != 0x00) /* STA->STA */ + break; + qpass(wifi->iq, b); + return; + case 0x04: /* control */ + break; + case 0x08: /* data */ + b->rp += WIFIHDRSIZE; + switch(w.fc[0] & 0xf0){ + case 0x80: + b->rp += 2; + if(w.fc[1] & 0x80) + b->rp += 4; + case 0x00: + break; + default: + goto drop; + } + if(BLEN(b) < SNAPHDRSIZE || b->rp[0] != 0xAA || b->rp[1] != 0xAA || b->rp[2] != 0x03) + break; + memmove(&s, b->rp, SNAPHDRSIZE); + b->rp += SNAPHDRSIZE-ETHERHDRSIZE; + e = (Etherpkt*)b->rp; + switch(w.fc[1] & 0x03){ + case 0x00: /* STA->STA */ + memmove(e->d, w.a1, Eaddrlen); + memmove(e->s, w.a2, Eaddrlen); + break; + case 0x01: /* STA->AP */ + memmove(e->d, w.a3, Eaddrlen); + memmove(e->s, w.a2, Eaddrlen); + break; + case 0x02: /* AP->STA */ + memmove(e->d, w.a1, Eaddrlen); + memmove(e->s, w.a3, Eaddrlen); + break; + case 0x03: /* AP->AP */ + goto drop; + } + memmove(e->type, s.type, 2); + etheriq(wifi->ether, b, 1); + return; + } +drop: + freeb(b); +} + +static void +wifitx(Wifi *wifi, Block *b) +{ + Wifipkt *w; + uint seq; + + seq = wifi->txseq++; + seq <<= 4; + + w = (Wifipkt*)b->rp; + w->dur[0] = 0; + w->dur[1] = 0; + w->seq[0] = seq; + w->seq[1] = seq>>8; + + (*wifi->transmit)(wifi, wifi->bss, b); +} + + +static Wnode* +nodelookup(Wifi *wifi, uchar *bssid, int new) +{ + Wnode *wn, *nn; + + if(memcmp(bssid, wifi->ether->bcast, Eaddrlen) == 0) + return nil; + for(wn = nn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){ + if(memcmp(wn->bssid, bssid, Eaddrlen) == 0){ + wn->lastseen = MACHP(0)->ticks; + return wn; + } + if(wn != wifi->bss && wn->lastseen < nn->lastseen) + nn = wn; + } + if(!new) + return nil; + memmove(nn->bssid, bssid, Eaddrlen); + nn->lastseen = MACHP(0)->ticks; + return nn; +} + +static void +sendauth(Wifi *wifi, Wnode *bss) +{ + Wifipkt *w; + Block *b; + uchar *p; + + b = allocb(WIFIHDRSIZE + 3*2); + w = (Wifipkt*)b->wp; + w->fc[0] = 0xB0; /* auth request */ + w->fc[1] = 0x00; /* STA->STA */ + memmove(w->a1, bss->bssid, Eaddrlen); /* ??? */ + memmove(w->a2, wifi->ether->ea, Eaddrlen); + memmove(w->a3, bss->bssid, Eaddrlen); + b->wp += WIFIHDRSIZE; + p = b->wp; + *p++ = 0; /* alg */ + *p++ = 0; + *p++ = 1; /* seq */ + *p++ = 0; + *p++ = 0; /* status */ + *p++ = 0; + b->wp = p; + wifitx(wifi, b); +} + +static void +sendassoc(Wifi *wifi, Wnode *bss) +{ + Wifipkt *w; + Block *b; + uchar *p; + + b = allocb(WIFIHDRSIZE + 128); + w = (Wifipkt*)b->wp; + w->fc[0] = 0x00; /* assoc request */ + w->fc[1] = 0x00; /* STA->STA */ + memmove(w->a1, bss->bssid, Eaddrlen); /* ??? */ + memmove(w->a2, wifi->ether->ea, Eaddrlen); + memmove(w->a3, bss->bssid, Eaddrlen); + b->wp += WIFIHDRSIZE; + p = b->wp; + *p++ = 1; /* capinfo */ + *p++ = 0; + *p++ = 16; /* interval */ + *p++ = 16>>8; + *p++ = 0; /* SSID */ + *p = strlen(bss->ssid); + memmove(p+1, bss->ssid, *p); + p += 1+*p; + *p++ = 1; /* RATES */ + *p++ = 1; + *p++ = 0x96; + b->wp = p; + wifitx(wifi, b); +} + +static void +recvassoc(Wifi *wifi, Wnode *wn, uchar *d, int len) +{ + uint s; + + if(len < 2+2+2) + return; + + d += 2; /* caps */ + s = d[0] | d[1]<<8; + d += 2; + switch(s){ + case 0x00: + wn->aid = d[0] | d[1]<<8; + wifi->status = Sassoc; + break; + default: + wifi->status = Sunassoc; + return; + } +} + +static void +recvbeacon(Wifi *wifi, Wnode *wn, uchar *d, int len) +{ + uchar *e, *x; + + if(len < 8+2+2) + return; + + d += 8; /* timestamp */ + wn->ival = d[0] | d[1]<<8; + d += 2; + wn->cap = d[0] | d[1]<<8; + d += 2; + + for(e = d + len; d+2 <= e; d = x){ + d += 2; + x = d + d[-1]; + switch(d[-2]){ + case 0: /* SSID */ + len = 0; + while(len < 32 && d+len < x && d[len] != 0) + len++; + if(len == 0) + continue; + if(len != strlen(wn->ssid) || strncmp(wn->ssid, (char*)d, len) != 0){ + strncpy(wn->ssid, (char*)d, len); + wn->ssid[len] = 0; + if(wifi->bss == nil && strcmp(wifi->essid, wn->ssid) == 0){ + wifi->bss = wn; + wifi->status = Sconn; + sendauth(wifi, wn); + } + } + return; + } + } +} + +static void +wifiproc(void *arg) +{ + Wifi *wifi; + Wifipkt *w; + Wnode *wn; + Block *b; + + b = nil; + wifi = arg; + for(;;){ + if(b != nil) + freeb(b); + if((b = qbread(wifi->iq, 100000)) == nil) + break; + w = (Wifipkt*)b->rp; + switch(w->fc[0] & 0xf0){ + case 0x50: /* probe response */ + case 0x80: /* beacon */ + if((wn = nodelookup(wifi, w->a3, 1)) == nil) + continue; + b->rp += WIFIHDRSIZE; + recvbeacon(wifi, wn, b->rp, BLEN(b)); + continue; + } + if((wn = nodelookup(wifi, w->a3, 0)) == nil) + continue; + if(wn != wifi->bss) + continue; + switch(w->fc[0] & 0xf0){ + case 0x10: /* assoc response */ + case 0x30: /* reassoc response */ + b->rp += WIFIHDRSIZE; + recvassoc(wifi, wn, b->rp, BLEN(b)); + break; + case 0xb0: /* auth */ + wifi->status = Sauth; + sendassoc(wifi, wn); + break; + case 0xc0: /* deauth */ + wifi->status = Sunauth; + break; + } + } + pexit("wifi in queue closed", 0); +} + +static void +wifietheroq(Wifi *wifi, Block *b) +{ + Etherpkt e; + Wifipkt *w; + SNAP *s; + + if(BLEN(b) < ETHERHDRSIZE){ + freeb(b); + return; + } + memmove(&e, b->rp, ETHERHDRSIZE); + + b->rp += ETHERHDRSIZE; + b = padblock(b, WIFIHDRSIZE + SNAPHDRSIZE); + + w = (Wifipkt*)b->rp; + w->fc[0] = 0x08; /* data */ + w->fc[1] = 0x01; /* STA->AP */ + memmove(w->a1, wifi->bss ? wifi->bss->bssid : wifi->ether->bcast, Eaddrlen); + memmove(w->a2, e.s, Eaddrlen); + memmove(w->a3, e.d, Eaddrlen); + + s = (SNAP*)(b->rp + WIFIHDRSIZE); + s->dsap = s->ssap = 0xAA; + s->control = 0x03; + s->orgcode[0] = 0; + s->orgcode[1] = 0; + s->orgcode[2] = 0; + memmove(s->type, e.type, 2); + + wifitx(wifi, b); +} + +static void +wifoproc(void *arg) +{ + Ether *ether; + Wifi *wifi; + Block *b; + + wifi = arg; + ether = wifi->ether; + while((b = qbread(ether->oq, 1000000)) != nil) + wifietheroq(wifi, b); + pexit("ether out queue closed", 0); +} + +Wifi* +wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*)) +{ + Wifi *wifi; + + wifi = malloc(sizeof(Wifi)); + wifi->ether = ether; + wifi->iq = qopen(8*1024, 0, 0, 0); + wifi->transmit = transmit; + wifi->status = Snone; + + kproc("wifi", wifiproc, wifi); + kproc("wifo", wifoproc, wifi); + + return wifi; +} + +long +wifictl(Wifi *wifi, void *buf, long n) +{ + Cmdbuf *cb; + Wnode *wn; + + cb = nil; + if(waserror()){ + free(cb); + nexterror(); + } + cb = parsecmd(buf, n); + if(cb->f[0] && strcmp(cb->f[0], "essid") == 0){ + if(cb->f[1] == nil){ + /* TODO senddeauth(wifi); */ + wifi->essid[0] = 0; + wifi->bss = nil; + } else { + strncpy(wifi->essid, cb->f[1], 32); + wifi->essid[32] = 0; + for(wn=wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++) + if(strcmp(wifi->essid, wn->ssid) == 0){ + wifi->bss = wn; + wifi->status = Sconn; + sendauth(wifi, wn); + break; + } + } + } + poperror(); + free(cb); + return n; +} + +long +wifistat(Wifi *wifi, void *buf, long n, ulong off) +{ + static uchar zeros[Eaddrlen]; + char *s, *p, *e; + Wnode *wn; + long now; + + p = s = smalloc(4096); + e = s + 4096; + + p = seprint(p, e, "status: %s\n", wifi->status); + p = seprint(p, e, "essid: %s\n", wifi->essid); + p = seprint(p, e, "bssid: %E\n", wifi->bss ? wifi->bss->bssid : zeros); + + now = MACHP(0)->ticks; + for(wn=wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){ + if(wn->lastseen == 0) + continue; + p = seprint(p, e, "node: %E %.4x %d %ld %s\n", + wn->bssid, wn->cap, wn->ival, TK2MS(now - wn->lastseen), wn->ssid); + } + n = readstr(off, buf, n, s); + free(s); + return n; +} diff --git a/sys/src/9/pc/wifi.h b/sys/src/9/pc/wifi.h new file mode 100644 index 000000000..363722a8f --- /dev/null +++ b/sys/src/9/pc/wifi.h @@ -0,0 +1,52 @@ +typedef struct Wnode Wnode; +typedef struct Wifi Wifi; + +typedef struct Wifipkt Wifipkt; + +struct Wifipkt +{ + uchar fc[2]; + uchar dur[2]; + uchar a1[Eaddrlen]; + uchar a2[Eaddrlen]; + uchar a3[Eaddrlen]; + uchar seq[2]; +}; + +enum { + WIFIHDRSIZE = 2+2+3*6+2, +}; + +struct Wnode +{ + uchar bssid[Eaddrlen]; + char ssid[32+2]; + int ival; + int cap; + + long lastseen; + + int aid; +}; + +struct Wifi +{ + Ether *ether; + + Queue *iq; + char *status; + void (*transmit)(Wifi*, Wnode*, Block*); + + Wnode node[16]; + Wnode *bss; + + uint txseq; + char essid[32+2]; +}; + +Wifi *wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*)); +void wifiiq(Wifi*, Block*); + +long wifistat(Wifi*, void*, long, ulong); +long wifictl(Wifi*, void*, long); + |