--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#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;
+}