]> git.lizzy.rs Git - plan9front.git/commitdiff
etheriwl: experimental intel wifi link driver
authorcinap_lenrek <cinap_lenrek@gmx.de>
Sat, 9 Feb 2013 02:19:50 +0000 (03:19 +0100)
committercinap_lenrek <cinap_lenrek@gmx.de>
Sat, 9 Feb 2013 02:19:50 +0000 (03:19 +0100)
sys/src/9/pc/etheriwl.c [new file with mode: 0644]
sys/src/9/pc/mkfile
sys/src/9/pc/pccpuf
sys/src/9/pc/pcf
sys/src/9/pc/wifi.c [new file with mode: 0644]
sys/src/9/pc/wifi.h [new file with mode: 0644]

diff --git a/sys/src/9/pc/etheriwl.c b/sys/src/9/pc/etheriwl.c
new file mode 100644 (file)
index 0000000..0e4b16e
--- /dev/null
@@ -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);
+}
index dc9d95d54a7736ec534c3a71a5f56deddbd7a85f..6965ffc7cc7584b1e24b9aed25805e4e006b7e88 100644 (file)
@@ -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
index b3873db50b38baa53161993a99e412d804454560..b34e46c2d6b3b3e5dc90f27de058cad04ca619ca 100644 (file)
@@ -67,6 +67,7 @@ link
        ethersink
        ethersmc        devi82365 cis
        etherwavelan    wavelan devi82365 cis pci
+       etheriwl        pci wifi
        ethermedium
        netdevmedium
        loopbackmedium
index 4c9465ae0aca5ae6f07f410342b52ed48acd64b2..308faf7b9835e8f26a7b07803fe7431979e0cdc7 100644 (file)
@@ -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 (file)
index 0000000..3864c6b
--- /dev/null
@@ -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 (file)
index 0000000..363722a
--- /dev/null
@@ -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);
+