d6e0e9c402
the only architecture dependence of devether was enabling interrupts, which is now done at the end of the driver's reset() function now. the wifi stack and dummy ethersink also go to port/. do the IRQ2->IRQ9 hack for pc kernels in intrenabale(), so not every caller of intrenable() has to be aware of it.
2559 lines
52 KiB
C
2559 lines
52 KiB
C
/*
|
|
* Intel WiFi Link driver.
|
|
*
|
|
* Written without any documentation but Damien Bergaminis
|
|
* OpenBSD iwn(4) driver sources. Requires intel firmware
|
|
* to be present in /lib/firmware/iwn-* on attach.
|
|
*/
|
|
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "../port/error.h"
|
|
#include "../port/netif.h"
|
|
#include "../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,
|
|
|
|
Tbufsize = 4*1024,
|
|
Tdscsize = 128,
|
|
Tcmdsize = 140,
|
|
};
|
|
|
|
/* registers */
|
|
enum {
|
|
Cfg = 0x000, /* config register */
|
|
MacSi = 1<<8,
|
|
RadioSi = 1<<9,
|
|
EepromLocked = 1<<21,
|
|
NicReady = 1<<22,
|
|
HapwakeL1A = 1<<23,
|
|
PrepareDone = 1<<25,
|
|
Prepare = 1<<27,
|
|
|
|
Isr = 0x008, /* interrupt status */
|
|
Imr = 0x00c, /* interrupt mask */
|
|
Ialive = 1<<0,
|
|
Iwakeup = 1<<1,
|
|
Iswrx = 1<<3,
|
|
Ictreached = 1<<6,
|
|
Irftoggled = 1<<7,
|
|
Iswerr = 1<<25,
|
|
Isched = 1<<26,
|
|
Ifhtx = 1<<27,
|
|
Irxperiodic = 1<<28,
|
|
Ihwerr = 1<<29,
|
|
Ifhrx = 1<<31,
|
|
|
|
Ierr = Iswerr | Ihwerr,
|
|
Idefmask = Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Ictreached | Irftoggled,
|
|
|
|
FhIsr = 0x010, /* second interrupt status */
|
|
|
|
Reset = 0x020,
|
|
|
|
Rev = 0x028, /* hardware revision */
|
|
|
|
EepromIo = 0x02c, /* EEPROM i/o register */
|
|
EepromGp = 0x030,
|
|
|
|
OtpromGp = 0x034,
|
|
DevSelOtp = 1<<16,
|
|
RelativeAccess = 1<<17,
|
|
EccCorrStts = 1<<20,
|
|
EccUncorrStts = 1<<21,
|
|
|
|
Gpc = 0x024, /* gp cntrl */
|
|
MacAccessEna = 1<<0,
|
|
MacClockReady = 1<<0,
|
|
InitDone = 1<<2,
|
|
MacAccessReq = 1<<3,
|
|
NicSleep = 1<<4,
|
|
RfKill = 1<<27,
|
|
|
|
Gio = 0x03c,
|
|
EnaL0S = 1<<1,
|
|
|
|
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,
|
|
|
|
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,
|
|
};
|
|
|
|
/*
|
|
* Flow-Handler registers.
|
|
*/
|
|
enum {
|
|
FhTfbdCtrl0 = 0x1900, // +q*8
|
|
FhTfbdCtrl1 = 0x1904, // +q*8
|
|
|
|
FhKwAddr = 0x197c,
|
|
|
|
FhSramAddr = 0x19a4, // +q*4
|
|
FhCbbcQueue = 0x19d0, // +q*4
|
|
FhStatusWptr = 0x1bc0,
|
|
FhRxBase = 0x1bc4,
|
|
FhRxWptr = 0x1bc8,
|
|
FhRxConfig = 0x1c00,
|
|
FhRxConfigEna = 1<<31,
|
|
FhRxConfigRbSize8K = 1<<16,
|
|
FhRxConfigSingleFrame = 1<<15,
|
|
FhRxConfigIrqDstHost = 1<<12,
|
|
FhRxConfigIgnRxfEmpty = 1<<2,
|
|
|
|
FhRxConfigNrbdShift = 20,
|
|
FhRxConfigRbTimeoutShift= 4,
|
|
|
|
FhRxStatus = 0x1c44,
|
|
|
|
FhTxConfig = 0x1d00, // +q*32
|
|
FhTxConfigDmaCreditEna = 1<<3,
|
|
FhTxConfigDmaEna = 1<<31,
|
|
FhTxConfigCirqHostEndTfd= 1<<20,
|
|
|
|
FhTxBufStatus = 0x1d08, // +q*32
|
|
FhTxBufStatusTbNumShift = 20,
|
|
FhTxBufStatusTbIdxShift = 12,
|
|
FhTxBufStatusTfbdValid = 3,
|
|
|
|
FhTxChicken = 0x1e98,
|
|
FhTxStatus = 0x1eb0,
|
|
};
|
|
|
|
/*
|
|
* NIC internal memory offsets.
|
|
*/
|
|
enum {
|
|
ApmgClkCtrl = 0x3000,
|
|
ApmgClkEna = 0x3004,
|
|
ApmgClkDis = 0x3008,
|
|
DmaClkRqt = 1<<9,
|
|
BsmClkRqt = 1<<11,
|
|
|
|
ApmgPs = 0x300c,
|
|
EarlyPwroffDis = 1<<22,
|
|
PwrSrcVMain = 0<<24,
|
|
PwrSrcVAux = 2<<24,
|
|
PwrSrcMask = 3<<24,
|
|
ResetReq = 1<<26,
|
|
|
|
ApmgDigitalSvr = 0x3058,
|
|
ApmgAnalogSvr = 0x306c,
|
|
ApmgPciStt = 0x3010,
|
|
BsmWrCtrl = 0x3400,
|
|
BsmWrMemSrc = 0x3404,
|
|
BsmWrMemDst = 0x3408,
|
|
BsmWrDwCount = 0x340c,
|
|
BsmDramTextAddr = 0x3490,
|
|
BsmDramTextSize = 0x3494,
|
|
BsmDramDataAddr = 0x3498,
|
|
BsmDramDataSize = 0x349c,
|
|
BsmSramBase = 0x3800,
|
|
};
|
|
|
|
/*
|
|
* TX scheduler registers.
|
|
*/
|
|
enum {
|
|
SchedBase = 0xa02c00,
|
|
SchedSramAddr = SchedBase,
|
|
|
|
SchedDramAddr4965 = SchedBase+0x010,
|
|
SchedTxFact4965 = SchedBase+0x01c,
|
|
SchedQueueRdptr4965 = SchedBase+0x064, // +q*4
|
|
SchedQChainSel4965 = SchedBase+0x0d0,
|
|
SchedIntrMask4965 = SchedBase+0x0e4,
|
|
SchedQueueStatus4965 = SchedBase+0x104, // +q*4
|
|
|
|
SchedDramAddr5000 = SchedBase+0x008,
|
|
SchedTxFact5000 = SchedBase+0x010,
|
|
SchedQueueRdptr5000 = SchedBase+0x068, // +q*4
|
|
SchedQChainSel5000 = SchedBase+0x0e8,
|
|
SchedIntrMask5000 = SchedBase+0x108,
|
|
SchedQueueStatus5000 = SchedBase+0x10c, // +q*4
|
|
SchedAggrSel5000 = SchedBase+0x248,
|
|
};
|
|
|
|
enum {
|
|
SchedCtxOff4965 = 0x380,
|
|
SchedCtxLen4965 = 416,
|
|
|
|
SchedCtxOff5000 = 0x600,
|
|
SchedCtxLen5000 = 512,
|
|
};
|
|
|
|
enum {
|
|
FilterPromisc = 1<<0,
|
|
FilterCtl = 1<<1,
|
|
FilterMulticast = 1<<2,
|
|
FilterNoDecrypt = 1<<3,
|
|
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,
|
|
};
|
|
|
|
typedef struct FWInfo FWInfo;
|
|
typedef struct FWImage FWImage;
|
|
typedef struct FWSect FWSect;
|
|
|
|
typedef struct TXQ TXQ;
|
|
typedef struct RXQ RXQ;
|
|
|
|
typedef struct Ctlr Ctlr;
|
|
|
|
struct FWSect
|
|
{
|
|
uchar *data;
|
|
uint size;
|
|
};
|
|
|
|
struct FWImage
|
|
{
|
|
struct {
|
|
FWSect text;
|
|
FWSect data;
|
|
} init, main, boot;
|
|
|
|
uint rev;
|
|
uint build;
|
|
char descr[64+1];
|
|
uchar data[];
|
|
};
|
|
|
|
struct FWInfo
|
|
{
|
|
uchar major;
|
|
uchar minjor;
|
|
uchar type;
|
|
uchar subtype;
|
|
|
|
u32int logptr;
|
|
u32int errptr;
|
|
u32int tstamp;
|
|
u32int valid;
|
|
};
|
|
|
|
struct TXQ
|
|
{
|
|
uint n;
|
|
uint i;
|
|
Block **b;
|
|
uchar *d;
|
|
uchar *c;
|
|
|
|
uint lastcmd;
|
|
|
|
Rendez;
|
|
QLock;
|
|
};
|
|
|
|
struct RXQ
|
|
{
|
|
uint i;
|
|
Block **b;
|
|
u32int *p;
|
|
uchar *s;
|
|
};
|
|
|
|
struct Ctlr {
|
|
Lock;
|
|
QLock;
|
|
|
|
Ctlr *link;
|
|
Pcidev *pdev;
|
|
Wifi *wifi;
|
|
|
|
int type;
|
|
int port;
|
|
int power;
|
|
int active;
|
|
int broken;
|
|
int attached;
|
|
|
|
u32int ie;
|
|
|
|
u32int *nic;
|
|
uchar *kwpage;
|
|
|
|
/* assigned node ids in hardware node table or -1 if unassigned */
|
|
int bcastnodeid;
|
|
int bssnodeid;
|
|
|
|
/* current receiver settings */
|
|
uchar bssid[Eaddrlen];
|
|
int channel;
|
|
int prom;
|
|
int aid;
|
|
|
|
RXQ rx;
|
|
TXQ tx[20];
|
|
|
|
struct {
|
|
Rendez;
|
|
u32int m;
|
|
u32int w;
|
|
} wait;
|
|
|
|
struct {
|
|
uchar type;
|
|
uchar step;
|
|
uchar dash;
|
|
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 {
|
|
Block *cmd[21];
|
|
int done;
|
|
} calib;
|
|
|
|
struct {
|
|
u32int base;
|
|
uchar *s;
|
|
} sched;
|
|
|
|
FWInfo fwinfo;
|
|
FWImage *fw;
|
|
};
|
|
|
|
/* 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,
|
|
};
|
|
|
|
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",
|
|
};
|
|
|
|
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
|
|
put16(uchar *p, uint v){
|
|
*((u16int*)p) = v;
|
|
};
|
|
|
|
static char*
|
|
niclock(Ctlr *ctlr)
|
|
{
|
|
int i;
|
|
|
|
csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | MacAccessReq);
|
|
for(i=0; i<1000; i++){
|
|
if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna)
|
|
return 0;
|
|
delay(10);
|
|
}
|
|
return "niclock: timeout";
|
|
}
|
|
|
|
static void
|
|
nicunlock(Ctlr *ctlr)
|
|
{
|
|
csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq);
|
|
}
|
|
|
|
static u32int
|
|
prphread(Ctlr *ctlr, uint off)
|
|
{
|
|
csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off);
|
|
coherence();
|
|
return csr32r(ctlr, PrphRdata);
|
|
}
|
|
static void
|
|
prphwrite(Ctlr *ctlr, uint off, u32int data)
|
|
{
|
|
csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off);
|
|
coherence();
|
|
csr32w(ctlr, PrphWdata, data);
|
|
}
|
|
|
|
static u32int
|
|
memread(Ctlr *ctlr, uint off)
|
|
{
|
|
csr32w(ctlr, MemRaddr, off);
|
|
coherence();
|
|
return csr32r(ctlr, MemRdata);
|
|
}
|
|
static void
|
|
memwrite(Ctlr *ctlr, uint off, u32int data)
|
|
{
|
|
csr32w(ctlr, MemWaddr, off);
|
|
coherence();
|
|
csr32w(ctlr, MemWdata, data);
|
|
}
|
|
|
|
static void
|
|
setfwinfo(Ctlr *ctlr, uchar *d, int len)
|
|
{
|
|
FWInfo *i;
|
|
|
|
if(len < 32)
|
|
return;
|
|
i = &ctlr->fwinfo;
|
|
i->minjor = *d++;
|
|
i->major = *d++;
|
|
d += 2+8;
|
|
i->type = *d++;
|
|
i->subtype = *d++;
|
|
d += 2;
|
|
i->logptr = get32(d); d += 4;
|
|
i->errptr = get32(d); d += 4;
|
|
i->tstamp = get32(d); d += 4;
|
|
i->valid = get32(d);
|
|
};
|
|
|
|
static void
|
|
dumpctlr(Ctlr *ctlr)
|
|
{
|
|
u32int dump[13];
|
|
int i;
|
|
|
|
print("lastcmd: %ud (0x%ux)\n", ctlr->tx[4].lastcmd, ctlr->tx[4].lastcmd);
|
|
if(ctlr->fwinfo.errptr == 0){
|
|
print("no error pointer\n");
|
|
return;
|
|
}
|
|
for(i=0; i<nelem(dump); i++)
|
|
dump[i] = memread(ctlr, ctlr->fwinfo.errptr + i*4);
|
|
print( "error:\tid %ux, pc %ux,\n"
|
|
"\tbranchlink %.8ux %.8ux, interruptlink %.8ux %.8ux,\n"
|
|
"\terrordata %.8ux %.8ux, srcline %ud, tsf %ux, time %ux\n",
|
|
dump[1], dump[2],
|
|
dump[4], dump[3], dump[6], dump[5],
|
|
dump[7], dump[8], dump[9], dump[10], dump[11]);
|
|
}
|
|
|
|
static char*
|
|
eepromlock(Ctlr *ctlr)
|
|
{
|
|
int i, j;
|
|
|
|
for(i=0; i<100; i++){
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | EepromLocked);
|
|
for(j=0; j<100; j++){
|
|
if(csr32r(ctlr, Cfg) & EepromLocked)
|
|
return 0;
|
|
delay(10);
|
|
}
|
|
}
|
|
return "eepromlock: timeout";
|
|
}
|
|
static void
|
|
eepromunlock(Ctlr *ctlr)
|
|
{
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked);
|
|
}
|
|
static char*
|
|
eepromread(Ctlr *ctlr, void *data, int count, uint off)
|
|
{
|
|
uchar *out = data;
|
|
u32int w, 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;
|
|
delay(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;
|
|
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
|
|
for(i=0; i<5; i++){
|
|
if(csr32r(ctlr, Cfg) & NicReady)
|
|
return 0;
|
|
delay(10);
|
|
}
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare);
|
|
for(i=0; i<15000; i++){
|
|
if((csr32r(ctlr, Cfg) & PrepareDone) == 0)
|
|
break;
|
|
delay(10);
|
|
}
|
|
if(i >= 15000)
|
|
return "handover: timeout";
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
|
|
for(i=0; i<5; i++){
|
|
if(csr32r(ctlr, Cfg) & NicReady)
|
|
return 0;
|
|
delay(10);
|
|
}
|
|
return "handover: timeout";
|
|
}
|
|
|
|
static char*
|
|
clockwait(Ctlr *ctlr)
|
|
{
|
|
int i;
|
|
|
|
/* Set "initialization complete" bit. */
|
|
csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | InitDone);
|
|
for(i=0; i<2500; i++){
|
|
if(csr32r(ctlr, Gpc) & MacClockReady)
|
|
return 0;
|
|
delay(10);
|
|
}
|
|
return "clockwait: timeout";
|
|
}
|
|
|
|
static char*
|
|
poweron(Ctlr *ctlr)
|
|
{
|
|
int capoff;
|
|
char *err;
|
|
|
|
/* Disable L0s exit timer (NMI bug workaround). */
|
|
csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | DisL0Stimer);
|
|
|
|
/* Don't wait for ICH L0s (ICH bug workaround). */
|
|
csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | L1AnoL0Srx);
|
|
|
|
/* Set FH wait threshold to max (HW bug under stress workaround). */
|
|
csr32w(ctlr, Dbghpetmem, csr32r(ctlr, Dbghpetmem) | 0xffff0000);
|
|
|
|
/* Enable HAP INTA to move adapter from L1a to L0s. */
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | HapwakeL1A);
|
|
|
|
capoff = pcicap(ctlr->pdev, PciCapPCIe);
|
|
if(capoff != -1){
|
|
/* Workaround for HW instability in PCIe L0->L0s->L1 transition. */
|
|
if(pcicfgr16(ctlr->pdev, capoff + 0x10) & 0x2) /* LCSR -> L1 Entry enabled. */
|
|
csr32w(ctlr, Gio, csr32r(ctlr, Gio) | EnaL0S);
|
|
else
|
|
csr32w(ctlr, Gio, csr32r(ctlr, Gio) & ~EnaL0S);
|
|
}
|
|
|
|
if(ctlr->type != Type4965 && ctlr->type <= Type1000)
|
|
csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | 0x00880300);
|
|
|
|
/* Wait for clock stabilization before accessing prph. */
|
|
if((err = clockwait(ctlr)) != nil)
|
|
return err;
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
|
|
/* Enable DMA and BSM (Bootstrap State Machine). */
|
|
if(ctlr->type == Type4965)
|
|
prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt);
|
|
else
|
|
prphwrite(ctlr, ApmgClkEna, DmaClkRqt);
|
|
delay(20);
|
|
|
|
/* Disable L1-Active. */
|
|
prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));
|
|
|
|
nicunlock(ctlr);
|
|
|
|
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->type != Type4965)
|
|
prphwrite(ctlr, SchedTxFact5000, 0);
|
|
else
|
|
prphwrite(ctlr, SchedTxFact4965, 0);
|
|
|
|
/* Stop TX ring */
|
|
if(niclock(ctlr) == nil){
|
|
for(i = (ctlr->type != Type4965) ? 7 : 6; i >= 0; i--){
|
|
csr32w(ctlr, FhTxConfig + i*32, 0);
|
|
for(j = 0; j < 200; j++){
|
|
if(csr32r(ctlr, FhTxStatus) & (0x10000<<i))
|
|
break;
|
|
delay(10);
|
|
}
|
|
}
|
|
nicunlock(ctlr);
|
|
}
|
|
|
|
/* Stop RX ring */
|
|
if(niclock(ctlr) == nil){
|
|
csr32w(ctlr, FhRxConfig, 0);
|
|
for(j = 0; j < 200; j++){
|
|
if(csr32r(ctlr, FhRxStatus) & 0x1000000)
|
|
break;
|
|
delay(10);
|
|
}
|
|
nicunlock(ctlr);
|
|
}
|
|
|
|
/* Disable DMA */
|
|
if(niclock(ctlr) == nil){
|
|
prphwrite(ctlr, ApmgClkDis, DmaClkRqt);
|
|
nicunlock(ctlr);
|
|
}
|
|
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;
|
|
delay(10);
|
|
}
|
|
|
|
/* Reset the entire device. */
|
|
csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<7));
|
|
delay(10);
|
|
|
|
/* 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);
|
|
delay(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;
|
|
if((err = handover(ctlr)) != nil)
|
|
goto Err;
|
|
if((err = poweron(ctlr)) != nil)
|
|
goto Err;
|
|
if((csr32r(ctlr, EepromGp) & 0x7) == 0){
|
|
err = "bad rom signature";
|
|
goto Err;
|
|
}
|
|
if((err = eepromlock(ctlr)) != nil)
|
|
goto Err;
|
|
if((err = 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){
|
|
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;
|
|
|
|
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--;
|
|
while(p < e){
|
|
FWSect dummy;
|
|
|
|
if((p + 2+2+4) > e)
|
|
goto Tooshort;
|
|
switch(get16(p)){
|
|
case 1: s = &i->main.text; break;
|
|
case 2: s = &i->main.data; break;
|
|
case 3: s = &i->init.text; break;
|
|
case 4: s = &i->init.data; break;
|
|
case 5: s = &i->boot.text; break;
|
|
default:s = &dummy;
|
|
}
|
|
p += 2;
|
|
if(get16(p) != 0 && get16(p) != alt)
|
|
s = &dummy;
|
|
p += 2;
|
|
s->size = get32(p); p += 4;
|
|
s->data = p;
|
|
if((p + s->size) > e)
|
|
goto Tooshort;
|
|
p += (s->size + 3) & ~3;
|
|
}
|
|
} else {
|
|
if(((i->rev>>8) & 0xFF) < 2)
|
|
return "need firmware api >= 2";
|
|
if(((i->rev>>8) & 0xFF) >= 3){
|
|
i->build = get32(p); p += 4;
|
|
}
|
|
if((p + 5*4) > e)
|
|
goto Tooshort;
|
|
i->main.text.size = get32(p); p += 4;
|
|
i->main.data.size = get32(p); p += 4;
|
|
i->init.text.size = get32(p); p += 4;
|
|
i->init.data.size = get32(p); p += 4;
|
|
i->boot.text.size = get32(p); p += 4;
|
|
i->main.text.data = p; p += i->main.text.size;
|
|
i->main.data.data = p; p += i->main.data.size;
|
|
i->init.text.data = p; p += i->init.text.size;
|
|
i->init.data.data = p; p += i->init.data.size;
|
|
i->boot.text.data = p; p += i->boot.text.size;
|
|
if(p > e)
|
|
goto Tooshort;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static FWImage*
|
|
readfirmware(char *name)
|
|
{
|
|
uchar dirbuf[sizeof(Dir)+100], *data;
|
|
char buf[128], *err;
|
|
FWImage *fw;
|
|
int n, r;
|
|
Chan *c;
|
|
Dir d;
|
|
|
|
if(!iseve())
|
|
error(Eperm);
|
|
if(!waserror()){
|
|
snprint(buf, sizeof buf, "/boot/%s", name);
|
|
c = namec(buf, Aopen, OREAD, 0);
|
|
poperror();
|
|
} else {
|
|
snprint(buf, sizeof buf, "/lib/firmware/%s", name);
|
|
c = namec(buf, Aopen, OREAD, 0);
|
|
}
|
|
if(waserror()){
|
|
cclose(c);
|
|
nexterror();
|
|
}
|
|
n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
|
|
if(n <= 0)
|
|
error("can't stat firmware");
|
|
convM2D(dirbuf, n, &d, nil);
|
|
fw = smalloc(sizeof(*fw) + 16 + d.length);
|
|
data = (uchar*)(fw+1);
|
|
if(waserror()){
|
|
free(fw);
|
|
nexterror();
|
|
}
|
|
r = 0;
|
|
while(r < d.length){
|
|
n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
|
|
if(n <= 0)
|
|
break;
|
|
r += n;
|
|
}
|
|
if((err = crackfw(fw, data, r, 1)) != nil)
|
|
error(err);
|
|
poperror();
|
|
poperror();
|
|
cclose(c);
|
|
return fw;
|
|
}
|
|
|
|
|
|
static int
|
|
gotirq(void *arg)
|
|
{
|
|
Ctlr *ctlr = arg;
|
|
return (ctlr->wait.m & ctlr->wait.w) != 0;
|
|
}
|
|
|
|
static u32int
|
|
irqwait(Ctlr *ctlr, u32int mask, int timeout)
|
|
{
|
|
u32int r;
|
|
|
|
ilock(ctlr);
|
|
r = ctlr->wait.m & mask;
|
|
if(r == 0){
|
|
ctlr->wait.w = mask;
|
|
iunlock(ctlr);
|
|
if(!waserror()){
|
|
tsleep(&ctlr->wait, gotirq, ctlr, timeout);
|
|
poperror();
|
|
}
|
|
ilock(ctlr);
|
|
ctlr->wait.w = 0;
|
|
r = ctlr->wait.m & mask;
|
|
}
|
|
ctlr->wait.m &= ~r;
|
|
iunlock(ctlr);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
rbplant(Ctlr *ctlr, int i)
|
|
{
|
|
Block *b;
|
|
|
|
b = iallocb(Rbufsize + 256);
|
|
if(b == nil)
|
|
return -1;
|
|
b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, 256);
|
|
memset(b->rp, 0, Rdscsize);
|
|
ctlr->rx.b[i] = b;
|
|
ctlr->rx.p[i] = PCIWADDR(b->rp) >> 8;
|
|
return 0;
|
|
}
|
|
|
|
static char*
|
|
initring(Ctlr *ctlr)
|
|
{
|
|
RXQ *rx;
|
|
TXQ *tx;
|
|
int i, q;
|
|
|
|
rx = &ctlr->rx;
|
|
if(rx->b == nil)
|
|
rx->b = malloc(sizeof(Block*) * Nrx);
|
|
if(rx->p == nil)
|
|
rx->p = mallocalign(sizeof(u32int) * Nrx, 256, 0, 0);
|
|
if(rx->s == nil)
|
|
rx->s = mallocalign(Rstatsize, 16, 0, 0);
|
|
if(rx->b == nil || rx->p == nil || rx->s == nil)
|
|
return "no memory for rx ring";
|
|
memset(ctlr->rx.s, 0, Rstatsize);
|
|
for(i=0; i<Nrx; i++){
|
|
rx->p[i] = 0;
|
|
if(rx->b[i] != nil){
|
|
freeb(rx->b[i]);
|
|
rx->b[i] = nil;
|
|
}
|
|
if(rbplant(ctlr, i) < 0)
|
|
return "no memory for rx descriptors";
|
|
}
|
|
rx->i = 0;
|
|
|
|
if(ctlr->sched.s == nil)
|
|
ctlr->sched.s = mallocalign(512 * nelem(ctlr->tx) * 2, 1024, 0, 0);
|
|
if(ctlr->sched.s == nil)
|
|
return "no memory for sched buffer";
|
|
memset(ctlr->sched.s, 0, 512 * nelem(ctlr->tx));
|
|
|
|
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, 256, 0, 0);
|
|
if(tx->c == nil)
|
|
tx->c = mallocalign(Tcmdsize * Ntx, 4, 0, 0);
|
|
if(tx->b == nil || tx->d == nil || tx->c == nil)
|
|
return "no memory for tx ring";
|
|
memset(tx->d, 0, Tdscsize * Ntx);
|
|
memset(tx->c, 0, Tcmdsize * Ntx);
|
|
for(i=0; i<Ntx; i++){
|
|
if(tx->b[i] != nil){
|
|
freeb(tx->b[i]);
|
|
tx->b[i] = nil;
|
|
}
|
|
}
|
|
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);
|
|
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
reset(Ctlr *ctlr)
|
|
{
|
|
char *err;
|
|
int i, q;
|
|
|
|
if(ctlr->power)
|
|
poweroff(ctlr);
|
|
if((err = initring(ctlr)) != nil)
|
|
return err;
|
|
if((err = poweron(ctlr)) != nil)
|
|
return err;
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
|
|
nicunlock(ctlr);
|
|
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi);
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
if(ctlr->type != Type4965)
|
|
prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis);
|
|
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));
|
|
}
|
|
nicunlock(ctlr);
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
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)
|
|
csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvRadioIqInvert);
|
|
nicunlock(ctlr);
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
csr32w(ctlr, FhRxConfig, 0);
|
|
csr32w(ctlr, FhRxWptr, 0);
|
|
csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p) >> 8);
|
|
csr32w(ctlr, FhStatusWptr, PCIWADDR(ctlr->rx.s) >> 4);
|
|
csr32w(ctlr, FhRxConfig,
|
|
FhRxConfigEna |
|
|
FhRxConfigIgnRxfEmpty |
|
|
FhRxConfigIrqDstHost |
|
|
FhRxConfigSingleFrame |
|
|
(Nrxlog << FhRxConfigNrbdShift));
|
|
csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7);
|
|
nicunlock(ctlr);
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
if(ctlr->type != Type4965)
|
|
prphwrite(ctlr, SchedTxFact5000, 0);
|
|
else
|
|
prphwrite(ctlr, SchedTxFact4965, 0);
|
|
csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4);
|
|
for(q = (ctlr->type != Type4965) ? 19 : 15; q >= 0; q--)
|
|
csr32w(ctlr, FhCbbcQueue + q*4, PCIWADDR(ctlr->tx[q].d) >> 8);
|
|
nicunlock(ctlr);
|
|
|
|
for(i = (ctlr->type != Type4965) ? 7 : 6; i >= 0; i--)
|
|
csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna);
|
|
|
|
csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
|
|
csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked);
|
|
|
|
ctlr->broken = 0;
|
|
ctlr->wait.m = 0;
|
|
ctlr->wait.w = 0;
|
|
|
|
ctlr->ie = Idefmask;
|
|
csr32w(ctlr, Imr, ctlr->ie);
|
|
csr32w(ctlr, Isr, ~0);
|
|
|
|
if(ctlr->type >= Type6000)
|
|
csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff);
|
|
|
|
return nil;
|
|
}
|
|
|
|
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->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;
|
|
|
|
/* 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*
|
|
postboot(Ctlr *ctlr)
|
|
{
|
|
uint ctxoff, ctxlen, dramaddr;
|
|
char *err;
|
|
int i, q;
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
|
|
if(ctlr->type != Type4965){
|
|
dramaddr = SchedDramAddr5000;
|
|
ctxoff = SchedCtxOff5000;
|
|
ctxlen = SchedCtxLen5000;
|
|
} 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);
|
|
|
|
csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2);
|
|
|
|
if(ctlr->type != Type4965){
|
|
/* Enable chain mode for all queues, except command queue 4. */
|
|
prphwrite(ctlr, SchedQChainSel5000, 0xfffef);
|
|
prphwrite(ctlr, SchedAggrSel5000, 0);
|
|
|
|
for(q=0; q<20; q++){
|
|
prphwrite(ctlr, SchedQueueRdptr5000 + q*4, 0);
|
|
csr32w(ctlr, HbusTargWptr, q << 8);
|
|
|
|
memwrite(ctlr, ctlr->sched.base + ctxoff + q*8, 0);
|
|
/* Set scheduler window size and frame limit. */
|
|
memwrite(ctlr, ctlr->sched.base + ctxoff + q*8 + 4, 64<<16 | 64);
|
|
}
|
|
/* Enable interrupts for all our 20 queues. */
|
|
prphwrite(ctlr, SchedIntrMask5000, 0xfffff);
|
|
|
|
/* Identify TX FIFO rings (0-7). */
|
|
prphwrite(ctlr, SchedTxFact5000, 0xff);
|
|
} else {
|
|
/* Disable chain mode for all our 16 queues. */
|
|
prphwrite(ctlr, SchedQChainSel4965, 0);
|
|
|
|
for(q=0; q<16; q++) {
|
|
prphwrite(ctlr, SchedQueueRdptr4965 + q*4, 0);
|
|
csr32w(ctlr, HbusTargWptr, q << 8);
|
|
|
|
/* Set scheduler window size. */
|
|
memwrite(ctlr, ctlr->sched.base + ctxoff + q*8, 64);
|
|
/* Set scheduler window size and frame limit. */
|
|
memwrite(ctlr, ctlr->sched.base + ctxoff + q*8 + 4, 64<<16);
|
|
}
|
|
/* Enable interrupts for all our 16 queues. */
|
|
prphwrite(ctlr, SchedIntrMask4965, 0xffff);
|
|
|
|
/* Identify TX FIFO rings (0-7). */
|
|
prphwrite(ctlr, SchedTxFact4965, 0xff);
|
|
}
|
|
|
|
/* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */
|
|
for(q=0; q<7; q++){
|
|
if(ctlr->type != Type4965){
|
|
static uchar qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 };
|
|
prphwrite(ctlr, SchedQueueStatus5000 + q*4, 0x00ff0018 | qid2fifo[q]);
|
|
} else {
|
|
static uchar qid2fifo[] = { 3, 2, 1, 0, 4, 5, 6 };
|
|
prphwrite(ctlr, SchedQueueStatus4965 + q*4, 0x0007fc01 | qid2fifo[q]<<1);
|
|
}
|
|
}
|
|
nicunlock(ctlr);
|
|
|
|
if(ctlr->type != Type4965){
|
|
uchar c[Tcmdsize];
|
|
|
|
/* disable wimax coexistance */
|
|
memset(c, 0, sizeof(c));
|
|
if((err = cmd(ctlr, 90, c, 4+4*16)) != nil)
|
|
return err;
|
|
|
|
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};
|
|
|
|
/* send calibration records (runtime firmware) */
|
|
for(q=0; q<nelem(cmds); q++){
|
|
Block *b;
|
|
|
|
i = cmds[q];
|
|
if(i == 8 && ctlr->type != Type5150 && ctlr->type != Type2030)
|
|
continue;
|
|
if(i == 17 && (ctlr->type >= Type6000 || ctlr->type == Type5150) &&
|
|
ctlr->type != Type2030)
|
|
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:
|
|
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;
|
|
}
|
|
|
|
/* set tx antenna config */
|
|
put32(c, ctlr->rfcfg.txantmask & 7);
|
|
if((err = cmd(ctlr, 152, c, 4)) != nil)
|
|
return err;
|
|
|
|
if(ctlr->type == Type2030){
|
|
if((err = sendbtcoexadv(ctlr)) != nil)
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size)
|
|
{
|
|
uchar *dma;
|
|
char *err;
|
|
|
|
dma = mallocalign(size, 16, 0, 0);
|
|
if(dma == nil)
|
|
return "no memory for dma";
|
|
memmove(dma, data, size);
|
|
coherence();
|
|
if((err = niclock(ctlr)) != 0){
|
|
free(dma);
|
|
return err;
|
|
}
|
|
csr32w(ctlr, FhTxConfig + 9*32, 0);
|
|
csr32w(ctlr, FhSramAddr + 9*4, dst);
|
|
csr32w(ctlr, FhTfbdCtrl0 + 9*8, PCIWADDR(dma));
|
|
csr32w(ctlr, FhTfbdCtrl1 + 9*8, size);
|
|
csr32w(ctlr, FhTxBufStatus + 9*32,
|
|
(1<<FhTxBufStatusTbNumShift) |
|
|
(1<<FhTxBufStatusTbIdxShift) |
|
|
FhTxBufStatusTfbdValid);
|
|
csr32w(ctlr, FhTxConfig + 9*32, FhTxConfigDmaEna | FhTxConfigCirqHostEndTfd);
|
|
nicunlock(ctlr);
|
|
if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx){
|
|
free(dma);
|
|
return "dma error / timeout";
|
|
}
|
|
free(dma);
|
|
return 0;
|
|
}
|
|
|
|
static 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 = loadfirmware1(ctlr, 0x00000000, fw->init.text.data, fw->init.text.size)) != nil)
|
|
return err;
|
|
if((err = loadfirmware1(ctlr, 0x00800000, fw->init.data.data, fw->init.data.size)) != nil)
|
|
return err;
|
|
csr32w(ctlr, Reset, 0);
|
|
if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
|
|
return "init firmware boot failed";
|
|
if((err = postboot(ctlr)) != nil)
|
|
return err;
|
|
if((err = reset(ctlr)) != nil)
|
|
return err;
|
|
}
|
|
if((err = loadfirmware1(ctlr, 0x00000000, fw->main.text.data, fw->main.text.size)) != nil)
|
|
return err;
|
|
if((err = loadfirmware1(ctlr, 0x00800000, fw->main.data.data, fw->main.data.size)) != nil)
|
|
return err;
|
|
csr32w(ctlr, Reset, 0);
|
|
if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
|
|
return "main firmware boot failed";
|
|
return postboot(ctlr);
|
|
}
|
|
|
|
size = ROUND(fw->init.data.size, 16) + ROUND(fw->init.text.size, 16);
|
|
dma = mallocalign(size, 16, 0, 0);
|
|
if(dma == nil)
|
|
return "no memory for dma";
|
|
|
|
if((err = niclock(ctlr)) != nil){
|
|
free(dma);
|
|
return err;
|
|
}
|
|
|
|
p = dma;
|
|
memmove(p, fw->init.data.data, fw->init.data.size);
|
|
coherence();
|
|
prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 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);
|
|
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;
|
|
delay(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 "init firmware boot failed";
|
|
}
|
|
free(dma);
|
|
|
|
size = ROUND(fw->main.data.size, 16) + ROUND(fw->main.text.size, 16);
|
|
dma = mallocalign(size, 16, 0, 0);
|
|
if(dma == nil)
|
|
return "no memory for dma";
|
|
if((err = niclock(ctlr)) != nil){
|
|
free(dma);
|
|
return err;
|
|
}
|
|
p = dma;
|
|
memmove(p, fw->main.data.data, fw->main.data.size);
|
|
coherence();
|
|
prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 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);
|
|
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)
|
|
{
|
|
uchar *d, *c;
|
|
TXQ *q;
|
|
|
|
assert(qid < nelem(ctlr->tx));
|
|
assert(size <= Tcmdsize-4);
|
|
|
|
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";
|
|
}
|
|
q->n++;
|
|
|
|
q->lastcmd = code;
|
|
q->b[q->i] = block;
|
|
c = q->c + q->i * Tcmdsize;
|
|
d = q->d + q->i * Tdscsize;
|
|
|
|
/* build command */
|
|
c[0] = code;
|
|
c[1] = 0; /* flags */
|
|
c[2] = q->i;
|
|
c[3] = qid;
|
|
|
|
if(size > 0)
|
|
memmove(c+4, data, size);
|
|
|
|
size += 4;
|
|
|
|
/* build descriptor */
|
|
*d++ = 0;
|
|
*d++ = 0;
|
|
*d++ = 0;
|
|
*d++ = 1 + (block != nil); /* nsegs */
|
|
put32(d, PCIWADDR(c)); d += 4;
|
|
put16(d, size << 4); d += 2;
|
|
if(block != nil){
|
|
size = BLEN(block);
|
|
put32(d, PCIWADDR(block->rp)); d += 4;
|
|
put16(d, size << 4);
|
|
}
|
|
|
|
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";
|
|
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)
|
|
return err;
|
|
return flushq(ctlr, 4);
|
|
}
|
|
|
|
static void
|
|
setled(Ctlr *ctlr, int which, int on, int off)
|
|
{
|
|
uchar c[8];
|
|
|
|
csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl);
|
|
|
|
memset(c, 0, sizeof(c));
|
|
put32(c, 10000);
|
|
c[4] = which;
|
|
c[5] = on;
|
|
c[6] = off;
|
|
cmd(ctlr, 72, c, sizeof(c));
|
|
}
|
|
|
|
static void
|
|
addnode(Ctlr *ctlr, uchar id, uchar *addr)
|
|
{
|
|
uchar c[Tcmdsize], *p;
|
|
|
|
memset(p = c, 0, sizeof(c));
|
|
*p++ = 0; /* control (1 = update) */
|
|
p += 3; /* reserved */
|
|
memmove(p, addr, 6);
|
|
p += 6;
|
|
p += 2; /* reserved */
|
|
*p++ = id; /* node id */
|
|
p++; /* flags */
|
|
p += 2; /* reserved */
|
|
p += 2; /* kflags */
|
|
p++; /* tcs2 */
|
|
p++; /* reserved */
|
|
p += 5*2; /* ttak */
|
|
p++; /* kid */
|
|
p++; /* reserved */
|
|
p += 16; /* key */
|
|
if(ctlr->type != Type4965){
|
|
p += 8; /* tcs */
|
|
p += 8; /* rxmic */
|
|
p += 8; /* txmic */
|
|
}
|
|
p += 4; /* htflags */
|
|
p += 4; /* mask */
|
|
p += 2; /* disable tid */
|
|
p += 2; /* reserved */
|
|
p++; /* add ba tid */
|
|
p++; /* del ba tid */
|
|
p += 2; /* add ba ssn */
|
|
p += 4; /* reserved */
|
|
cmd(ctlr, 24, c, p - c);
|
|
}
|
|
|
|
static void
|
|
rxon(Ether *edev, Wnode *bss)
|
|
{
|
|
uchar c[Tcmdsize], *p;
|
|
int filter, flags;
|
|
Ctlr *ctlr;
|
|
char *err;
|
|
|
|
ctlr = edev->ctlr;
|
|
filter = FilterNoDecrypt | FilterMulticast | FilterBeacon;
|
|
if(ctlr->prom){
|
|
filter |= FilterPromisc;
|
|
if(bss != nil)
|
|
ctlr->channel = bss->channel;
|
|
bss = nil;
|
|
}
|
|
flags = RFlagTSF | RFlagCTSToSelf | RFlag24Ghz | RFlagAuto;
|
|
if(bss != nil){
|
|
if(bss->cap & (1<<5))
|
|
flags |= RFlagShPreamble;
|
|
if(bss->cap & (1<<10))
|
|
flags |= RFlagShSlot;
|
|
ctlr->channel = bss->channel;
|
|
memmove(ctlr->bssid, bss->bssid, Eaddrlen);
|
|
ctlr->aid = bss->aid;
|
|
if(ctlr->aid != 0){
|
|
filter |= FilterBSS;
|
|
filter &= ~FilterBeacon;
|
|
ctlr->bssnodeid = -1;
|
|
} else
|
|
ctlr->bcastnodeid = -1;
|
|
} else {
|
|
memmove(ctlr->bssid, edev->bcast, Eaddrlen);
|
|
ctlr->aid = 0;
|
|
ctlr->bcastnodeid = -1;
|
|
ctlr->bssnodeid = -1;
|
|
}
|
|
|
|
if(ctlr->aid != 0)
|
|
setled(ctlr, 2, 0, 1); /* on when associated */
|
|
else if(memcmp(ctlr->bssid, edev->bcast, Eaddrlen) != 0)
|
|
setled(ctlr, 2, 10, 10); /* slow blink when connecting */
|
|
else
|
|
setled(ctlr, 2, 5, 5); /* fast blink when scanning */
|
|
|
|
if(ctlr->wifi->debug)
|
|
print("#l%d: rxon: bssid %E, aid %x, channel %d, filter %x, flags %x\n",
|
|
edev->ctlrno, ctlr->bssid, ctlr->aid, ctlr->channel, filter, flags);
|
|
|
|
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, flags);
|
|
p += 4;
|
|
put32(p, filter);
|
|
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("rxon: %s\n", err);
|
|
return;
|
|
}
|
|
|
|
if(ctlr->bcastnodeid == -1){
|
|
ctlr->bcastnodeid = (ctlr->type != Type4965) ? 15 : 31;
|
|
addnode(ctlr, ctlr->bcastnodeid, edev->bcast);
|
|
}
|
|
if(ctlr->bssnodeid == -1 && bss != nil && ctlr->aid != 0){
|
|
ctlr->bssnodeid = 0;
|
|
addnode(ctlr, ctlr->bssnodeid, bss->bssid);
|
|
}
|
|
}
|
|
|
|
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
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
static void
|
|
transmit(Wifi *wifi, Wnode *wn, Block *b)
|
|
{
|
|
int flags, nodeid, rate, ant;
|
|
uchar c[Tcmdsize], *p;
|
|
Ether *edev;
|
|
Ctlr *ctlr;
|
|
Wifipkt *w;
|
|
char *err;
|
|
|
|
edev = wifi->ether;
|
|
ctlr = edev->ctlr;
|
|
|
|
qlock(ctlr);
|
|
if(ctlr->attached == 0 || ctlr->broken){
|
|
qunlock(ctlr);
|
|
freeb(b);
|
|
return;
|
|
}
|
|
|
|
if((wn->channel != ctlr->channel)
|
|
|| (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0)))
|
|
rxon(edev, wn);
|
|
|
|
if(b == nil){
|
|
/* association note has no data to transmit */
|
|
qunlock(ctlr);
|
|
return;
|
|
}
|
|
|
|
flags = 0;
|
|
nodeid = ctlr->bcastnodeid;
|
|
p = wn->minrate;
|
|
w = (Wifipkt*)b->rp;
|
|
if((w->a1[0] & 1) == 0){
|
|
flags |= TFlagNeedACK;
|
|
|
|
if(BLEN(b) > 512-4)
|
|
flags |= TFlagNeedRTS;
|
|
|
|
if((w->fc[0] & 0x0c) == 0x08 && ctlr->bssnodeid != -1){
|
|
nodeid = ctlr->bssnodeid;
|
|
p = wn->actrate;
|
|
}
|
|
|
|
if(flags & (TFlagNeedRTS|TFlagNeedCTS)){
|
|
if(ctlr->type != Type4965){
|
|
flags &= ~(TFlagNeedRTS|TFlagNeedCTS);
|
|
flags |= TFlagNeedProtection;
|
|
} else
|
|
flags |= TFlagFullTxOp;
|
|
}
|
|
}
|
|
if(p >= wifi->rates)
|
|
rate = p - wifi->rates;
|
|
else
|
|
rate = 0;
|
|
qunlock(ctlr);
|
|
|
|
/* 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++ = nodeid;
|
|
*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 */
|
|
if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){
|
|
print("transmit: %s\n", 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;
|
|
|
|
ctlr->bcastnodeid = -1;
|
|
ctlr->bssnodeid = -1;
|
|
ctlr->aid = 0;
|
|
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->wifi == nil){
|
|
qsetlimit(edev->oq, MaxQueue);
|
|
|
|
ctlr->wifi = wifiattach(edev, transmit);
|
|
/* tested with 2230, it has transmit issues using higher bit rates */
|
|
if(ctlr->type != Type2030)
|
|
ctlr->wifi->rates = iwlrates;
|
|
}
|
|
|
|
if(ctlr->fw == nil){
|
|
char *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 %ux+%ux+%ux+%ux+%ux\n",
|
|
edev->ctlrno, fn,
|
|
fw->rev, fw->build,
|
|
fw->main.text.size, fw->main.data.size,
|
|
fw->init.text.size, fw->init.data.size,
|
|
fw->boot.text.size);
|
|
ctlr->fw = fw;
|
|
}
|
|
|
|
if((err = reset(ctlr)) != nil)
|
|
error(err);
|
|
if((err = boot(ctlr)) != nil)
|
|
error(err);
|
|
|
|
ctlr->bcastnodeid = -1;
|
|
ctlr->bssnodeid = -1;
|
|
ctlr->channel = 1;
|
|
ctlr->aid = 0;
|
|
|
|
setoptions(edev);
|
|
|
|
ctlr->attached = 1;
|
|
|
|
kproc("iwlrecover", iwlrecover, edev);
|
|
}
|
|
qunlock(ctlr);
|
|
poperror();
|
|
}
|
|
|
|
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;
|
|
|
|
bb = nil;
|
|
for(hw = get16(rx->s) % Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){
|
|
uchar type, flags, idx, qid;
|
|
u32int len;
|
|
|
|
b = rx->b[rx->i];
|
|
if(b == nil)
|
|
continue;
|
|
|
|
d = b->rp;
|
|
len = get32(d); d += 4;
|
|
type = *d++;
|
|
flags = *d++;
|
|
USED(flags);
|
|
idx = *d++;
|
|
qid = *d++;
|
|
|
|
if(bb != nil){
|
|
freeb(bb);
|
|
bb = nil;
|
|
}
|
|
if((qid & 0x80) == 0 && qid < nelem(ctlr->tx)){
|
|
tx = &ctlr->tx[qid];
|
|
if(tx->n > 0){
|
|
bb = tx->b[idx];
|
|
tx->b[idx] = nil;
|
|
tx->n--;
|
|
|
|
wakeup(tx);
|
|
}
|
|
}
|
|
|
|
len &= 0x3fff;
|
|
if(len < 4 || type == 0)
|
|
continue;
|
|
|
|
len -= 4;
|
|
switch(type){
|
|
case 1: /* microcontroller ready */
|
|
setfwinfo(ctlr, d, len);
|
|
break;
|
|
case 24: /* add node done */
|
|
break;
|
|
case 28: /* tx done */
|
|
if(ctlr->type == Type4965){
|
|
if(len <= 20 || d[20] == 1 || d[20] == 2)
|
|
break;
|
|
} else {
|
|
if(len <= 32 || d[32] == 1 || d[32] == 2)
|
|
break;
|
|
}
|
|
wifitxfail(ctlr->wifi, bb);
|
|
break;
|
|
case 102: /* calibration result (Type5000 only) */
|
|
if(len < 4)
|
|
break;
|
|
idx = d[0];
|
|
if(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;
|
|
continue;
|
|
case 103: /* calibration done (Type5000 only) */
|
|
ctlr->calib.done = 1;
|
|
break;
|
|
case 130: /* start scan */
|
|
break;
|
|
case 132: /* stop scan */
|
|
break;
|
|
case 156: /* rx statistics */
|
|
break;
|
|
case 157: /* beacon statistics */
|
|
break;
|
|
case 161: /* state changed */
|
|
break;
|
|
case 162: /* beacon missed */
|
|
break;
|
|
case 192: /* rx phy */
|
|
break;
|
|
case 195: /* rx done */
|
|
if(d + 2 > b->lim)
|
|
break;
|
|
d += d[1];
|
|
d += 56;
|
|
case 193: /* mpdu rx done */
|
|
if(d + 4 > b->lim)
|
|
break;
|
|
len = get16(d); d += 4;
|
|
if(d + len + 4 > b->lim)
|
|
break;
|
|
if((get32(d + len) & 3) != 3)
|
|
break;
|
|
if(ctlr->wifi == nil)
|
|
break;
|
|
if(rbplant(ctlr, rx->i) < 0)
|
|
break;
|
|
b->rp = d;
|
|
b->wp = d + len;
|
|
wifiiq(ctlr->wifi, b);
|
|
continue;
|
|
case 197: /* rx compressed ba */
|
|
break;
|
|
}
|
|
}
|
|
csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7);
|
|
if(bb != nil)
|
|
freeb(bb);
|
|
}
|
|
|
|
static void
|
|
iwlinterrupt(Ureg*, void *arg)
|
|
{
|
|
u32int isr, fhisr;
|
|
Ether *edev;
|
|
Ctlr *ctlr;
|
|
|
|
edev = arg;
|
|
ctlr = edev->ctlr;
|
|
ilock(ctlr);
|
|
csr32w(ctlr, Imr, 0);
|
|
isr = csr32r(ctlr, Isr);
|
|
fhisr = csr32r(ctlr, FhIsr);
|
|
if(isr == 0xffffffff || (isr & 0xfffffff0) == 0xa5a5a5a0){
|
|
iunlock(ctlr);
|
|
return;
|
|
}
|
|
if(isr == 0 && fhisr == 0)
|
|
goto done;
|
|
csr32w(ctlr, Isr, isr);
|
|
csr32w(ctlr, FhIsr, fhisr);
|
|
if((isr & (Iswrx | Ifhrx | Irxperiodic)) || (fhisr & Ifhrx))
|
|
receive(ctlr);
|
|
if(isr & Ierr){
|
|
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;
|
|
}
|
|
|
|
static Ctlr *iwlhead, *iwltail;
|
|
|
|
static void
|
|
iwlpci(void)
|
|
{
|
|
Pcidev *pdev;
|
|
|
|
pdev = nil;
|
|
while(pdev = pcimatch(pdev, 0, 0)) {
|
|
Ctlr *ctlr;
|
|
void *mem;
|
|
|
|
if(pdev->ccrb != 2 || pdev->ccru != 0x80)
|
|
continue;
|
|
if(pdev->vid != 0x8086)
|
|
continue;
|
|
|
|
switch(pdev->did){
|
|
default:
|
|
continue;
|
|
case 0x0084: /* WiFi Link 1000 */
|
|
case 0x4229: /* WiFi Link 4965 */
|
|
case 0x4230: /* WiFi Link 4965 */
|
|
case 0x4232: /* Wifi Link 5100 */
|
|
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 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 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 */
|
|
break;
|
|
}
|
|
|
|
/* Clear device-specific "PCI retry timeout" register (41h). */
|
|
if(pcicfgr8(pdev, 0x41) != 0)
|
|
pcicfgw8(pdev, 0x41, 0);
|
|
|
|
/* Clear interrupt disable bit. Hardware bug workaround. */
|
|
if(pdev->pcr & 0x400){
|
|
pdev->pcr &= ~0x400;
|
|
pcicfgw16(pdev, PciPCR, pdev->pcr);
|
|
}
|
|
|
|
pcisetbme(pdev);
|
|
pcisetpms(pdev, 0);
|
|
|
|
ctlr = malloc(sizeof(Ctlr));
|
|
if(ctlr == nil) {
|
|
print("iwl: unable to alloc Ctlr\n");
|
|
continue;
|
|
}
|
|
ctlr->port = pdev->mem[0].bar & ~0x0F;
|
|
mem = vmap(pdev->mem[0].bar & ~0x0F, pdev->mem[0].size);
|
|
if(mem == nil) {
|
|
print("iwl: can't map %8.8luX\n", pdev->mem[0].bar);
|
|
free(ctlr);
|
|
continue;
|
|
}
|
|
ctlr->nic = mem;
|
|
ctlr->pdev = pdev;
|
|
ctlr->type = (csr32r(ctlr, Rev) >> 4) & 0x1F;
|
|
|
|
if(fwname[ctlr->type] == nil){
|
|
print("iwl: unsupported controller type %d\n", ctlr->type);
|
|
vunmap(mem, pdev->mem[0].size);
|
|
free(ctlr);
|
|
continue;
|
|
}
|
|
|
|
if(iwlhead != nil)
|
|
iwltail->link = ctlr;
|
|
else
|
|
iwlhead = ctlr;
|
|
iwltail = ctlr;
|
|
}
|
|
}
|
|
|
|
static int
|
|
iwlpnp(Ether* edev)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
if(iwlhead == nil)
|
|
iwlpci();
|
|
again:
|
|
for(ctlr = iwlhead; ctlr != nil; ctlr = ctlr->link){
|
|
if(ctlr->active)
|
|
continue;
|
|
if(edev->port == 0 || edev->port == ctlr->port){
|
|
ctlr->active = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ctlr == nil)
|
|
return -1;
|
|
|
|
edev->ctlr = ctlr;
|
|
edev->port = ctlr->port;
|
|
edev->irq = ctlr->pdev->intl;
|
|
edev->tbdf = ctlr->pdev->tbdf;
|
|
edev->arg = edev;
|
|
edev->attach = iwlattach;
|
|
edev->ifstat = iwlifstat;
|
|
edev->ctl = iwlctl;
|
|
edev->shutdown = iwlshutdown;
|
|
edev->promiscuous = iwlpromiscuous;
|
|
edev->multicast = iwlmulticast;
|
|
edev->mbps = 54;
|
|
|
|
if(iwlinit(edev) < 0){
|
|
edev->ctlr = nil;
|
|
goto again;
|
|
}
|
|
|
|
intrenable(edev->irq, iwlinterrupt, edev, edev->tbdf, edev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
etheriwllink(void)
|
|
{
|
|
addethercard("iwl", iwlpnp);
|
|
}
|