diff options
author | Sigrid Solveig Haflínudóttir <sigrid@ftrv.se> | 2023-04-19 19:49:11 +0000 |
---|---|---|
committer | Sigrid Solveig Haflínudóttir <sigrid@ftrv.se> | 2023-04-19 19:49:11 +0000 |
commit | de727cdb3b617b44ec223261044b9046350e0e61 (patch) | |
tree | be8e525d1b2091aa8cde9e2754bf26bea489c579 /sys/src/9/port | |
parent | 6118f598e0caff4cba0d5838afa98631b1acf0e1 (diff) |
imx8: make etheriwl work on MNT Reform, move it to port/ (thanks cinap); tested with 6205
Diffstat (limited to 'sys/src/9/port')
-rw-r--r-- | sys/src/9/port/etheriwl.c | 4565 | ||||
-rw-r--r-- | sys/src/9/port/portmkfile | 1 |
2 files changed, 4566 insertions, 0 deletions
diff --git a/sys/src/9/port/etheriwl.c b/sys/src/9/port/etheriwl.c new file mode 100644 index 000000000..af5592747 --- /dev/null +++ b/sys/src/9/port/etheriwl.c @@ -0,0 +1,4565 @@ +/* + * Intel WiFi Link driver. + * + * Written without any documentation but Damien Bergamini's + * iwn(4) and Stefan Sperling's iwm(4) OpenBSD driver sources. + * Requires Intel firmware to be present in /lib/firmware/iw[nm]-* + * on attach. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/pci.h" +#include "../port/error.h" +#include "../port/netif.h" +#include "../port/etherif.h" +#include "../port/wifi.h" + +enum { + MaxQueue = 24*1024, /* total buffer is 2*MaxQueue: 48k at 22Mbit ≅ 20ms */ + + Ntxlog = 8, + Ntx = 1<<Ntxlog, + Ntxqmax = MaxQueue/1500, + + Nrxlog = 8, + Nrx = 1<<Nrxlog, + + Rstatsize = 16, + + Rbufsize = 4*1024, + Rdscsize = 8, + + Tdscsize = 128, + Tcmdsize = 140, + + FWPageshift = 12, + FWPagesize = 1<<FWPageshift, + FWBlockshift = 3, + FWBlockpages = 1<<FWBlockshift, + FWBlocksize = 1<<(FWBlockshift + FWPageshift), +}; + +/* registers */ +enum { + Cfg = 0x000, /* config register */ + CfgMacDashShift = 0, + CfgMacDashMask = 3<<CfgMacDashShift, + CfgMacStepShift = 2, + CfgMacStepMask = 3<<CfgMacStepShift, + + MacSi = 1<<8, + RadioSi = 1<<9, + + CfgPhyTypeShift = 10, + CfgPhyTypeMask = 3<<CfgPhyTypeShift, + CfgPhyDashShift = 12, + CfgPhyDashMask = 3<<CfgPhyDashShift, + CfgPhyStepShift = 14, + CfgPhyStepMask = 3<<CfgPhyStepShift, + + EepromLocked = 1<<21, + NicReady = 1<<22, + HapwakeL1A = 1<<23, + PrepareDone = 1<<25, + Prepare = 1<<27, + EnablePme = 1<<28, + + 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, + + GpDrv = 0x050, + GpDrvCalV6 = 1<<2, + GpDrv1X2 = 1<<3, + GpDrvRadioIqInvert = 1<<7, + + Led = 0x094, + LedBsmCtrl = 1<<5, + LedOn = 0x38, + LedOff = 0x78, + + UcodeGp1Clr = 0x05c, + UcodeGp1RfKill = 1<<1, + UcodeGp1CmdBlocked = 1<<2, + UcodeGp1CtempStopRf = 1<<3, + + ShadowRegCtrl = 0x0a8, + + MboxSet = 0x088, + MboxSetOsAlive = 1<<5, + + Giochicken = 0x100, + L1AnoL0Srx = 1<<23, + DisL0Stimer = 1<<29, + + AnaPll = 0x20c, + + Dbghpetmem = 0x240, + Dbglinkpwrmgmt = 0x250, + + MemRaddr = 0x40c, + MemWaddr = 0x410, + MemWdata = 0x418, + MemRdata = 0x41c, + + PrphWaddr = 0x444, + PrphRaddr = 0x448, + PrphWdata = 0x44c, + PrphRdata = 0x450, + + HbusTargWptr = 0x460, + + UcodeLoadStatus = 0x1af0, +}; + +/* + * Flow-Handler registers. + */ +enum { + FhTfbdCtrl0 = 0x1900, // +q*8 + FhTfbdCtrl1 = 0x1904, // +q*8 + + FhKwAddr = 0x197c, + + FhSramAddr = 0x19a4, // +q*4 + + FhCbbcQueue0 = 0x19d0, // +q*4 + FhCbbcQueue16 = 0x1bf0, // +q*4 + FhCbbcQueue20 = 0x1b20, // +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, + + FhRxQ0Wptr = 0x1c80, // +q*4 (9000 mqrx) + + 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, + FhTxErrors = 0x1eb8, +}; + +/* + * 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, + + /* 8000 family */ + ReleaseCpuReset = 0x300c, + CpuResetBit = 0x1000000, + + LmpmChick = 0xa01ff8, + ExtAddr = 1, + + SbCpu1Status = 0xa01e30, + SbCpu2Status = 0xa01e34, + OscClk = 0xa04068, + OscClkCtrl = 1<<3, + UregChick = 0xa05c00, + UregChickMsiEnable = 1<<24, + + FhUcodeLoadStatus=0xa05c40, +}; + +/* + * RX ring for mqrx 9000 +*/ +enum { + RfhQ0FreeBase = 0xa08000, // +q*8 + RfhQ0FreeWptr = 0xa08080, // +q*4 + RfhQ0FreeRptr = 0xa080c0, // +q*4 + + RfhQ0UsedBase = 0xa08100, // +q*8 + RfhQ0UsedWptr = 0xa08180, // +q*4 + + RfhQ0SttsBase = 0xa08200, // +q*8 + + RfhGenCfg = 0xa09800, + RfhGenServiceDmaSnoop = 1<<0, + RfhGenRfhDmaSnoop = 1<<1, + RfhGenRbChunkSize64 = 0<<4, + RfhGenRbChunkSize128 = 1<<4, + + RfhGenStatus = 0xa09808, + RfhGenStatusDmaIdle = 1<<31, + + RfhRxqActive = 0xa0980c, + + RfhDmaCfg = 0xa09820, + RfhDma1KSizeShift = 16, + RfhDmaNrbdShift = 20, + RfhDmaMinRbSizeShift = 24, + RfhDmaDropTooLarge = 1<<26, + RfhDmaEnable = 1<<31, +}; + +/* + * TX scheduler registers. + */ +enum { + SchedBase = 0xa02c00, + SchedSramAddr = SchedBase, + + SchedDramAddr4965 = SchedBase+0x010, + SchedTxFact4965 = SchedBase+0x01c, + SchedQueueRdptr4965 = SchedBase+0x064, // +q*4 + SchedQChainSel4965 = SchedBase+0x0d0, + SchedIntrMask4965 = SchedBase+0x0e4, + SchedQueueStatus4965 = SchedBase+0x104, // +q*4 + + SchedDramAddr = SchedBase+0x008, + SchedTxFact = SchedBase+0x010, + SchedQueueWrptr = SchedBase+0x018, // +q*4 + SchedQueueRdptr = SchedBase+0x068, // +q*4 + SchedQChainSel = SchedBase+0x0e8, + SchedIntrMask = SchedBase+0x108, + + SchedQueueStatus = SchedBase+0x10c, // +q*4 + + SchedGpCtrl = SchedBase+0x1a8, + Enable31Queues = 1<<0, + AutoActiveMode = 1<<18, + + SchedChainExtEn = SchedBase+0x244, + SchedAggrSel = SchedBase+0x248, + SchedEnCtrl = SchedBase+0x254, + + SchedQueueRdptr20 = SchedBase+0x2b4, // +q*4 + SchedQueueStatus20 = SchedBase+0x334, // +q*4 +}; + +enum { + SchedCtxOff4965 = 0x380, + SchedCtxLen4965 = 416, + + SchedCtxOff = 0x600, // +q*8 + + SchedSttsOff = 0x6A0, // +q*16 + + SchedTransTblOff = 0x7E0, // +q*2 +}; + +/* + * uCode TLV api + */ +enum { + /* api[0] */ + UcodeApiSta = 1<<30, +}; + +/* + * uCode capabilities + */ +enum { + /* capa[0] */ + UcodeCapLar = 1<<1, + + /* capa[1] */ + UcodeCapQuota = 1<<12, + + /* capa[2] */ + UcodeCapLar2 = 1<<9, +}; + +enum { + FilterPromisc = 1<<0, + FilterCtl = 1<<1, + FilterMulticast = 1<<2, + FilterNoDecrypt = 1<<3, + FilterNoDecryptMcast = 1<<4, + FilterBSS = 1<<5, + FilterBeacon = 1<<6, +}; + +enum { + RFlag24Ghz = 1<<0, + RFlagCCK = 1<<1, + RFlagAuto = 1<<2, + RFlagShSlot = 1<<4, + RFlagShPreamble = 1<<5, + RFlagNoDiversity = 1<<7, + RFlagAntennaA = 1<<8, + RFlagAntennaB = 1<<9, + RFlagTSF = 1<<15, + RFlagCTSToSelf = 1<<30, +}; + +enum { + TFlagNeedProtection = 1<<0, + TFlagNeedRTS = 1<<1, + TFlagNeedCTS = 1<<2, + TFlagNeedACK = 1<<3, + TFlagLinkq = 1<<4, + TFlagImmBa = 1<<6, + TFlagFullTxOp = 1<<7, + TFlagBtDis = 1<<12, + TFlagAutoSeq = 1<<13, + TFlagMoreFrag = 1<<14, + TFlagInsertTs = 1<<16, + TFlagNeedPadding = 1<<20, +}; + +enum { + CmdAdd = 1, + CmdModify, + CmdRemove, +}; + +typedef struct FWInfo FWInfo; +typedef struct FWImage FWImage; +typedef struct FWSect FWSect; +typedef struct FWBlock FWBlock; +typedef struct FWMem FWMem; + +typedef struct TXQ TXQ; +typedef struct RXQ RXQ; + +typedef struct Station Station; + +typedef struct Ctlr Ctlr; + +struct FWSect +{ + uchar *data; + uint addr; + uint size; +}; + +struct FWImage +{ + struct { + int nsect; + union { + struct { + FWSect text; + FWSect data; + }; + FWSect sect[16]; + }; + struct { + u32int flowmask; + u32int eventmask; + } defcalib; + } init, main; + + struct { + FWSect text; + } boot; + + uint rev; + uint build; + char descr[64+1]; + + u32int flags; + u32int capa[4]; + u32int api[4]; + + u32int physku; + + u32int pagedmemsize; + + uchar data[]; +}; + +struct FWInfo +{ + int valid; + + u16int status; + u16int flags; + + u32int major; + u32int minor; + uchar type; + uchar subtype; + + u32int scdptr; + u32int regptr; + u32int logptr; + u32int errptr; + u32int tstamp; + + struct { + u32int major; + u32int minor; + u32int errptr; + u32int logptr; + } umac; +}; + +struct FWBlock +{ + uint size; + uchar *p; +}; + +struct FWMem +{ + uchar *css; + + uint npage; + uint nblock; + + FWBlock block[32]; +}; + +struct TXQ +{ + uint n; + uint i; + Block **b; + uchar *d; + uchar *c; + + uint lastcmd; + + Rendez; + QLock; +}; + +struct RXQ +{ + uint i; + int psz; + Block **b; + uchar *s; + uchar *p; + uchar *u; +}; + +struct Station +{ + int id; +}; + +struct Ctlr { + Lock; + QLock; + + Ctlr *link; + uvlong port; + Pcidev *pdev; + Ether *edev; + Wifi *wifi; + + char *fwname; + int mqrx; + int family; + int type; + uint step; + uint dash; + + int power; + int broken; + int attached; + + u32int ie; + + u32int *nic; + uchar *kwpage; + + /* assigned sta ids in hardware sta table or -1 if unassigned */ + Station bcast; + Station bss; + + u32int rxflags; + u32int rxfilter; + + int phyid; + int macid; + int bindid; + + /* current receiver settings */ + uchar bssid[Eaddrlen]; + int channel; + int prom; + int aid; + + struct { + Rendez; + int id; + int active; + } te; + + uvlong systime; + + RXQ rx; + TXQ tx[7]; + + int ndma; + int ntxq; + + struct { + Rendez; + u32int m; + u32int w; + } wait; + + struct { + uchar type; + uchar step; + uchar dash; + uchar pnum; + uchar txantmask; + uchar rxantmask; + } rfcfg; + + struct { + int otp; + uint off; + + uchar version; + uchar type; + u16int volt; + u16int temp; + u16int rawtemp; + + char regdom[4+1]; + + u32int crystal; + } eeprom; + + struct { + u32int version; + + void *buf; + int len; + + int off; + int ret; + int type; + int sts; + } nvm; + + struct { + union { + Block *cmd[21]; + struct { + Block *cfg; + Block *nch; + Block *papd[9]; + Block *txp[9]; + }; + }; + int done; + } calib; + + struct { + u32int base; + uchar *s; + } sched; + + FWInfo fwinfo; + FWImage *fw; + + FWMem fwmem; +}; + +/* controller types */ +enum { + Type4965 = 0, + Type5300 = 2, + Type5350 = 3, + Type5150 = 4, + Type5100 = 5, + Type1000 = 6, + Type6000 = 7, + Type6050 = 8, + Type6005 = 11, /* also Centrino Advanced-N 6030, 6235 */ + Type2030 = 12, + Type2000 = 16, + + Type7260 = 20, +}; + +static struct ratetab { + uchar rate; + uchar plcp; + uchar flags; +} ratetab[] = { + { 2, 10, RFlagCCK }, + { 4, 20, RFlagCCK }, + { 11, 55, RFlagCCK }, + { 22, 110, RFlagCCK }, + + { 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 uchar iwlrates[] = { + 0x80 | 2, + 0x80 | 4, + 0x80 | 11, + 0x80 | 22, + + 0x80 | 12, + 0x80 | 18, + 0x80 | 24, + 0x80 | 36, + 0x80 | 48, + 0x80 | 72, + 0x80 | 96, + 0x80 | 108, + 0x80 | 120, + 0 +}; + +static char *fwname[32] = { + [Type4965] "iwn-4965", + [Type5300] "iwn-5000", + [Type5350] "iwn-5000", + [Type5150] "iwn-5150", + [Type5100] "iwn-5000", + [Type1000] "iwn-1000", + [Type6000] "iwn-6000", + [Type6050] "iwn-6050", + [Type6005] "iwn-6005", /* see in iwlattach() below */ + [Type2030] "iwn-2030", + [Type2000] "iwn-2000", +}; + +static char *qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block); +static char *flushq(Ctlr *ctlr, uint qid); +static char *cmd(Ctlr *ctlr, uint code, uchar *data, int size); + +#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 +put64(uchar *p, uvlong v) +{ + *((u64int*)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); + if(ctlr->family >= 8000) + microdelay(2); + for(i=0; i<1500; i++){ + if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna) + return 0; + microdelay(10); + } + return "niclock: timeout"; +} + +static void +nicunlock(Ctlr *ctlr) +{ + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq); +} + +static u32int +prphread(Ctlr *ctlr, uint off) +{ + off &= 0xfffff; + csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off); + coherence(); + return csr32r(ctlr, PrphRdata); +} +static void +prphwrite(Ctlr *ctlr, uint off, u32int data) +{ + off &= 0xfffff; + csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off); + coherence(); + csr32w(ctlr, PrphWdata, data); +} + +static void +prphwrite64(Ctlr *ctlr, uint off, u64int data) +{ + prphwrite(ctlr, off, data & 0xFFFFFFFF); + prphwrite(ctlr, off+4, data >> 32); +} + +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; + + i = &ctlr->fwinfo; + switch(len){ + case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4: + case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4 + 4+4 + 1+1 + 2 + 4+4: + i->status = get16(d); d += 2; + i->flags = get16(d); d += 2; + + i->minor = *d++; + i->major = *d++; + d += 2; // id + d++; // api minor + d++; // api major + i->subtype = *d++; + i->type = *d++; + d++; // mac + d++; // opt + d += 2; // reserved2 + + i->tstamp = get32(d); d += 4; + i->errptr = get32(d); d += 4; + i->logptr = get32(d); d += 4; + i->regptr = get32(d); d += 4; + d += 4; // dbgm_config_ptr + d += 4; // alive counter ptr + + i->scdptr = get32(d); d += 4; + + if(len < 1+1+2+1+1+1+1+1+1+2+4+4+4+4+4 + 4+4+4+1+1+2+4+4) + break; + + d += 4; // fwrd addr + d += 4; // fwrd size + + i->umac.minor = *d++; + i->umac.major = *d++; + d++; // id + d += 2; + i->umac.errptr = get32(d); d += 4; + i->umac.logptr = get32(d); d += 4; + + i->valid = (i->status == 0xcafe); + break; + + case 2+2 + 4+4 + 1+1 + 1+1 + 4+4+4+4+4+4 + 4 + 4+4 + 4+4+4+4: + i->status = get16(d); d += 2; + i->flags = get16(d); d += 2; + + i->minor = get32(d); + d += 4; + i->major = get32(d); + d += 4; + i->subtype = *d++; + i->type = *d++; + d++; // mac + d++; // opt + + i->tstamp = get32(d); d += 4; + i->errptr = get32(d); d += 4; + i->logptr = get32(d); d += 4; + i->regptr = get32(d); d += 4; + d += 4; // dbgm_config_ptr + d += 4; // alive counter ptr + + i->scdptr = get32(d); + d += 4; + + d += 4; // fwrd addr + d += 4; // fwrd size + + i->umac.minor = get32(d); d += 4; + i->umac.major = get32(d); d += 4; + i->umac.errptr = get32(d); d += 4; + i->umac.logptr = get32(d); d += 4; + + i->valid = (i->status == 0xcafe); + break; + + default: + if(len < 32) + break; + i->minor = *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 = 1; + } + USED(d); +} + +static void +printfwinfo(Ctlr *ctlr) +{ + FWInfo *i = &ctlr->fwinfo; + + print("fwinfo: status=%.4ux flags=%.4ux\n", + i->status, i->flags); + + print("fwinfo: ver %ud.%ud type %ud.%ud\n", + i->major, i->minor, i->type, i->subtype); + print("fwinfo: scdptr=%.8ux\n", i->scdptr); + print("fwinfo: regptr=%.8ux\n", i->regptr); + print("fwinfo: logptr=%.8ux\n", i->logptr); + print("fwinfo: errptr=%.8ux\n", i->errptr); + + print("fwinfo: ts=%.8ux\n", i->tstamp); + + print("fwinfo: umac ver %ud.%ud\n", i->umac.major, i->umac.minor); + print("fwinfo: umac errptr %.8ux\n", i->umac.errptr); + print("fwinfo: umac logptr %.8ux\n", i->umac.logptr); +} + +static void +dumpctlr(Ctlr *ctlr) +{ + u32int dump[13]; + int i; + + print("lastcmd: %ud (0x%ux)\n", ctlr->tx[4].lastcmd, ctlr->tx[4].lastcmd); + + if(!ctlr->fwinfo.valid || 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); + + if(ctlr->family >= 7000){ + print( "error:\tid %ux, trm_hw_status %.8ux %.8ux,\n" + "\tbranchlink2 %.8ux, interruptlink %.8ux %.8ux,\n" + "\terrordata %.8ux %.8ux %.8ux\n", + dump[1], dump[2], dump[3], + dump[4], dump[5], dump[6], + dump[7], dump[8], dump[9]); + } else { + 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; + microdelay(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, s; + int i; + + w = 0; + off += ctlr->eeprom.off; + 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; + microdelay(5); + } + if(i == 10) + return "eepromread: timeout"; + if(ctlr->eeprom.otp){ + s = csr32r(ctlr, OtpromGp); + if(s & EccUncorrStts) + return "eepromread: otprom ecc error"; + if(s & EccCorrStts) + csr32w(ctlr, OtpromGp, s); + } + *out++ = w >> 16; + if(count > 1) + *out++ = w >> 24; + } + return 0; +} + +static char* +handover(Ctlr *ctlr) +{ + int i, j; + + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady); + for(i=0; i<5; i++){ + if(csr32r(ctlr, Cfg) & NicReady) + goto Ready; + microdelay(10); + } + + if(ctlr->family >= 7000){ + csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31)); + delay(1); + } + + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare); + for(i=0; i<750; i++){ + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady); + for(j=0; j<5; j++){ + if(csr32r(ctlr, Cfg) & NicReady) + goto Ready; + microdelay(10); + } + microdelay(200); + } + + return "handover: timeout"; +Ready: + if(ctlr->family >= 7000) + csr32w(ctlr, MboxSet, csr32r(ctlr, MboxSet) | MboxSetOsAlive); + return nil; +} + +static char* +clockwait(Ctlr *ctlr) +{ + int i; + + /* Set "initialization complete" bit. */ + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | InitDone); + for(i=0; i<2500; i++){ + if(csr32r(ctlr, Gpc) & MacClockReady) + return 0; + microdelay(10); + } + return "clockwait: timeout"; +} + +static char* +poweron(Ctlr *ctlr) +{ + int capoff; + char *err; + + if(ctlr->family >= 7000){ + /* Reset entire device */ + csr32w(ctlr, Reset, (1<<7)); + delay(5); + } + + if(ctlr->family < 8000){ + /* 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->family < 7000){ + 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(ctlr->mqrx){ + /* Newer cards default to MSIX? */ + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, UregChick, UregChickMsiEnable); + nicunlock(ctlr); + } + + /* Enable the oscillator to count wake up time for L1 exit. (weird W/A) */ + if(ctlr->type == Type7260){ + if((err = niclock(ctlr)) != nil) + return err; + + prphread(ctlr, OscClk); + prphread(ctlr, OscClk); + microdelay(20); + + prphwrite(ctlr, OscClk, prphread(ctlr, OscClk) | OscClkCtrl); + + prphread(ctlr, OscClk); + prphread(ctlr, OscClk); + + nicunlock(ctlr); + } + + if(ctlr->family < 8000){ + 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); + microdelay(20); + + /* Disable L1-Active. */ + prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11)); + + nicunlock(ctlr); + } + + ctlr->power = 1; + + return 0; +} + +static void +poweroff(Ctlr *ctlr) +{ + int i, j; + + csr32w(ctlr, Reset, 1); + + /* Disable interrupts */ + ctlr->ie = 0; + csr32w(ctlr, Imr, 0); + csr32w(ctlr, Isr, ~0); + csr32w(ctlr, FhIsr, ~0); + + /* Stop scheduler */ + if(ctlr->family >= 7000 || ctlr->type != Type4965) + prphwrite(ctlr, SchedTxFact, 0); + else + prphwrite(ctlr, SchedTxFact4965, 0); + + /* Stop TX ring */ + if(niclock(ctlr) == nil){ + for(i = 0; i < ctlr->ndma; i++){ + csr32w(ctlr, FhTxConfig + i*32, 0); + for(j = 0; j < 200; j++){ + if(csr32r(ctlr, FhTxStatus) & (0x10000<<i)) + break; + microdelay(20); + } + } + nicunlock(ctlr); + } + + /* Stop RX ring */ + if(niclock(ctlr) == nil){ + if(ctlr->mqrx){ + prphwrite(ctlr, RfhDmaCfg, 0); + for(j = 0; j < 1000; j++){ + if(prphread(ctlr, RfhGenStatus) & RfhGenStatusDmaIdle) + break; + microdelay(10); + } + } else { + csr32w(ctlr, FhRxConfig, 0); + for(j = 0; j < 1000; j++){ + if(csr32r(ctlr, FhRxStatus) & 0x1000000) + break; + microdelay(10); + } + } + nicunlock(ctlr); + } + + if(ctlr->family <= 7000){ + /* Disable DMA */ + if(niclock(ctlr) == nil){ + prphwrite(ctlr, ApmgClkDis, DmaClkRqt); + nicunlock(ctlr); + } + microdelay(5); + } + + if(ctlr->family >= 7000){ + csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31)); + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare|EnablePme); + delay(1); + csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) & ~(1<<31)); + delay(5); + } + + /* Stop busmaster DMA activity. */ + csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<9)); + for(j = 0; j < 100; j++){ + if(csr32r(ctlr, Reset) & (1<<8)) + break; + microdelay(10); + } + + /* Reset the entire device. */ + csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<7)); + delay(5); + + /* Clear "initialization complete" bit. */ + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~InitDone); + + ctlr->power = 0; +} + +static char* +rominit(Ctlr *ctlr) +{ + uint prev, last; + uchar buf[2]; + char *err; + int i; + + ctlr->eeprom.otp = 0; + ctlr->eeprom.off = 0; + if(ctlr->type < Type1000 || (csr32r(ctlr, OtpromGp) & DevSelOtp) == 0) + return nil; + + /* Wait for clock stabilization before accessing prph. */ + if((err = clockwait(ctlr)) != nil) + return err; + + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | ResetReq); + microdelay(5); + prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) & ~ResetReq); + nicunlock(ctlr); + + /* Set auto clock gate disable bit for HW with OTP shadow RAM. */ + if(ctlr->type != Type1000) + csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31)); + + csr32w(ctlr, EepromGp, csr32r(ctlr, EepromGp) & ~0x00000180); + + /* Clear ECC status. */ + csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) | (EccCorrStts | EccUncorrStts)); + + ctlr->eeprom.otp = 1; + if(ctlr->type != Type1000) + return nil; + + /* Switch to absolute addressing mode. */ + csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) & ~RelativeAccess); + + /* + * Find the block before last block (contains the EEPROM image) + * for HW without OTP shadow RAM. + */ + prev = last = 0; + for(i=0; i<3; i++){ + if((err = eepromread(ctlr, buf, 2, last)) != nil) + return err; + if(get16(buf) == 0) + break; + prev = last; + last = get16(buf); + } + if(i == 0 || i >= 3) + return "rominit: missing eeprom image"; + + ctlr->eeprom.off = prev+1; + return nil; +} + +static int +iwlinit(Ether *edev) +{ + Ctlr *ctlr; + char *err; + uchar b[4]; + uint u, caloff, regoff; + + ctlr = edev->ctlr; + + /* Clear device-specific "PCI retry timeout" register (41h). */ + if(pcicfgr8(ctlr->pdev, 0x41) != 0) + pcicfgw8(ctlr->pdev, 0x41, 0); + + /* Clear interrupt disable bit. Hardware bug workaround. */ + if(ctlr->pdev->pcr & 0x400){ + ctlr->pdev->pcr &= ~0x400; + pcicfgw16(ctlr->pdev, PciPCR, ctlr->pdev->pcr); + } + + ctlr->type = csr32r(ctlr, Rev); + if(ctlr->family >= 8000){ + ctlr->type &= 0xFFFF; + ctlr->dash = 0; + ctlr->step = ctlr->type & 15, ctlr->type >>= 4; + } else { + ctlr->type &= 0x1FF; + ctlr->dash = ctlr->type & 3, ctlr->type >>= 2; + ctlr->step = ctlr->type & 3, ctlr->type >>= 2; + if(ctlr->fwname == nil && fwname[ctlr->type] == nil){ + print("iwl: unsupported controller type %d\n", ctlr->type); + return -1; + } + } + + if((err = handover(ctlr)) != nil) + goto Err; + + /* >= 7000 family needs firmware loaded to access NVM */ + if(ctlr->family >= 7000) + return 0; + + 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 = rominit(ctlr)) != nil) + goto Err2; + if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){ + eepromunlock(ctlr); + goto Err; + } + if((err = eepromread(ctlr, b, 2, 0x048)) != nil){ + Err2: + 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, 2, 0x66)) != nil) + goto Err2; + regoff = get16(b); + if((err = eepromread(ctlr, b, 4, regoff+1)) != nil) + goto Err2; + strncpy(ctlr->eeprom.regdom, (char*)b, 4); + ctlr->eeprom.regdom[4] = 0; + if((err = eepromread(ctlr, b, 2, 0x67)) != nil) + goto Err2; + caloff = get16(b); + if((err = eepromread(ctlr, b, 4, caloff)) != nil) + goto Err2; + ctlr->eeprom.version = b[0]; + ctlr->eeprom.type = b[1]; + ctlr->eeprom.volt = get16(b+2); + + ctlr->eeprom.temp = 0; + ctlr->eeprom.rawtemp = 0; + if(ctlr->type == Type2030 || ctlr->type == Type2000){ + if((err = eepromread(ctlr, b, 2, caloff + 0x12a)) != nil) + goto Err2; + ctlr->eeprom.temp = get16(b); + if((err = eepromread(ctlr, b, 2, caloff + 0x12b)) != nil) + goto Err2; + ctlr->eeprom.rawtemp = get16(b); + } + + if(ctlr->type != Type4965 && ctlr->type != Type5150){ + if((err = eepromread(ctlr, b, 4, caloff + 0x128)) != nil) + goto Err2; + ctlr->eeprom.crystal = get32(b); + } + eepromunlock(ctlr); + + switch(ctlr->type){ + case Type4965: + ctlr->rfcfg.txantmask = 3; + ctlr->rfcfg.rxantmask = 7; + break; + case Type5100: + ctlr->rfcfg.txantmask = 2; + ctlr->rfcfg.rxantmask = 3; + break; + case Type6000: + if(ctlr->pdev->did == 0x422c || ctlr->pdev->did == 0x4230){ + ctlr->rfcfg.txantmask = 6; + ctlr->rfcfg.rxantmask = 6; + } + break; + } + poweroff(ctlr); + return 0; +Err: + print("iwlinit: %s\n", err); + poweroff(ctlr); + return -1; +} + +static char* +crackfw(FWImage *i, uchar *data, uint size, int alt) +{ + uchar *p, *e; + FWSect *s; + uint t, l; + + 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[64] = 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--; + for(;p < e; p += (l + 3) & ~3){ + if(p + 8 > e) + goto Tooshort; + + t = get32(p), p += 4; + l = get32(p), p += 4; + if(p + l > e) + goto Tooshort; + + if((t >> 16) != 0 && (t >> 16) != alt) + continue; + + switch(t & 0xFFFF){ + case 1: + s = &i->main.text; + if(i->main.nsect < 1) + i->main.nsect = 1; + s->addr = 0x00000000; + goto Sect; + case 2: + s = &i->main.data; + if(i->main.nsect < 2) + i->main.nsect = 2; + s->addr = 0x00800000; + goto Sect; + case 3: + s = &i->init.text; + if(i->init.nsect < 1) + i->init.nsect = 1; + s->addr = 0x00000000; + goto Sect; + case 4: + s = &i->init.data; + if(i->init.nsect < 2) + i->init.nsect = 2; + s->addr = 0x00800000; + goto Sect; + case 5: + s = &i->boot.text; + s->addr = 0x00000000; + goto Sect; + case 18: + if(l < 4) + goto Tooshort; + i->flags = get32(p); + break; + case 19: + if(i->main.nsect >= nelem(i->main.sect)) + return "too many main sections"; + s = &i->main.sect[i->main.nsect++]; + goto Chunk; + case 20: + if(i->init.nsect >= nelem(i->init.sect)) + return "too many init sections"; + s = &i->init.sect[i->init.nsect++]; + Chunk: + if(l < 4) + goto Tooshort; + s->addr = get32(p); + p += 4, l -= 4; + Sect: + s->size = l; + s->data = p; + break; + case 22: + if(l < 12) + goto Tooshort; + switch(get32(p)){ + case 0: + i->main.defcalib.flowmask = get32(p+4); + i->main.defcalib.eventmask = get32(p+8); + break; + case 1: + i->init.defcalib.flowmask = get32(p+4); + i->init.defcalib.eventmask = get32(p+8); + break; + } + break; + case 23: + if(l < 4) + goto Tooshort; + i->physku = get32(p); + break; + case 29: + if(l < 8) + goto Tooshort; + t = get32(p); + if(t >= nelem(i->api)) + goto Tooshort; + i->api[t] = get32(p+4); + break; + case 30: + if(l < 8) + goto Tooshort; + t = get32(p); + if(t >= nelem(i->capa)) + goto Tooshort; + i->capa[t] = get32(p+4); + break; + case 32: + if(l < 4) + goto Tooshort; + i->pagedmemsize = get32(p) & -FWPagesize; + break; + } + } + } 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; + + i->main.nsect = 2; + i->init.nsect = 2; + i->main.text.addr = 0x00000000; + i->main.data.addr = 0x00800000; + i->init.text.addr = 0x00000000; + i->init.data.addr = 0x00800000; + } + 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; +} + + +static int +gotirq(void *arg) +{ + Ctlr *ctlr = arg; + return (ctlr->wait.m & ctlr->wait.w) != 0; +} + +static u32int +irqwait(Ctlr *ctlr, u32int mask, int timeout) +{ + u32int r; + + ilock(ctlr); + r = ctlr->wait.m & mask; + if(r == 0){ + ctlr->wait.w = mask; + iunlock(ctlr); + if(!waserror()){ + tsleep(&ctlr->wait, gotirq, ctlr, timeout); + poperror(); + } + ilock(ctlr); + ctlr->wait.w = 0; + r = ctlr->wait.m & mask; + } + ctlr->wait.m &= ~r; + iunlock(ctlr); + return r; +} + +static int +rbplant(Ctlr *ctlr, uint i) +{ + Block *b; + + assert(i < Nrx); + + b = iallocb(Rbufsize*2); + if(b == nil) + return -1; + b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, Rbufsize); + memset(b->rp, 0, Rbufsize); + dmaflush(1, b->rp, Rbufsize); + coherence(); + ctlr->rx.b[i] = b; + + if(ctlr->mqrx) + put64(ctlr->rx.p + (i<<3), PCIWADDR(b->rp)); + else + put32(ctlr->rx.p + (i<<2), PCIWADDR(b->rp) >> 8); + dmaflush(1, ctlr->rx.p + i*ctlr->rx.psz, ctlr->rx.psz); + + return 0; +} + +static char* +initmem(Ctlr *ctlr) +{ + RXQ *rx; + TXQ *tx; + int i, q; + + if(ctlr->fw->pagedmemsize > 0){ + ctlr->fwmem.npage = ctlr->fw->pagedmemsize >> FWPageshift; + ctlr->fwmem.nblock = ctlr->fwmem.npage >> FWBlockshift; + if(ctlr->fwmem.nblock >= nelem(ctlr->fwmem.block)-1) + return "paged memory size too big"; + for(i = 0; i < ctlr->fwmem.nblock; i++) + ctlr->fwmem.block[i].size = FWBlocksize; + ctlr->fwmem.block[i].size = (ctlr->fwmem.npage % FWBlockpages) << FWPageshift; + if(ctlr->fwmem.block[i].size != 0) + ctlr->fwmem.nblock++; + for(i = 0; i < ctlr->fwmem.nblock; i++){ + if(ctlr->fwmem.block[i].p == nil){ + ctlr->fwmem.block[i].p = mallocalign(ctlr->fwmem.block[i].size, FWPagesize, 0, 0); + if(ctlr->fwmem.block[i].p == nil) + return "no memory for firmware block"; + } + } + if(ctlr->fwmem.css == nil){ + if((ctlr->fwmem.css = mallocalign(FWPagesize, FWPagesize, 0, 0)) == nil) + return "no memory for firmware css page"; + } + } + + rx = &ctlr->rx; + if(ctlr->mqrx){ + if(rx->u == nil) + rx->u = mallocalign(4 * Nrx, 4096, 0, 0); + if(rx->u == nil) + return "no memory for rx rings"; + memset(rx->u, 0, 4 * Nrx); + dmaflush(1, rx->u, 4 * Nrx); + rx->psz = 8; + } else { + rx->u = nil; + rx->psz = 4; + } + if(rx->p == nil) + rx->p = mallocalign(rx->psz*Nrx, 4096, 0, 0); + if(rx->s == nil) + rx->s = mallocalign(Rstatsize, 4096, 0, 0); + if(rx->b == nil) + rx->b = malloc(sizeof(Block*) * Nrx); + if(rx->p == nil || rx->b == nil || rx->s == nil) + return "no memory for rx ring"; + memset(rx->s, 0, Rstatsize); + for(i=0; i<Nrx; i++){ + if(rx->b[i] != nil){ + freeb(rx->b[i]); + rx->b[i] = nil; + } + if(rbplant(ctlr, i) < 0) + return "no memory for rx descriptors"; + } + rx->i = 0; + + ctlr->ndma = 8; + ctlr->ntxq = 20; + if(ctlr->family >= 7000) { + ctlr->ntxq = 31; + } else { + if(ctlr->type == Type4965) { + ctlr->ndma = 7; + ctlr->ntxq = 16; + } + } + + if(ctlr->sched.s == nil) + ctlr->sched.s = mallocalign((256+64)*2 * ctlr->ntxq, 4096, 0, 0); + if(ctlr->sched.s == nil) + return "no memory for sched buffer"; + memset(ctlr->sched.s, 0, (256+64)*2 * ctlr->ntxq); + dmaflush(1, ctlr->sched.s, (256+64)*2 * ctlr->ntxq); + + for(q=0; q < nelem(ctlr->tx); q++){ + tx = &ctlr->tx[q]; + if(tx->b == nil) + tx->b = malloc(sizeof(Block*) * Ntx); + if(tx->d == nil) + tx->d = mallocalign(Tdscsize * Ntx, 4096, 0, 0); + if(tx->c == nil) + tx->c = mallocalign(Tcmdsize * Ntx, 4096, 0, 0); + if(tx->b == nil || tx->d == nil || tx->c == nil) + return "no memory for tx ring"; + memset(tx->d, 0, Tdscsize * Ntx); + dmaflush(1, tx->d, Tdscsize * Ntx); + memset(tx->c, 0, Tcmdsize * Ntx); + dmaflush(1, tx->c, Tcmdsize * Ntx); + for(i=0; i<Ntx; i++){ + if(tx->b[i] != nil){ + freeblist(tx->b[i]); + tx->b[i] = nil; + } + } + tx->i = 0; + tx->n = 0; + tx->lastcmd = 0; + } + + if(ctlr->kwpage == nil) + ctlr->kwpage = mallocalign(4096, 4096, 0, 0); + if(ctlr->kwpage == nil) + return "no memory for kwpage"; + memset(ctlr->kwpage, 0, 4096); + dmaflush(1, ctlr->kwpage, 4096); + + return nil; +} + +static char* +reset(Ctlr *ctlr) +{ + char *err; + int q, i; + + if(ctlr->power) + poweroff(ctlr); + if((err = initmem(ctlr)) != nil) + return err; + if((err = poweron(ctlr)) != nil) + return err; + + if(ctlr->family <= 7000){ + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain); + nicunlock(ctlr); + } + + if(ctlr->family >= 7000){ + u32int u; + + u = csr32r(ctlr, Cfg); + + u &= ~(RadioSi|MacSi|CfgMacDashMask|CfgMacStepMask|CfgPhyTypeMask|CfgPhyStepMask|CfgPhyDashMask); + + u |= (ctlr->step << CfgMacStepShift) & CfgMacStepMask; + u |= (ctlr->dash << CfgMacDashShift) & CfgMacDashMask; + + u |= ctlr->rfcfg.type << CfgPhyTypeShift; + u |= ctlr->rfcfg.step << CfgPhyStepShift; + u |= ctlr->rfcfg.dash << CfgPhyDashShift; + + csr32w(ctlr, Cfg, u); + + } else { + csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi); + } + + if(ctlr->family < 8000){ + if((err = niclock(ctlr)) != nil) + return err; + if(ctlr->family == 7000 || ctlr->type != Type4965) + prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis); + nicunlock(ctlr); + } + if(ctlr->family < 7000){ + if((err = niclock(ctlr)) != nil) + return err; + if(ctlr->type == Type1000){ + /* + * Select first Switching Voltage Regulator (1.32V) to + * solve a stability issue related to noisy DC2DC line + * in the silicon of 1000 Series. + */ + prphwrite(ctlr, ApmgDigitalSvr, + (prphread(ctlr, ApmgDigitalSvr) & ~(0xf<<5)) | (3<<5)); + } + if((ctlr->type == Type6005 || ctlr->type == Type6050) && ctlr->eeprom.version == 6) + csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvCalV6); + if(ctlr->type == Type6005) + csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrv1X2); + if(ctlr->type == Type2030 || ctlr->type == Type2000) + csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvRadioIqInvert); + nicunlock(ctlr); + } + + if((err = niclock(ctlr)) != nil) + return err; + + dmaflush(1, ctlr->rx.s, Rstatsize); + + if(ctlr->mqrx){ + /* Stop RX DMA. */ + prphwrite(ctlr, RfhDmaCfg, 0); + /* Disable RX used and free queue operation. */ + prphwrite(ctlr, RfhRxqActive, 0); + + prphwrite64(ctlr, RfhQ0SttsBase, PCIWADDR(ctlr->rx.s)); + prphwrite64(ctlr, RfhQ0FreeBase, PCIWADDR(ctlr->rx.p)); + prphwrite64(ctlr, RfhQ0UsedBase, PCIWADDR(ctlr->rx.u)); + + prphwrite(ctlr, RfhQ0FreeWptr, 0); + prphwrite(ctlr, RfhQ0FreeRptr, 0); + prphwrite(ctlr, RfhQ0UsedWptr, 0); + + /* Enable RX DMA */ + prphwrite(ctlr, RfhDmaCfg, + RfhDmaEnable | + RfhDmaDropTooLarge | + ((Rbufsize/1024) << RfhDma1KSizeShift) | + (3 << RfhDmaMinRbSizeShift) | + (Nrxlog << RfhDmaNrbdShift)); + + /* Enable RX DMA snooping. */ + prphwrite(ctlr, RfhGenCfg, + RfhGenServiceDmaSnoop | + RfhGenRfhDmaSnoop | + RfhGenRbChunkSize128); + + /* Enable Q0 */ + prphwrite(ctlr, RfhRxqActive, (1 << 16) | 1); + delay(1); + + csr32w(ctlr, FhRxQ0Wptr, (Nrx-1) & ~7); + delay(1); + } else { + 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); + } + + for(i = 0; i < ctlr->ndma; i++) + csr32w(ctlr, FhTxConfig + i*32, 0); + + if(ctlr->family >= 7000 || ctlr->type != Type4965) + prphwrite(ctlr, SchedTxFact, 0); + else + prphwrite(ctlr, SchedTxFact4965, 0); + + if(ctlr->family >= 7000){ + prphwrite(ctlr, SchedEnCtrl, 0); + prphwrite(ctlr, SchedGpCtrl, prphread(ctlr, SchedGpCtrl) + | Enable31Queues*(ctlr->ntxq == 31) + | AutoActiveMode); + for(q = 0; q < ctlr->ntxq; q++) + prphwrite(ctlr, (q<20? SchedQueueStatus: SchedQueueStatus20) + q*4, 1 << 19); + } + + csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4); + for(q = 0; q < ctlr->ntxq; q++){ + i = q < nelem(ctlr->tx) ? q : nelem(ctlr->tx)-1; + if(q < 16) + csr32w(ctlr, FhCbbcQueue0 + q*4, PCIWADDR(ctlr->tx[i].d) >> 8); + else if(q < 20) + csr32w(ctlr, FhCbbcQueue16 + (q-16)*4, PCIWADDR(ctlr->tx[i].d) >> 8); + else + csr32w(ctlr, FhCbbcQueue20 + (q-20)*4, PCIWADDR(ctlr->tx[i].d) >> 8); + } + + if(ctlr->family >= 7000 || ctlr->type >= Type6000) + csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff); + + nicunlock(ctlr); + + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked); + + ctlr->systime = 0; + + ctlr->broken = 0; + ctlr->wait.m = 0; + ctlr->wait.w = 0; + + ctlr->bcast.id = -1; + ctlr->bss.id = -1; + + ctlr->phyid = -1; + ctlr->macid = -1; + ctlr->bindid = -1; + ctlr->te.id = -1; + ctlr->te.active = 0; + ctlr->aid = 0; + + if(ctlr->family >= 9000) + csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | 0x4000000); + + ctlr->ie = Idefmask; + csr32w(ctlr, Imr, ctlr->ie); + csr32w(ctlr, Isr, ~0); + + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill); + + return nil; +} + +static char* +sendmccupdate(Ctlr *ctlr, char *mcc) +{ + uchar c[2+1+1+4+5*4], *p; + + memset(p = c, 0, sizeof(c)); + *p++ = mcc[1]; + *p++ = mcc[0]; + *p++ = 0; + *p++ = 0; // reserved + if(ctlr->fw->capa[2] & UcodeCapLar2){ + p += 4; + p += 5*4; + } + return cmd(ctlr, 200, c, p - c); +} + +static char* +sendbtcoexadv(Ctlr *ctlr) +{ + static u32int btcoex3wire[12] = { + 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, + 0xcc00ff28, 0x0000aaaa, 0xcc00aaaa, 0x0000aaaa, + 0xc0004000, 0x00004000, 0xf0005000, 0xf0005000, + }; + + uchar c[Tcmdsize], *p; + char *err; + int i; + + /* set BT config */ + memset(c, 0, sizeof(c)); + p = c; + + if(ctlr->family >= 7000){ + put32(p, 3); + p += 4; + put32(p, (1<<4)); + p += 4; + } else if(ctlr->type == Type2030){ + *p++ = 145; /* flags */ + p++; /* lead time */ + *p++ = 5; /* max kill */ + *p++ = 1; /* bt3 t7 timer */ + put32(p, 0xffff0000); /* kill ack */ + p += 4; + put32(p, 0xffff0000); /* kill cts */ + p += 4; + *p++ = 2; /* sample time */ + *p++ = 0xc; /* bt3 t2 timer */ + p += 2; /* bt4 reaction */ + for (i = 0; i < nelem(btcoex3wire); i++){ + put32(p, btcoex3wire[i]); + p += 4; + } + p += 2; /* bt4 decision */ + put16(p, 0xff); /* valid */ + p += 2; + put32(p, 0xf0); /* prio boost */ + p += 4; + p++; /* reserved */ + p++; /* tx prio boost */ + p += 2; /* rx prio boost */ + } + if((err = cmd(ctlr, 155, c, p-c)) != nil) + return err; + + if(ctlr->family >= 7000) + return nil; + + /* set BT priority */ + memset(c, 0, sizeof(c)); + p = c; + + *p++ = 0x6; /* init1 */ + *p++ = 0x7; /* init2 */ + *p++ = 0x2; /* periodic low1 */ + *p++ = 0x3; /* periodic low2 */ + *p++ = 0x4; /* periodic high1 */ + *p++ = 0x5; /* periodic high2 */ + *p++ = 0x6; /* dtim */ + *p++ = 0x8; /* scan52 */ + *p++ = 0xa; /* scan24 */ + p += 7; /* reserved */ + if((err = cmd(ctlr, 204, c, p-c)) != nil) + return err; + + /* force BT state machine change */ + memset(c, 0, sizeof(c)); + p = c; + + *p++ = 1; /* open */ + *p++ = 1; /* type */ + p += 2; /* reserved */ + if((err = cmd(ctlr, 205, c, p-c)) != nil) + return err; + + c[0] = 0; /* open */ + return cmd(ctlr, 205, c, p-c); +} + +static char* +sendpagingcmd(Ctlr *ctlr) +{ + uchar c[3*4 + 4 + 32*4], *p; + int i; + + p = c; + put32(p, (3<<8) | (ctlr->fwmem.npage % FWBlockpages)); + p += 4; + put32(p, FWPageshift + FWBlockshift); + p += 4; + put32(p, ctlr->fwmem.nblock); + p += 4; + + dmaflush(1, ctlr->fwmem.css, FWPagesize); + put32(p, PCIWADDR(ctlr->fwmem.css) >> FWPageshift); + p += 4; + + for(i = 0; i < ctlr->fwmem.nblock; i++){ + dmaflush(1, ctlr->fwmem.block[i].p, ctlr->fwmem.block[i].size); + put32(p, PCIWADDR(ctlr->fwmem.block[i].p) >> FWPageshift); + p += 4; + } + + for(; i < 32; i++){ + put32(p, 0); + p += 4; + } + + return cmd(ctlr, 79 | (1<<8), c, p-c); +} + +static char* +enablepaging(Ctlr *ctlr) +{ + FWSect *sect; + int nsect; + int i, j, o, n; + + if(ctlr->fwmem.css == nil) + return nil; + + if(1){ + /* clear everything */ + memset(ctlr->fwmem.css, 0, FWPagesize); + for(i = 0; i < ctlr->fwmem.nblock; i++) + memset(ctlr->fwmem.block[i].p, 0, ctlr->fwmem.block[i].size); + } + + if(ctlr->calib.done == 0){ + sect = ctlr->fw->init.sect; + nsect = ctlr->fw->init.nsect; + } else { + sect = ctlr->fw->main.sect; + nsect = ctlr->fw->main.nsect; + } + + /* first CSS segment */ + for(i = 0; i < nsect; i++) { + if(sect[i].addr == 0xAAAABBBB){ + i++; + break; + } + } + + if(i+1 >= nsect) + return "firmware misses CSS+paging sections"; + + if(sect[i].size > FWPagesize) + return "CSS section too big"; + if(sect[i+1].size > (ctlr->fwmem.npage << FWPageshift)) + return "paged section too big"; + + memmove(ctlr->fwmem.css, sect[i].data, sect[i].size); + + for(j = 0, o = 0; o < sect[i+1].size; o += n, j++){ + n = sect[i+1].size - o; + if(n > ctlr->fwmem.block[j].size) + n = ctlr->fwmem.block[j].size; + memmove(ctlr->fwmem.block[j].p, sect[i+1].data + o, n); + } + + return sendpagingcmd(ctlr); +} + +static int +readnvmsect1(Ctlr *ctlr, int type, void *data, int len, int off) +{ + uchar c[2+2+2+2], *p; + char *err; + + p = c; + *p++ = 0; // read op + *p++ = 0; // target + put16(p, type); + p += 2; + put16(p, off); + p += 2; + put16(p, len); + p += 2; + + ctlr->nvm.off = -1; + ctlr->nvm.ret = -1; + ctlr->nvm.type = -1; + ctlr->nvm.sts = -1; + + ctlr->nvm.buf = data; + ctlr->nvm.len = len; + + if((err = cmd(ctlr, 136, c, p - c)) != nil){ + ctlr->nvm.buf = nil; + ctlr->nvm.len = 0; + print("readnvmsect: %s\n", err); + return -1; + } + + if(ctlr->nvm.ret < len) + len = ctlr->nvm.ret; + + if(ctlr->nvm.sts != 0 || ctlr->nvm.off != off || (ctlr->nvm.type & 0xFF) != type) + return -1; + + return len; +} + +static int +readnvmsect(Ctlr *ctlr, int type, void *data, int len, int off) +{ + int n, r, o; + + for(o = 0; o < len; o += n){ + r = len - o; + if(r > 256) + r = 256; + if((n = readnvmsect1(ctlr, type, (char*)data + o, r, o+off)) < 0) + return -1; + if(n < r){ + o += n; + break; + } + } + return o; +} + +static char* +readnvmconfig(Ctlr *ctlr) +{ + uchar *ea = ctlr->edev->ea; + uchar buf[8]; + uint u; + char *err; + + if(readnvmsect(ctlr, 1, buf, 8, 0) != 8) + return "can't read nvm version"; + + ctlr->nvm.version = get16(buf); + if (ctlr->family == 7000) { + u = get16(buf + 2); + + ctlr->rfcfg.type = (u >> 4) & 3; + ctlr->rfcfg.step = (u >> 2) & 3; + ctlr->rfcfg.dash = (u >> 0) & 3; + ctlr->rfcfg.pnum = (u >> 6) & 3; + } else { + if(readnvmsect(ctlr, 12, buf, 8, 0) != 8) + return "can't read nvm phy config"; + + u = get32(buf); + + ctlr->rfcfg.type = (u >> 12) & 0xFFF; + ctlr->rfcfg.step = (u >> 8) & 15; + ctlr->rfcfg.dash = (u >> 4) & 15; + ctlr->rfcfg.pnum = (u >> 6) & 3; + + ctlr->rfcfg.txantmask = (u >> 24) & 15; + ctlr->rfcfg.rxantmask = (u >> 28) & 15; + } + if(ctlr->family >= 8000){ + if(readnvmsect(ctlr, 11, ea, Eaddrlen, 0x01<<1) != Eaddrlen){ + u32int a0, a1; + + if((err = niclock(ctlr)) != nil) + return err; + a0 = prphread(ctlr, 0xa03080); + a1 = prphread(ctlr, 0xa03084); + nicunlock(ctlr); + + ea[0] = a0 >> 24; + ea[1] = a0 >> 16; + ea[2] = a0 >> 8; + ea[3] = a0 >> 0; + ea[4] = a1 >> 8; + ea[5] = a1 >> 0; + } + } else { + /* + * 7260 gets angry if we read 6 bytes from 0x15*2. + * reading 8 bytes from 0x14*2 works fine. + */ + if(readnvmsect(ctlr, 0, buf, 8, 0x14<<1) != 8) + return "can't read ea from nvm"; + + /* byte order is 16 bit little endian. */ + ea[0] = buf[3]; + ea[1] = buf[2]; + ea[2] = buf[5]; + ea[3] = buf[4]; + ea[4] = buf[7]; + ea[5] = buf[6]; + } + memmove(ctlr->edev->addr, ea, Eaddrlen); + + return nil; +} + +static char* +sendtxantconfig(Ctlr *ctlr, uint val) +{ + uchar c[4]; + + put32(c, val); + return cmd(ctlr, 152, c, 4); +} + +static char* +sendphyconfig(Ctlr *ctlr, u32int physku, u32int flowmask, u32int eventmask) +{ + uchar c[3*4]; + + put32(c+0, physku); + put32(c+4, flowmask); + put32(c+8, eventmask); + return cmd(ctlr, 106, c, 3*4); +} + +static char* +delstation(Ctlr *ctlr, Station *sta) +{ + uchar c[4], *p; + char *err; + + if(sta->id < 0) + return nil; + + memset(p = c, 0, sizeof(c)); + *p = sta->id; + + if((err = cmd(ctlr, 25, c, 4)) != nil) + return err; + + sta->id = -1; + return nil; +} + +enum { + StaTypeLink = 0, + StaTypeGeneralPurpose, + StaTypeMulticast, + StaTypeTdlsLink, + StaTypeAux, +}; + +static char* +setstation(Ctlr *ctlr, int id, int type, uchar addr[6], Station *sta) +{ + uchar c[Tcmdsize], *p; + char *err; + + memset(p = c, 0, sizeof(c)); + + *p++ = 0; /* control (1 = update) */ + p++; /* reserved */ + if(ctlr->family >= 7000){ + put16(p, 0xffff); + p += 2; + put32(p, ctlr->macid); + p += 4; + } else { + p += 2; /* reserved */ + } + + memmove(p, addr, 6); + p += 8; + + *p++ = id; /* sta id */ + + if(ctlr->family >= 7000){ + *p++ = 1 << 1; /* modify mask */ + p += 2; /* reserved */ + + put32(p, 0<<26 | 0<<28); + p += 4; /* station_flags */ + + put32(p, 3<<26 | 3<<28); + p += 4; /* station_flags_mask */ + + p++; /* add_immediate_ba_tid */ + p++; /* remove_immediate_ba_tid */ + p += 2; /* add_immediate_ba_ssn */ + p += 2; /* sleep_tx_count */ + p++; /* sleep state flags */ + + *p++ = ctlr->fw->api[0] & UcodeApiSta ? type : 0; /* station_type */ + + p += 2; /* assoc id */ + + p += 2; /* beamform flags */ + + put32(p, 1<<0); + p += 4; /* tfd_queue_mask */ + + if(ctlr->fw->api[0] & UcodeApiSta){ + p += 2; /* rx_ba_window */ + p++; /* sp_length */ + p++; /* uapsd_acs */ + } + } else { + p += 3; + 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 */ + } + + if((err = cmd(ctlr, 24, c, p - c)) != nil) + return err; + sta->id = id; + return nil; +} + +static char* +setphycontext(Ctlr *ctlr, int amr) +{ + uchar c[Tcmdsize], *p; + int phyid; + char *err; + + phyid = ctlr->phyid; + if(phyid < 0){ + if(amr == CmdRemove) + return nil; + amr = CmdAdd; + phyid = 0; + } else if(amr == CmdAdd) + amr = CmdModify; + + memset(p = c, 0, sizeof(c)); + put32(p, phyid); // id and color + p += 4; + put32(p, amr); + p += 4; + put32(p, 0); // apply time 0 = immediate + p += 4; + put32(p, 0); // tx param color ???? + p += 4; + + *p++ = (ctlr->rxflags & RFlag24Ghz) != 0; + *p++ = ctlr->channel; // channel number + *p++ = 0; // channel width (20MHz<<val) + *p++ = 0; // pos1 below + + put32(p, ctlr->rfcfg.txantmask); + p += 4; + put32(p, ctlr->rfcfg.rxantmask<<1 | (1<<10) | (1<<12)); + p += 4; + put32(p, 0); // acquisition_data ???? + p += 4; + put32(p, 0); // dsp_cfg_flags + p += 4; + + if((err = cmd(ctlr, 8, c, p - c)) != nil) + return err; + + if(amr == CmdRemove) + phyid = -1; + ctlr->phyid = phyid; + return nil; +} + +static u32int +reciprocal(u32int v) +{ + return v != 0 ? 0xFFFFFFFFU / v : 0; +} + +static char* +setmaccontext(Ether *edev, Ctlr *ctlr, int amr, Wnode *bss) +{ + uchar c[4+4 + 4+4 + 8+8 + 4+4+4+4+4+4+4 + 5*8 + 12*4], *p; + int macid, i; + char *err; + + macid = ctlr->macid; + if(macid < 0){ + if(amr == CmdRemove) + return nil; + amr = CmdAdd; + macid = 0; + } else if(amr == CmdAdd) + amr = CmdModify; + + memset(p = c, 0, sizeof(c)); + put32(p, macid); + p += 4; + put32(p, amr); + p += 4; + + put32(p, 5); // mac type 5 = bss + p += 4; + + put32(p, 0); // tsf id ??? + p += 4; + + memmove(p, edev->ea, 6); + p += 8; + + memmove(p, ctlr->bssid, 6); + p += 8; + + put32(p, bss == nil? 0xF : (bss->validrates & 0xF)); + p += 4; + put32(p, bss == nil? 0xFF : (bss->validrates >> 4)); + p += 4; + + put32(p, 0); // protection flags + p += 4; + + put32(p, ctlr->rxflags & RFlagShPreamble); + p += 4; + put32(p, ctlr->rxflags & RFlagShSlot); + p += 4; + put32(p, ctlr->rxfilter); + p += 4; + + put32(p, 0); // qos flags + p += 4; + + for(i = 0; i < 4; i++){ + put16(p, 0x07); // cw_min + p += 2; + put16(p, 0x0f); // cw_max + p += 2; + *p++ = 2; // aifsn + *p++ = (1<<i); // fifos_mask + put16(p, 102*32); // edca_txop + p += 2; + } + p += 8; + + if(bss != nil){ + int dtimoff = bss->ival * (int)bss->dtimcount * 1024; + + /* is assoc */ + put32(p, bss->aid != 0); + p += 4; + + /* dtim time (system time) */ + put32(p, bss->rs + dtimoff); + p += 4; + + /* dtim tsf */ + put64(p, bss->ts + dtimoff); + p += 8; + + /* beacon interval */ + put32(p, bss->ival); + p += 4; + put32(p, reciprocal(bss->ival)); + p += 4; + + /* dtim interval */ + put32(p, bss->ival * bss->dtimperiod); + p += 4; + put32(p, reciprocal(bss->ival * bss->dtimperiod)); + p += 4; + + /* listen interval */ + put32(p, 10); + p += 4; + + /* assoc id */ + put32(p, bss->aid & 0x3fff); + p += 4; + + /* assoc beacon arrive time */ + put32(p, bss->rs); + p += 4; + } + USED(p); + + if((err = cmd(ctlr, 40, c, sizeof(c))) != nil) + return err; + + if(amr == CmdRemove) + macid = -1; + ctlr->macid = macid; + + return nil; +} + +static char* +setbindingcontext(Ctlr *ctlr, int amr) +{ + uchar c[Tcmdsize], *p; + int bindid; + char *err; + int i; + + bindid = ctlr->bindid; + if(bindid < 0){ + if(amr == CmdRemove) + return nil; + amr = CmdAdd; + bindid = 0; + } else if(amr == CmdAdd) + amr = CmdModify; + + if(ctlr->phyid < 0) + return "setbindingcontext: no phyid"; + if(ctlr->macid < 0) + return "setbindingcontext: no macid"; + + p = c; + put32(p, bindid); + p += 4; + put32(p, amr); + p += 4; + + i = 0; + if(amr != CmdRemove){ + put32(p, ctlr->macid); + p += 4; + i++; + } + for(; i < 3; i++){ + put32(p, -1); + p += 4; + } + put32(p, ctlr->phyid); + p += 4; + + if((err = cmd(ctlr, 43, c, p - c)) != nil) + return err; + + if(amr == CmdRemove) + bindid = -1; + ctlr->bindid = bindid; + return nil; +} + +static int +timeeventdone(void *arg) +{ + Ctlr *ctlr = arg; + return ctlr->te.id == -1 || ctlr->te.active != 0; +} + +static char* +settimeevent(Ctlr *ctlr, int amr, int ival) +{ + int timeid; + uchar c[9*4], *p; + char *err; + + switch(amr){ + case CmdAdd: + timeid = ctlr->te.id; + if(timeid == -1) + timeid = 0; + else { + if(ctlr->te.active) + return nil; + amr = CmdModify; + } + break; + default: + timeid = ctlr->te.id; + if(timeid == -1) + return nil; + break; + } + + memset(p = c, 0, sizeof(c)); + put32(p, ctlr->macid); + p += 4; + put32(p, amr); + p += 4; + put32(p, timeid); + p += 4; + + if(amr == CmdRemove) + p += 6*4; + else{ + put32(p, 0); // apply time + p += 4; + put32(p, ival/2); // max delay + p += 4; + put32(p, 0); // depends on + p += 4; + put32(p, 1); // interval + p += 4; + put32(p, ival? ival*2: 1024); // duration + p += 4; + *p++ = 1; // repeat + *p++ = 0; // max frags + put16(p, 1<<0 | 1<<1 | 1<<11); // policy + p += 2; + } + + ctlr->te.active = 0; + if((err = cmd(ctlr, 41, c, p - c)) != nil) + return err; + + if(amr == CmdRemove){ + ctlr->te.active = 0; + ctlr->te.id = -1; + return nil; + } + tsleep(&ctlr->te, timeeventdone, ctlr, 100); + return ctlr->te.active? nil: "timeevent did not start"; +} + + +static char* +setbindingquotas(Ctlr *ctlr, int bindid) +{ + uchar c[4*(3*4)], *p; + int i; + + if((ctlr->fw->capa[1] & UcodeCapQuota) == 0) + return nil; + + i = 0; + p = c; + + if(bindid != -1){ + put32(p, bindid); + p += 4; + put32(p, 128); + p += 4; + put32(p, 0); + p += 4; + i++; + } + for(; i < 4; i++){ + put32(p, -1); + p += 4; + put32(p, 0); + p += 4; + put32(p, 0); + p += 4; + } + + return cmd(ctlr, 44, c, p - c); +} + +static char* +setmcastfilter(Ctlr *ctlr) +{ + uchar *p; + char *err; + Block *b; + + b = allocb(4+6+2); + p = b->rp; + + *p++ = 1; // filter own + *p++ = 0; // port id + *p++ = 0; // count + *p++ = 1; // pass all + + memmove(p, ctlr->bssid, 6); + p += 6; + *p++ = 0; + *p++ = 0; + + b->wp = p; + if((err = qcmd(ctlr, 4, 208, nil, 0, b)) != nil){ + freeb(b); + return err; + } + return flushq(ctlr, 4); +} + +static char* +setmacpowermode(Ctlr *ctlr) +{ + uchar c[4 + 2+2 + 4+4+4+4 + 1+1 + 2+2 + 1+1+1+1 + 1+1+1+1 + 1+1], *p; + + p = c; + put32(p, ctlr->macid); + p += 4; + + put16(p, 0); // flags + p += 2; + put16(p, 5); // keep alive seconds + p += 2; + + put32(p, 0); // rx data timeout + p += 4; + put32(p, 0); // tx data timeout + p += 4; + put32(p, 0); // rx data timeout uapsd + p += 4; + put32(p, 0); // tx data timeout uapsd + p += 4; + + *p++ = 0; // lprx rssi threshold + *p++ = 0; // skip dtim periods + + put16(p, 0); // snooze interval + p += 2; + put16(p, 0); // snooze window + p += 2; + + *p++ = 0; // snooze step + *p++ = 0; // qndp tid + *p++ = 0; // uapsd ac flags + *p++ = 0; // uapsd max sp + + *p++ = 0; // heavy tx thld packets + *p++ = 0; // heavy rx thld packets + + *p++ = 0; // heavy tx thld percentage + *p++ = 0; // heavy rx thld percentage + + *p++ = 0; // limited ps threshold + *p++ = 0; // reserved + + return cmd(ctlr, 169, c, p - c); +} + +static char* +disablebeaconfilter(Ctlr *ctlr) +{ + uchar c[11*4]; + + memset(c, 0, sizeof(c)); + return cmd(ctlr, 210, c, 11*4); +} + +static char* +tttxbackoff(Ctlr *ctlr) +{ + uchar c[4]; + + put32(c, 0); + return cmd(ctlr, 126, c, sizeof(c)); +} + +static char* +updatedevicepower(Ctlr *ctlr) +{ + uchar c[4]; + + memset(c, 0, sizeof(c)); + put16(c, 0<<13 | 1<<0); // cont active off, pm enable + + return cmd(ctlr, 119, c, 4); +} + +static char* +postboot7000(Ctlr *ctlr) +{ + char *err; + + if(ctlr->calib.done == 0){ + if(ctlr->family == 7000) + if((err = sendbtcoexadv(ctlr)) != nil) + return err; + if((err = readnvmconfig(ctlr)) != nil) + return err; + } + + if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask)) != nil) + return err; + + if(ctlr->calib.done == 0){ + if((err = sendphyconfig(ctlr, + ctlr->fw->physku, + ctlr->fw->init.defcalib.flowmask, + ctlr->fw->init.defcalib.eventmask)) != nil) + return err; + + /* wait to collect calibration records */ + if(irqwait(ctlr, Ierr, 2000)) + return "calibration failed"; + + if(ctlr->calib.done == 0){ + print("iwl: no calibration results\n"); + ctlr->calib.done = 1; + } + } else { + Block *b; + int i; + + for(i = 0; i < nelem(ctlr->calib.cmd); i++){ + if((b = ctlr->calib.cmd[i]) == nil) + continue; + b = copyblock(b, BLEN(b)); + if((qcmd(ctlr, 4, 108, nil, 0, b)) != nil){ + freeb(b); + return err; + } + if((err = flushq(ctlr, 4)) != nil) + return err; + } + + if((err = sendphyconfig(ctlr, + ctlr->fw->physku, + ctlr->fw->main.defcalib.flowmask, + ctlr->fw->main.defcalib.eventmask)) != nil) + return err; + + if((err = sendbtcoexadv(ctlr)) != nil) + return err; + + /* Initialize tx backoffs to the minimum. */ + if(ctlr->family == 7000) + if((err = tttxbackoff(ctlr)) != nil) + return err; + + if((err = updatedevicepower(ctlr)) != nil){ + print("can't update device power: %s\n", err); + return err; + } + if(ctlr->fw->capa[0] & UcodeCapLar) + if((err = sendmccupdate(ctlr, "ZZ")) != nil) + return err; + if((err = disablebeaconfilter(ctlr)) != nil){ + print("can't disable beacon filter: %s\n", err); + return err; + } + } + + return nil; +} + +static char* +postboot6000(Ctlr *ctlr) +{ + uchar c[Tcmdsize]; + char *err; + + /* disable wimax coexistance */ + memset(c, 0, sizeof(c)); + if((err = cmd(ctlr, 90, c, 4+4*16)) != nil) + return err; + + /* 6235 times out if we calibrate the crystal immediately */ + tsleep(&up->sleep, return0, nil, 10); + if(ctlr->type != Type5150){ + /* calibrate crystal */ + memset(c, 0, sizeof(c)); + c[0] = 15; /* code */ + c[1] = 0; /* group */ + c[2] = 1; /* ngroup */ + c[3] = 1; /* isvalid */ + c[4] = ctlr->eeprom.crystal; + c[5] = ctlr->eeprom.crystal>>16; + /* for some reason 8086:4238 needs a second try */ + if(cmd(ctlr, 176, c, 8) != nil && (err = cmd(ctlr, 176, c, 8)) != nil) + return err; + } + + if(ctlr->calib.done == 0){ + /* query calibration (init firmware) */ + memset(c, 0, sizeof(c)); + put32(c + 0*(5*4) + 0, 0xffffffff); + put32(c + 0*(5*4) + 4, 0xffffffff); + put32(c + 0*(5*4) + 8, 0xffffffff); + put32(c + 2*(5*4) + 0, 0xffffffff); + if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil) + return err; + + /* wait to collect calibration records */ + if(irqwait(ctlr, Ierr, 2000)) + return "calibration failed"; + + if(ctlr->calib.done == 0){ + print("iwl: no calibration results\n"); + ctlr->calib.done = 1; + } + } else { + static uchar cmds[] = {8, 9, 11, 17, 16}; + int q; + + /* send calibration records (runtime firmware) */ + for(q=0; q<nelem(cmds); q++){ + Block *b; + int i; + + i = cmds[q]; + if(i == 8 && ctlr->type != Type5150 && ctlr->type != Type2030 && + ctlr->type != Type2000) + continue; + if(i == 17 && (ctlr->type >= Type6000 || ctlr->type == Type5150) && + ctlr->type != Type2030 && ctlr->type != Type2000) + continue; + + if((b = ctlr->calib.cmd[i]) == nil) + continue; + b = copyblock(b, BLEN(b)); + if((err = qcmd(ctlr, 4, 176, nil, 0, b)) != nil){ + freeb(b); + return err; + } + if((err = flushq(ctlr, 4)) != nil) + return err; + } + + /* temperature sensor offset */ + switch (ctlr->type){ + case Type6005: + memset(c, 0, sizeof(c)); + c[0] = 18; + c[1] = 0; + c[2] = 1; + c[3] = 1; + put16(c + 4, 2700); + if((err = cmd(ctlr, 176, c, 4+2+2)) != nil) + return err; + break; + + case Type2030: + case Type2000: + memset(c, 0, sizeof(c)); + c[0] = 18; + c[1] = 0; + c[2] = 1; + c[3] = 1; + if(ctlr->eeprom.rawtemp != 0){ + put16(c + 4, ctlr->eeprom.temp); + put16(c + 6, ctlr->eeprom.rawtemp); + } else{ + put16(c + 4, 2700); + put16(c + 6, 2700); + } + put16(c + 8, ctlr->eeprom.volt); + if((err = cmd(ctlr, 176, c, 4+2+2+2+2)) != nil) + return err; + break; + } + + if(ctlr->type == Type6005 || ctlr->type == Type6050){ + /* runtime DC calibration */ + memset(c, 0, sizeof(c)); + put32(c + 0*(5*4) + 0, 0xffffffff); + put32(c + 0*(5*4) + 4, 1<<1); + if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil) + return err; + } + + if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask & 7)) != nil) + return err; + + if(ctlr->type == Type2030){ + if((err = sendbtcoexadv(ctlr)) != nil) + return err; + } + } + return nil; +} + +static void +initqueue(Ctlr *ctlr, int qid, int fifo, int chainmode, int window) +{ + csr32w(ctlr, HbusTargWptr, (qid << 8) | 0); + + if(ctlr->family >= 7000 || ctlr->type != Type4965){ + if(ctlr->family >= 7000) + prphwrite(ctlr, SchedQueueStatus + qid*4, 1 << 19); + + if(chainmode) + prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) | (1<<qid)); + else + prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) & ~(1<<qid)); + + prphwrite(ctlr, SchedAggrSel, prphread(ctlr, SchedAggrSel) & ~(1<<qid)); + + prphwrite(ctlr, SchedQueueRdptr + qid*4, 0); + + /* Set scheduler window size and frame limit. */ + memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8, 0); + memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8 + 4, window<<16 | window); + + if(ctlr->family >= 7000){ + prphwrite(ctlr, SchedQueueStatus + qid*4, 0x017f0018 | fifo); + } else { + prphwrite(ctlr, SchedQueueStatus + qid*4, 0x00ff0018 | fifo); + } + } else { + if(chainmode) + prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) | (1<<qid)); + else + prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) & ~(1<<qid)); + + prphwrite(ctlr, SchedQueueRdptr4965 + qid*4, 0); + + /* Set scheduler window size and frame limit. */ + memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8, window); + memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8 + 4, window<<16); + + prphwrite(ctlr, SchedQueueStatus4965 + qid*4, 0x0007fc01 | fifo<<1); + } +} + +static char* +postboot(Ctlr *ctlr) +{ + uint ctxoff, ctxlen, dramaddr; + char *err; + int i, f; + + if((err = niclock(ctlr)) != nil) + return err; + + if(ctlr->family >= 7000 || ctlr->type != Type4965){ + dramaddr = SchedDramAddr; + ctxoff = SchedCtxOff; + ctxlen = (SchedTransTblOff + 2*ctlr->ntxq) - ctxoff; + } else { + dramaddr = SchedDramAddr4965; + ctxoff = SchedCtxOff4965; + ctxlen = SchedCtxLen4965; + } + + ctlr->sched.base = prphread(ctlr, SchedSramAddr); + for(i=0; i < ctxlen; i += 4) + memwrite(ctlr, ctlr->sched.base + ctxoff + i, 0); + + prphwrite(ctlr, dramaddr, PCIWADDR(ctlr->sched.s)>>10); + + if(ctlr->family >= 7000) { + prphwrite(ctlr, SchedEnCtrl, 0); + prphwrite(ctlr, SchedChainExtEn, 0); + } + + for(i = 0; i < nelem(ctlr->tx); i++){ + if(i == 4 && ctlr->family < 7000 && ctlr->type == Type4965) + f = 4; + else { + static char qid2fifo[] = { + 3, 2, 1, 0, 7, 5, 6, + }; + f = qid2fifo[i]; + } + initqueue(ctlr, i, f, i != 4 && ctlr->type != Type4965, 64); + } + + /* Enable interrupts for all queues. */ + if(ctlr->family >= 7000){ + prphwrite(ctlr, SchedEnCtrl, 1 << 4); + } else if(ctlr->type != Type4965) { + prphwrite(ctlr, SchedIntrMask, (1<<ctlr->ntxq)-1); + } else { + prphwrite(ctlr, SchedIntrMask4965, (1<<ctlr->ntxq)-1); + } + + /* Identify TX FIFO rings (0-7). */ + if(ctlr->family >= 7000 || ctlr->type != Type4965){ + prphwrite(ctlr, SchedTxFact, 0xff); + } else { + prphwrite(ctlr, SchedTxFact4965, 0xff); + } + + /* Enable DMA channels */ + for(i = 0; i < ctlr->ndma; i++) + csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna); + + /* Auto Retry Enable */ + csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2); + + nicunlock(ctlr); + + if((err = enablepaging(ctlr)) != nil){ + ctlr->calib.done = 0; + return err; + } + + if(ctlr->family >= 7000) + return postboot7000(ctlr); + else if(ctlr->type != Type4965) + return postboot6000(ctlr); + + return nil; +} + +static char* +loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size) +{ + enum { Maxchunk = 0x20000 }; + uchar *dma; + char *err; + + while(size > Maxchunk){ + if((err = loadfirmware1(ctlr, dst, data, Maxchunk)) != nil) + return err; + size -= Maxchunk; + data += Maxchunk; + dst += Maxchunk; + } + + dma = mallocalign(size, 4096, 0, 0); + if(dma == nil) + return "no memory for dma"; + memmove(dma, data, size); + dmaflush(1, dma, size); + coherence(); + + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + + if(ctlr->family >= 7000 && dst >= 0x40000 && dst < 0x57fff) + prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) | ExtAddr); + else + prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr); + + 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); + + err = nil; + if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx) + err = "dma error / timeout"; + + if(niclock(ctlr) == nil){ + prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr); + nicunlock(ctlr); + } + + free(dma); + + return err; +} + +static char* +setloadstatus(Ctlr *ctlr, u32int val) +{ + char *err; + + if((err = niclock(ctlr)) != nil) + return err; + csr32w(ctlr, UcodeLoadStatus, val); + nicunlock(ctlr); + return nil; +} + +static char* +loadsections(Ctlr *ctlr, FWSect *sect, int nsect) +{ + int i, num; + char *err; + + if(ctlr->family >= 8000){ + if((err = niclock(ctlr)) != nil) + return err; + prphwrite(ctlr, ReleaseCpuReset, CpuResetBit); + nicunlock(ctlr); + } + + num = 0; + for(i = 0; i < nsect; i++){ + if(sect[i].addr == 0xAAAABBBB) + break; + if(sect[i].addr == 0xFFFFCCCC) + num = 16; + else { + if(sect[i].data == nil || sect[i].size == 0) + return "bad load section"; + if((err = loadfirmware1(ctlr, sect[i].addr, sect[i].data, sect[i].size)) != nil) + return err; + num++; + } + if(ctlr->family >= 8000 + && (err = setloadstatus(ctlr, (1ULL << num)-1)) != nil) + return err; + } + return nil; +} + +static char* +ucodestart(Ctlr *ctlr) +{ + memset(&ctlr->fwinfo, 0, sizeof(ctlr->fwinfo)); + coherence(); + if(ctlr->family >= 8000) + return setloadstatus(ctlr, -1); + csr32w(ctlr, Reset, 0); + return nil; +} + +static char* +boot(Ctlr *ctlr) +{ + int i, n, size; + uchar *p, *dma; + FWImage *fw; + char *err; + + fw = ctlr->fw; + + if(fw->boot.text.size == 0){ + if(ctlr->calib.done == 0){ + if((err = loadsections(ctlr, fw->init.sect, fw->init.nsect)) != nil) + return err; + if((err = ucodestart(ctlr)) != nil) + return err; + + tsleep(&up->sleep, return0, 0, 100); + + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive) + return "init firmware boot failed"; + if(!ctlr->fwinfo.valid) + return "invalid fw info"; + + if((err = postboot(ctlr)) != nil) + return err; + if((err = reset(ctlr)) != nil) + return err; + } + + if((err = loadsections(ctlr, fw->main.sect, fw->main.nsect)) != nil) + return err; + if((err = ucodestart(ctlr)) != nil) + return err; + + tsleep(&up->sleep, return0, 0, 100); + + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive) + return "main firmware boot failed"; + if(!ctlr->fwinfo.valid) + return "invalid main fw info"; + + return postboot(ctlr); + } + + if(ctlr->family >= 7000) + return "wrong firmware image"; + + size = ROUND(fw->init.data.size, 16) + ROUND(fw->init.text.size, 16); + dma = mallocalign(size, 64, 0, 0); + if(dma == nil) + return "no memory for dma"; + + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + + p = dma; + memmove(p, fw->init.data.data, fw->init.data.size); + dmaflush(1, p, fw->init.data.size); + coherence(); + prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4); + prphwrite(ctlr, BsmDramDataSize, fw->init.data.size); + p += ROUND(fw->init.data.size, 16); + memmove(p, fw->init.text.data, fw->init.text.size); + dmaflush(1, p, fw->init.text.size); + coherence(); + prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4); + prphwrite(ctlr, BsmDramTextSize, fw->init.text.size); + + nicunlock(ctlr); + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + + p = fw->boot.text.data; + n = fw->boot.text.size/4; + for(i=0; i<n; i++, p += 4) + prphwrite(ctlr, BsmSramBase+i*4, get32(p)); + + prphwrite(ctlr, BsmWrMemSrc, 0); + prphwrite(ctlr, BsmWrMemDst, 0); + prphwrite(ctlr, BsmWrDwCount, n); + + prphwrite(ctlr, BsmWrCtrl, 1<<31); + + for(i=0; i<1000; i++){ + if((prphread(ctlr, BsmWrCtrl) & (1<<31)) == 0) + break; + microdelay(10); + } + if(i == 1000){ + nicunlock(ctlr); + free(dma); + return "bootcode timeout"; + } + + prphwrite(ctlr, BsmWrCtrl, 1<<30); + nicunlock(ctlr); + + csr32w(ctlr, Reset, 0); + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){ + free(dma); + return "boot firmware boot failed"; + } + free(dma); + + size = ROUND(fw->main.data.size, 16) + ROUND(fw->main.text.size, 16); + dma = mallocalign(size, 64, 0, 0); + if(dma == nil) + return "no memory for dma"; + if((err = niclock(ctlr)) != nil){ + free(dma); + return err; + } + p = dma; + memmove(p, fw->main.data.data, fw->main.data.size); + dmaflush(1, p, fw->main.data.size); + coherence(); + prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4); + prphwrite(ctlr, BsmDramDataSize, fw->main.data.size); + p += ROUND(fw->main.data.size, 16); + memmove(p, fw->main.text.data, fw->main.text.size); + dmaflush(1, p, fw->main.text.size); + coherence(); + prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4); + prphwrite(ctlr, BsmDramTextSize, fw->main.text.size | (1<<31)); + nicunlock(ctlr); + + if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){ + free(dma); + return "main firmware boot failed"; + } + free(dma); + return postboot(ctlr); +} + +static int +txqready(void *arg) +{ + TXQ *q = arg; + return q->n < Ntxqmax; +} + +static char* +qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block) +{ + char *err; + int hdrlen; + Block *bcmd; + uchar *d, *c; + TXQ *q; + + assert(qid < ctlr->ntxq); + + if((code & 0xFF00) != 0) + hdrlen = 8; + else + hdrlen = 4; + + if(hdrlen+size > Tcmdsize) + bcmd = allocb(hdrlen + size); + else + bcmd = nil; + + ilock(ctlr); + q = &ctlr->tx[qid]; + while(q->n >= Ntxqmax && !ctlr->broken){ + iunlock(ctlr); + qlock(q); + if(!waserror()){ + tsleep(q, txqready, q, 5); + poperror(); + } + qunlock(q); + ilock(ctlr); + } + if(ctlr->broken){ + iunlock(ctlr); + return "qcmd: broken"; + } + /* wake up the nic (just needed for 7k) */ + if(ctlr->family == 7000 && qid == 4 && q->n == 0) + if((err = niclock(ctlr)) != nil){ + iunlock(ctlr); + return err; + } + q->n++; + q->lastcmd = code; + + q->b[q->i] = block; + if(bcmd != nil){ + bcmd->next = q->b[q->i]; + q->b[q->i] = bcmd; + + c = bcmd->rp; + bcmd->wp = c + hdrlen + size; + } else { + c = q->c + q->i * Tcmdsize; + } + + /* build command */ + if(hdrlen == 8){ + c[0] = code; + c[1] = code>>8; /* group id */ + c[2] = q->i; + c[3] = qid; + put16(c+4, size); + c[6] = 0; + c[7] = code>>16; + } else { + c[0] = code; + c[1] = 0; /* flags */ + c[2] = q->i; + c[3] = qid; + } + if(size > 0) + memmove(c+hdrlen, data, size); + size += hdrlen; + + /* build descriptor */ + d = q->d + q->i * Tdscsize; + d[0] = 0; + d[1] = 0; + d[2] = 0; + d[3] = 1 + (block != nil); /* nsegs */ + dmaflush(1, c, size); + put32(d+4, PCIWADDR(c)); + put16(d+8, size << 4); + if(block != nil){ + size = BLEN(block); + dmaflush(1, block->rp, size); + put32(d+10, PCIWADDR(block->rp)); + put16(d+14, size << 4); + } + dmaflush(1, d, Tdscsize); + coherence(); + + q->i = (q->i+1) % Ntx; + csr32w(ctlr, HbusTargWptr, (qid<<8) | q->i); + + iunlock(ctlr); + + return nil; +} + +static int +txqempty(void *arg) +{ + TXQ *q = arg; + return q->n == 0; +} + +static char* +flushq(Ctlr *ctlr, uint qid) +{ + TXQ *q; + int i; + + q = &ctlr->tx[qid]; + qlock(q); + for(i = 0; i < 200 && !ctlr->broken; i++){ + if(txqempty(q)){ + qunlock(q); + return nil; + } + if(!waserror()){ + tsleep(q, txqempty, q, 10); + poperror(); + } + } + qunlock(q); + if(ctlr->broken) + return "flushq: broken"; + ctlr->broken = 1; + return "flushq: timeout"; +} + +static char* +cmd(Ctlr *ctlr, uint code, uchar *data, int size) +{ + char *err; + + if(0) print("cmd %ud\n", code); + + if((err = qcmd(ctlr, 4, code, data, size, nil)) != nil + || (err = flushq(ctlr, 4)) != nil){ + print("#l%d: cmd %ud: %s\n", ctlr->edev->ctlrno, code, err); + return err; + } + return nil; +} + +static void +setled(Ctlr *ctlr, int which, int on, int off) +{ + uchar c[8]; + + if(ctlr->family >= 7000) + return; // TODO + + csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl); + put32(c, 10000); + c[4] = which; + c[5] = on; + c[6] = off; + c[7] = 0; + cmd(ctlr, 72, c, sizeof(c)); +} + +static char* +rxoff7000(Ether *edev, Ctlr *ctlr) +{ + char *err; + int i; + + for(i = 0; i < nelem(ctlr->tx); i++) + if((err = flushq(ctlr, i)) != nil){ + print("can't flush queue %d: %s\n", i, err); + return err; + } + + if((err = settimeevent(ctlr, CmdRemove, 0)) != nil){ + print("can't remove time event: %s\n", err); + return err; + } + + if((err = setbindingquotas(ctlr, -1)) != nil){ + print("can't disable quotas: %s\n", err); + return err; + } + if((err = delstation(ctlr, &ctlr->bss)) != nil){ + print("can't remove bss station: %s\n", err); + return err; + } + if((err = delstation(ctlr, &ctlr->bcast)) != nil){ + print("can't remove bcast station: %s\n", err); + return err; + } + if((err = setbindingcontext(ctlr, CmdRemove)) != nil){ + print("removing bindingcontext: %s\n", err); + return err; + } + if((err = setmaccontext(edev, ctlr, CmdRemove, nil)) != nil){ + print("removing maccontext: %s\n", err); + return err; + } + if((err = setphycontext(ctlr, CmdRemove)) != nil){ + print("setphycontext: %s\n", err); + return err; + } + return nil; +} + +static char* +rxon7000(Ether *edev, Ctlr *ctlr) +{ + char *err; + + if((err = setphycontext(ctlr, CmdAdd)) != nil){ + print("setphycontext: %s\n", err); + return err; + } + if((err = setmaccontext(edev, ctlr, CmdAdd, nil)) != nil){ + print("setmaccontext: %s\n", err); + return err; + } + if((err = setbindingcontext(ctlr, CmdAdd)) != nil){ + print("adding bindingcontext: %s\n", err); + return err; + } + if((err = setmcastfilter(ctlr)) != nil){ + print("can't set mcast filter: %s\n", err); + return err; + } + if((err = setmacpowermode(ctlr)) != nil){ + print("can't set mac power: %s\n", err); + return err; + } + if((err = setbindingquotas(ctlr, ctlr->aid != 0 ? ctlr->bindid : -1)) != nil){ + print("can't set binding quotas: %s\n", err); + return err; + } + return nil; +} + +static char* +rxon6000(Ether *edev, Ctlr *ctlr) +{ + uchar c[Tcmdsize], *p; + char *err; + + memset(p = c, 0, sizeof(c)); + memmove(p, edev->ea, 6); p += 8; /* myaddr */ + memmove(p, ctlr->bssid, 6); p += 8; /* bssid */ + memmove(p, edev->ea, 6); p += 8; /* wlap */ + *p++ = 3; /* mode (STA) */ + *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) */ + put16(p, ctlr->aid & 0x3fff); + p += 2; /* aid */ + put32(p, ctlr->rxflags); + p += 4; + put32(p, ctlr->rxfilter); + p += 4; + *p++ = ctlr->channel; + 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 */ + } + if((err = cmd(ctlr, 16, c, p - c)) != nil){ + print("rxon6000: %s\n", err); + return err; + } + return nil; +} + +static char* +rxon(Ether *edev, Wnode *bss) +{ + Ctlr *ctlr = edev->ctlr; + char *err; + + if(ctlr->family >= 7000) + if((err = rxoff7000(edev, ctlr)) != nil) + goto Out; + + ctlr->rxfilter = FilterNoDecrypt | FilterMulticast | FilterBeacon; + if(ctlr->family >= 7000) + ctlr->rxfilter |= FilterNoDecryptMcast; + if(ctlr->prom) + ctlr->rxfilter |= FilterPromisc; + + ctlr->rxflags = RFlagTSF | RFlagCTSToSelf | RFlag24Ghz | RFlagAuto; + if(bss != nil){ + ctlr->aid = bss->aid; + ctlr->channel = bss->channel; + memmove(ctlr->bssid, bss->bssid, sizeof(ctlr->bssid)); + if(bss->cap & (1<<5)) + ctlr->rxflags |= RFlagShPreamble; + if(bss->cap & (1<<10)) + ctlr->rxflags |= RFlagShSlot; + if(ctlr->aid != 0){ + ctlr->rxfilter |= FilterBSS; + ctlr->rxfilter &= ~FilterBeacon; + ctlr->bss.id = -1; + } else { + ctlr->bcast.id = -1; + } + } else { + ctlr->aid = 0; + memmove(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid)); + ctlr->bcast.id = -1; + ctlr->bss.id = -1; + } + + if(ctlr->aid != 0) + setled(ctlr, 2, 0, 1); /* on when associated */ + else if(memcmp(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid)) != 0) + setled(ctlr, 2, 10, 10); /* slow blink when connecting */ + else + setled(ctlr, 2, 5, 5); /* fast blink when scanning */ + + if(ctlr->wifi->debug) + print("#l%d: rxon: bssid %E, aid %x, channel %d, rxfilter %ux, rxflags %ux\n", + edev->ctlrno, ctlr->bssid, ctlr->aid, ctlr->channel, ctlr->rxfilter, ctlr->rxflags); + + if(ctlr->family >= 7000) + err = rxon7000(edev, ctlr); + else + err = rxon6000(edev, ctlr); + if(err != nil) + goto Out; + + if(ctlr->bcast.id == -1){ + if((err = setstation(ctlr, + (ctlr->type != Type4965)? 15: 31, + StaTypeGeneralPurpose, + edev->bcast, + &ctlr->bcast)) != nil) + goto Out; + } + if(ctlr->bss.id == -1 && bss != nil && ctlr->aid != 0){ + if((err = setstation(ctlr, + 0, + StaTypeLink, + bss->bssid, + &ctlr->bss)) != nil) + goto Out; + + if(ctlr->family >= 7000) + if((err = setmaccontext(edev, ctlr, CmdModify, bss)) != nil) + goto Out; + } +Out: + return err; +} + +static void +transmit(Wifi *wifi, Wnode *wn, Block *b) +{ + int flags, rate, ant; + uchar c[Tcmdsize], *p; + Ether *edev; + Station *sta; + Ctlr *ctlr; + Wifipkt *w; + char *err; + + edev = wifi->ether; + ctlr = edev->ctlr; + + qlock(ctlr); + if(ctlr->attached == 0 || ctlr->broken){ +Broken: + qunlock(ctlr); + freeb(b); + return; + } + + if((wn->channel != ctlr->channel) + || (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0))){ + if(rxon(edev, wn) != nil) + goto Broken; + } + + /* + * unless authenticated, the firmware will hop + * channels unless we force it onto a channel using + * a timeevent. + */ + if(ctlr->aid == 0 && ctlr->family >= 7000) + if(settimeevent(ctlr, CmdAdd, wn->ival) != nil) + goto Broken; + + if(b == nil){ + /* association note has no data to transmit */ + qunlock(ctlr); + return; + } + flags = 0; + sta = &ctlr->bcast; + p = wn->minrate; + w = (Wifipkt*)b->rp; + if((w->a1[0] & 1) == 0){ + flags |= TFlagNeedACK; + + if(BLEN(b) > 512-4) + flags |= TFlagNeedRTS; + + if((w->fc[0] & 0x0c) == 0x08 && ctlr->bss.id != -1){ + sta = &ctlr->bss; + p = wn->actrate; + } + + if(flags & (TFlagNeedRTS|TFlagNeedCTS)){ + if(ctlr->family >= 7000 || ctlr->type != Type4965){ + flags &= ~(TFlagNeedRTS|TFlagNeedCTS); + flags |= TFlagNeedProtection; + } else + flags |= TFlagFullTxOp; + } + } + + if(sta->id == -1) + goto Broken; + + if(p >= wifi->rates) + rate = p - wifi->rates; + else + rate = 0; + + /* select first available antenna */ + ant = ctlr->rfcfg.txantmask & 7; + ant |= (ant == 0); + ant = ((ant - 1) & ant) ^ ant; + + memset(p = c, 0, sizeof(c)); + put16(p, BLEN(b)); + p += 2; + p += 2; /* lnext */ + put32(p, flags); + p += 4; + put32(p, 0); + p += 4; /* scratch */ + + *p++ = ratetab[rate].plcp; + *p++ = ratetab[rate].flags | (ant<<6); + + p += 2; /* xflags */ + *p++ = sta->id; /* station id */ + *p++ = 0; /* security */ + *p++ = 0; /* linkq */ + p++; /* reserved */ + p += 16; /* key */ + p += 2; /* fnext */ + p += 2; /* reserved */ + put32(p, ~0); /* lifetime */ + p += 4; + + /* BUG: 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 */ + qunlock(ctlr); + + if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){ + print("#l%d: transmit %s\n", edev->ctlrno, err); + freeb(b); + } +} + +static long +iwlctl(Ether *edev, void *buf, long n) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(n >= 5 && memcmp(buf, "reset", 5) == 0){ + ctlr->broken = 1; + return n; + } + if(ctlr->wifi) + return wifictl(ctlr->wifi, buf, n); + return 0; +} + +static long +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; + int i; + + ctlr = edev->ctlr; + for(i = 0; i < edev->nopt; i++) + wificfg(ctlr->wifi, edev->opt[i]); +} + +static void +iwlpromiscuous(void *arg, int on) +{ + Ether *edev; + Ctlr *ctlr; + + edev = arg; + ctlr = edev->ctlr; + qlock(ctlr); + ctlr->prom = on; + rxon(edev, ctlr->wifi->bss); + qunlock(ctlr); +} + +static void +iwlmulticast(void *, uchar*, int) +{ +} + +static void +iwlrecover(void *arg) +{ + Ether *edev; + Ctlr *ctlr; + + edev = arg; + ctlr = edev->ctlr; + while(waserror()) + ; + for(;;){ + tsleep(&up->sleep, return0, 0, 4000); + + qlock(ctlr); + for(;;){ + if(ctlr->broken == 0) + break; + + if(ctlr->power) + poweroff(ctlr); + + if((csr32r(ctlr, Gpc) & RfKill) == 0) + break; + + if(reset(ctlr) != nil) + break; + if(boot(ctlr) != nil) + break; + + rxon(edev, ctlr->wifi->bss); + break; + } + qunlock(ctlr); + } +} + +static void +iwlattach(Ether *edev) +{ + FWImage *fw; + Ctlr *ctlr; + char *err; + + ctlr = edev->ctlr; + eqlock(ctlr); + if(waserror()){ + print("#l%d: %s\n", edev->ctlrno, up->errstr); + if(ctlr->power) + poweroff(ctlr); + qunlock(ctlr); + nexterror(); + } + if(ctlr->attached == 0){ + if((csr32r(ctlr, Gpc) & RfKill) == 0) + error("wifi disabled by switch"); + + if(ctlr->fw == nil){ + char *fn; + + fn = ctlr->fwname; + if(fn == nil){ + fn = fwname[ctlr->type]; + if(ctlr->type == Type6005){ + switch(ctlr->pdev->did){ + case 0x0082: /* Centrino Advanced-N 6205 */ + case 0x0085: /* Centrino Advanced-N 6205 */ + break; + default: /* Centrino Advanced-N 6030, 6235 */ + fn = "iwn-6030"; + } + } + } + fw = readfirmware(fn); + print("#l%d: firmware: %s, rev %ux, build %ud, size [%d] %ux+%ux + [%d] %ux+%ux + %ux\n", + edev->ctlrno, fn, + fw->rev, fw->build, + fw->main.nsect, fw->main.text.size, fw->main.data.size, + fw->init.nsect, fw->init.text.size, fw->init.data.size, + fw->boot.text.size); + ctlr->fw = fw; + } + + if(ctlr->family >= 7000){ + u32int u = ctlr->fw->physku; + + ctlr->rfcfg.type = u & 3; u >>= 2; + ctlr->rfcfg.step = u & 3; u >>= 2; + ctlr->rfcfg.dash = u & 3; u >>= 12; + + ctlr->rfcfg.txantmask = u & 15; u >>= 4; + ctlr->rfcfg.rxantmask = u & 15; + } + + if((err = reset(ctlr)) != nil) + error(err); + if((err = boot(ctlr)) != nil) + error(err); + + if(ctlr->wifi == nil){ + qsetlimit(edev->oq, MaxQueue); + + ctlr->wifi = wifiattach(edev, transmit); + /* tested with 2230, it has transmit issues using higher bit rates */ + if(ctlr->family >= 7000 || ctlr->type != Type2030) + ctlr->wifi->rates = iwlrates; + } + + setoptions(edev); + + ctlr->attached = 1; + + kproc("iwlrecover", iwlrecover, edev); + } + qunlock(ctlr); + poperror(); +} + +static void +updatesystime(Ctlr *ctlr, u32int ts) +{ + u32int dt = ts - (u32int)ctlr->systime; + ctlr->systime += dt; +} + +static void +receive(Ctlr *ctlr) +{ + Block *b, *bb; + uchar *d; + RXQ *rx; + TXQ *tx; + uint hw; + + rx = &ctlr->rx; + if(ctlr->broken || rx->s == nil || rx->b == nil) + return; + + dmaflush(0, rx->s, Rstatsize); + hw = get16(rx->s); + for(hw %= Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){ + int type, flags, idx, qid, len; + + b = rx->b[rx->i]; + if(b == nil) + continue; + + d = b->rp; + dmaflush(0, d, Rbufsize); + len = get32(d); d += 4; + type = *d++; + flags = *d++; + USED(flags); + idx = *d++; + qid = *d++; + + tx = nil; + bb = nil; + if((qid & 0x80) == 0 && qid < ctlr->ntxq){ + tx = &ctlr->tx[qid]; + bb = tx->b[idx]; + tx->b[idx] = nil; + } + + len &= 0x3fff; + len -= 4; + if(len >= 0) switch(type){ + case 1: /* microcontroller ready */ + setfwinfo(ctlr, d, len); + break; + case 24: /* add node done */ + if(len < 4) + break; + break; + case 28: /* tx done */ + if(ctlr->family >= 7000){ + if(len <= 36 || d[36] == 1 || d[36] == 2) + break; + } else if(ctlr->type == Type4965){ + if(len <= 20 || d[20] == 1 || d[20] == 2) + break; + } else { + if(len <= 32 || d[32] == 1 || d[32] == 2) + break; + } + if(ctlr->wifi != nil) + wifitxfail(ctlr->wifi, bb); + break; + case 41: + if(len < 2*4) + break; + if(get32(d) != 0) + break; + if(ctlr->te.id == -1) + ctlr->te.id = get32(d+8); + break; + case 42: + if(len < 6*4) + break; + if(ctlr->te.id == -1 || get32(d+8) != ctlr->te.id) + break; + switch(get32(d+16)){ + case 1: + ctlr->te.active = 1; + wakeup(&ctlr->te); + break; + case 2: + ctlr->te.active = 0; + ctlr->te.id = -1; + wakeup(&ctlr->te); + } + break; + case 102: /* calibration result (Type5000 only) */ + if(ctlr->family >= 7000) + break; + if(len < 4) + break; + idx = d[0]; + Calib: + if(idx < 0 || idx >= nelem(ctlr->calib.cmd)) + break; + if(rbplant(ctlr, rx->i) < 0) + break; + if(ctlr->calib.cmd[idx] != nil) + freeb(ctlr->calib.cmd[idx]); + b->rp = d; + b->wp = d + len; + ctlr->calib.cmd[idx] = b; + break; + case 4: /* init complete (>= 7000 family) */ + if(ctlr->family < 7000) + break; + /* wet floor */ + case 103: /* calibration done (Type5000 only) */ + ctlr->calib.done = 1; + if(ctlr->wait.w == Ierr) + wakeup(&ctlr->wait); + break; + case 107: /* calibration result (>= 7000 family) */ + if(ctlr->family < 7000) + break; + len -= 4; + if(len < 0) + break; + idx = get16(d+2); + if(idx < len) + len = idx; + idx = -1; + switch(get16(d)){ + case 1: + idx = &ctlr->calib.cfg - &ctlr->calib.cmd[0]; + break; + case 2: + idx = &ctlr->calib.nch - &ctlr->calib.cmd[0]; + break; + case 4: + if(len < 2) + break; + idx = &ctlr->calib.papd[get16(d+4) % nelem(ctlr->calib.papd)] - &ctlr->calib.cmd[0]; + break; + case 5: + if(len < 2) + break; + idx = &ctlr->calib.txp[get16(d+4) % nelem(ctlr->calib.txp)] - &ctlr->calib.cmd[0]; + break; + } + len += 4; + goto Calib; + case 130: /* start scan */ + case 132: /* stop scan */ + break; + case 136: /* NVM access (>= 7000 family) */ + if(ctlr->family < 7000) + break; + len -= 8; + if(len < 0) + break; + if(ctlr->nvm.len < len) + len = ctlr->nvm.len; + ctlr->nvm.off = get16(d + 0); + ctlr->nvm.ret = get16(d + 2); + ctlr->nvm.type= get16(d + 4); + ctlr->nvm.sts = get16(d + 6); + d += 8; + if(ctlr->nvm.ret < len) + len = ctlr->nvm.ret; + if(ctlr->nvm.buf != nil && len > 0) + memmove(ctlr->nvm.buf, d, len); + ctlr->nvm.buf = nil; + ctlr->nvm.len = 0; + break; + case 156: /* rx statistics */ + case 157: /* beacon statistics */ + case 161: /* state changed */ + case 162: /* beacon missed */ + case 177: /* mduart load notification */ + break; + case 192: /* rx phy */ + if(len >= 8) + updatesystime(ctlr, get32(d+4)); + break; + case 195: /* rx done */ + if(d + 2 > b->lim) + break; + d += d[1]; + d += 56; + /* wet floor */ + case 193: /* mpdu rx done */ + if(d + 4 > b->lim) + break; + len = get16(d); + if(ctlr->mqrx){ + if(d + 48 + len > b->lim) + break; + updatesystime(ctlr, get32(d+36)); + if((d[12] & 3) != 3) + break; + d += 48; + } else { + d += 4; + if(d + len + 4 > b->lim) + break; + if((d[len] & 3) != 3) + break; + } + if(ctlr->wifi == nil) + break; + if(rbplant(ctlr, rx->i) < 0) + break; + b->rp = d; + b->wp = d + len; + + put64(d - 8, ctlr->systime); + b->flag |= Btimestamp; + + wifiiq(ctlr->wifi, b); + break; + case 197: /* rx compressed ba */ + break; + } + freeblist(bb); + if(tx != nil && tx->n > 0){ + tx->n--; + wakeup(tx); + /* unlock 7k family nics as the command is done */ + if(ctlr->family == 7000 && qid == 4 && tx->n == 0) + nicunlock(ctlr); + } + } + + if(ctlr->mqrx){ + csr32w(ctlr, FhRxQ0Wptr, ((hw+Nrx-1) % Nrx) & ~7); + }else + 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 | Ialive)) || (fhisr & Ifhrx)) + receive(ctlr); + if(isr & Ierr){ + ctlr->broken = 1; + print("#l%d: fatal firmware error\n", edev->ctlrno); + dumpctlr(ctlr); + } + ctlr->wait.m |= isr; + if(ctlr->wait.m & ctlr->wait.w) + wakeup(&ctlr->wait); +done: + csr32w(ctlr, Imr, ctlr->ie); + iunlock(ctlr); +} + +static void +iwlshutdown(Ether *edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(ctlr->power) + poweroff(ctlr); + ctlr->broken = 0; + pcidisable(ctlr->pdev); +} + +static Ctlr *iwlhead, *iwltail; + +static void +iwlpci(void) +{ + Pcidev *pdev; + char *fwname; + int family; + + pdev = nil; + while(pdev = pcimatch(pdev, Vintel, 0)) { + Ctlr *ctlr; + void *mem; + + if(pdev->ccrb != 2 || pdev->ccru != 0x80) + continue; + if(pdev->mem[0].bar & 1) + continue; + + switch(pdev->did){ + default: + continue; + case 0x0084: /* WiFi Link 1000 */ + case 0x4229: /* WiFi Link 4965 */ + case 0x4230: /* WiFi Link 4965 */ + case 0x4232: /* Wifi Link 5100 */ + case 0x4235: /* Intel Corporation Ultimate N WiFi Link 5300 */ + case 0x4236: /* WiFi Link 5300 AGN */ + case 0x4237: /* Wifi Link 5100 AGN */ + case 0x4239: /* Centrino Advanced-N 6200 */ + case 0x423d: /* Wifi Link 5150 */ + case 0x423b: /* PRO/Wireless 5350 AGN */ + case 0x0082: /* Centrino Advanced-N 6205 */ + case 0x0085: /* Centrino Advanced-N 6205 */ + case 0x0089: /* Centrino Advanced-N + WiMAX 6250 */ + case 0x422b: /* Centrino Ultimate-N 6300 variant 1 */ + case 0x4238: /* Centrino Ultimate-N 6300 variant 2 */ + case 0x08ae: /* Centrino Wireless-N 100 */ + case 0x0083: /* Centrino Wireless-N 1000 */ + case 0x008a: /* Centrino Wireless-N 1030 */ + case 0x0891: /* Centrino Wireless-N 2200 */ + case 0x0887: /* Centrino Wireless-N 2230 */ + case 0x0888: /* Centrino Wireless-N 2230 */ + case 0x0090: /* Centrino Advanced-N 6030 */ + case 0x0091: /* Centrino Advanced-N 6030 */ + case 0x088e: /* Centrino Advanced-N 6235 */ + case 0x088f: /* Centrino Advanced-N 6235 */ + family = 0; + fwname = nil; + break; + case 0x08b1: /* Wireless AC 7260 */ + case 0x08b2: /* Wireless AC 7260 */ + family = 7000; + fwname = "iwm-7260-17"; + break; + case 0x095a: /* Wireless AC 7265 */ + case 0x095b: /* Wireless AC 7265 */ + family = 7000; + fwname = "iwm-7265-17"; + break; + case 0x24f3: /* Wireless AC 8260 */ + family = 8000; + fwname = "iwm-8000C-34"; + break; + case 0x24fd: /* Wireless AC 8265 */ + family = 8000; + fwname = "iwm-8265-34"; + break; + case 0x2526: /* Wireless AC 9260 */ + family = 9000; + fwname = "iwm-9260-34"; + break; + } + + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil) { + print("iwl: unable to alloc Ctlr\n"); + continue; + } + ctlr->port = pdev->mem[0].bar & ~0xF; + mem = vmap(ctlr->port, pdev->mem[0].size); + if(mem == nil) { + print("iwl: can't map %llux\n", ctlr->port); + free(ctlr); + continue; + } + ctlr->nic = mem; + ctlr->pdev = pdev; + ctlr->fwname = fwname; + ctlr->family = family; + ctlr->mqrx = family >= 9000; + + 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->edev != nil) + continue; + if(edev->port == 0 || edev->port == ctlr->port){ + ctlr->edev = edev; + break; + } + } + + if(ctlr == nil) + return -1; + + edev->ctlr = ctlr; + edev->port = ctlr->port; + edev->irq = ctlr->pdev->intl; + edev->tbdf = ctlr->pdev->tbdf; + edev->arg = edev; + edev->attach = iwlattach; + edev->ifstat = iwlifstat; + edev->ctl = iwlctl; + edev->shutdown = iwlshutdown; + edev->promiscuous = iwlpromiscuous; + edev->multicast = iwlmulticast; + edev->mbps = 54; + + pcienable(ctlr->pdev); + if(iwlinit(edev) < 0){ + pcidisable(ctlr->pdev); + ctlr->edev = (void*)-1; + edev->ctlr = nil; + goto again; + } + + pcisetbme(ctlr->pdev); + intrenable(edev->irq, iwlinterrupt, edev, edev->tbdf, edev->name); + + return 0; +} + +void +etheriwllink(void) +{ + addethercard("iwl", iwlpnp); +} diff --git a/sys/src/9/port/portmkfile b/sys/src/9/port/portmkfile index 5340a1aa4..1f05b9fd7 100644 --- a/sys/src/9/port/portmkfile +++ b/sys/src/9/port/portmkfile @@ -114,5 +114,6 @@ usbxhci.$O usbxhcipci.$O: ../port/usbxhci.h devether.$O ethersink.$O: ../port/etherif.h ../port/netif.h wifi.$O: ../port/etherif.h ../port/netif.h ../port/wifi.h /sys/include/libsec.h wifi.$O: ../ip/ip.h ../ip/ipv6.h +etheriwl.$O: ../port/wifi.h ethermii.$O: ../port/ethermii.h pci.$O devpnp.$O devpci.$O usbxhcipci.$O: ../port/pci.h |