1870 lines
35 KiB
C
1870 lines
35 KiB
C
#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 = 6,
|
|
Nrx = 1<<Nrxlog,
|
|
|
|
Rbufsize = 3*1024,
|
|
Rdscsize = 8,
|
|
|
|
Tdscsize = 64,
|
|
Tcmdsize = 128,
|
|
};
|
|
|
|
/* registers */
|
|
enum {
|
|
Cfg = 0x000,
|
|
AlmMb = 1<<8,
|
|
AlmMm = 1<<9,
|
|
SkuMrc = 1<<10,
|
|
RevD = 1<<11,
|
|
TypeB = 1<<12,
|
|
Isr = 0x008,
|
|
Imr = 0x00c,
|
|
Ialive = 1<<0,
|
|
Iwakeup = 1<<1,
|
|
Iswrx = 1<<3,
|
|
Irftoggled = 1<<7,
|
|
Iswerr = 1<<25,
|
|
Ifhtx = 1<<27,
|
|
Ihwerr = 1<<29,
|
|
Ifhrx = 1<<31,
|
|
Ierr = Iswerr | Ihwerr,
|
|
Idefmask = Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Irftoggled,
|
|
FhIsr = 0x010,
|
|
GpioIn = 0x018,
|
|
Reset = 0x020,
|
|
Nevo = 1<<0,
|
|
SW = 1<<7,
|
|
MasterDisabled = 1<<8,
|
|
StopMaster = 1<<9,
|
|
|
|
Gpc = 0x024,
|
|
MacAccessEna = 1<<0,
|
|
MacClockReady = 1<<0,
|
|
InitDone = 1<<2,
|
|
MacAccessReq = 1<<3,
|
|
NicSleep = 1<<4,
|
|
RfKill = 1<<27,
|
|
Eeprom = 0x02c,
|
|
EepromGp = 0x030,
|
|
|
|
UcodeGp1Clr = 0x05c,
|
|
UcodeGp1RfKill = 1<<1,
|
|
UcodeGp1CmdBlocked = 1<<2,
|
|
UcodeGp2 = 0x060,
|
|
|
|
GioChicken = 0x100,
|
|
L1AnoL0Srx = 1<<23,
|
|
AnaPll = 0x20c,
|
|
Init = 1<<24,
|
|
|
|
PrphWaddr = 0x444,
|
|
PrphRaddr = 0x448,
|
|
PrphWdata = 0x44c,
|
|
PrphRdata = 0x450,
|
|
HbusTargWptr = 0x460,
|
|
};
|
|
|
|
/*
|
|
* Flow-Handler registers.
|
|
*/
|
|
enum {
|
|
FhCbbcCtrl = 0x940,
|
|
FhCbbcBase = 0x944,
|
|
FhRxConfig = 0xc00,
|
|
FhRxConfigDmaEna = 1<<31,
|
|
FhRxConfigRdrbdEna = 1<<29,
|
|
FhRxConfigWrstatusEna = 1<<27,
|
|
FhRxConfigMaxfrag = 1<<24,
|
|
FhRxConfigIrqDstHost = 1<<12,
|
|
|
|
FhRxConfigNrdbShift = 20,
|
|
FhRxConfigIrqRbthShift = 4,
|
|
FhRxBase = 0xc04,
|
|
FhRxWptr = 0xc20,
|
|
FhRxRptrAddr = 0xc24,
|
|
FhRssrTbl = 0xcc0,
|
|
FhRxStatus = 0xcc4,
|
|
FhTxConfig = 0xd00, // +q*32
|
|
FhTxBase = 0xe80,
|
|
FhMsgConfig = 0xe88,
|
|
FhTxStatus = 0xe90,
|
|
};
|
|
|
|
/*
|
|
* NIC internal memory offsets.
|
|
*/
|
|
enum {
|
|
AlmSchedMode = 0x2e00,
|
|
AlmSchedArastat = 0x2e04,
|
|
AlmSchedTxfact = 0x2e10,
|
|
AlmSchedTxf4mf = 0x2e14,
|
|
AlmSchedTxf5mf = 0x2e20,
|
|
AlmSchedBP1 = 0x2e2c,
|
|
AlmSchedBP2 = 0x2e30,
|
|
ApmgClkEna = 0x3004,
|
|
ApmgClkDis = 0x3008,
|
|
DmaClkRqt = 1<<9,
|
|
BsmClkRqt = 1<<11,
|
|
ApmgPs = 0x300c,
|
|
PwrSrcVMain = 0<<24,
|
|
PwrSrcMask = 3<<24,
|
|
|
|
ApmgPciStt = 0x3010,
|
|
|
|
BsmWrCtrl = 0x3400,
|
|
BsmWrMemSrc = 0x3404,
|
|
BsmWrMemDst = 0x3408,
|
|
BsmWrDwCount = 0x340c,
|
|
BsmDramTextAddr = 0x3490,
|
|
BsmDramTextSize = 0x3494,
|
|
BsmDramDataAddr = 0x3498,
|
|
BsmDramDataSize = 0x349c,
|
|
BsmSramBase = 0x3800,
|
|
};
|
|
|
|
enum {
|
|
FilterPromisc = 1<<0,
|
|
FilterCtl = 1<<1,
|
|
FilterMulticast = 1<<2,
|
|
FilterNoDecrypt = 1<<3,
|
|
FilterBSS = 1<<5,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
typedef struct FWSect FWSect;
|
|
typedef struct FWImage FWImage;
|
|
|
|
typedef struct TXQ TXQ;
|
|
typedef struct RXQ RXQ;
|
|
|
|
typedef struct Shared Shared;
|
|
typedef struct Sample Sample;
|
|
typedef struct Powergrp Powergrp;
|
|
|
|
typedef struct Ctlr Ctlr;
|
|
|
|
struct FWSect
|
|
{
|
|
uchar *data;
|
|
uint size;
|
|
};
|
|
|
|
struct FWImage
|
|
{
|
|
struct {
|
|
FWSect text;
|
|
FWSect data;
|
|
} init, main, boot;
|
|
|
|
uint version;
|
|
uchar data[];
|
|
};
|
|
|
|
struct TXQ
|
|
{
|
|
uint n;
|
|
uint i;
|
|
Block **b;
|
|
uchar *d;
|
|
uchar *c;
|
|
|
|
uint lastcmd;
|
|
|
|
Rendez;
|
|
QLock;
|
|
};
|
|
|
|
struct RXQ
|
|
{
|
|
uint i;
|
|
Block **b;
|
|
u32int *p;
|
|
};
|
|
|
|
struct Shared
|
|
{
|
|
u32int txbase[8];
|
|
u32int next;
|
|
u32int reserved[2];
|
|
};
|
|
|
|
struct Sample
|
|
{
|
|
uchar index;
|
|
char power;
|
|
};
|
|
|
|
struct Powergrp
|
|
{
|
|
uchar chan;
|
|
char maxpwr;
|
|
short temp;
|
|
Sample samples[5];
|
|
};
|
|
|
|
struct Ctlr {
|
|
Lock;
|
|
QLock;
|
|
|
|
Ctlr *link;
|
|
Pcidev *pdev;
|
|
Wifi *wifi;
|
|
|
|
int port;
|
|
int power;
|
|
int active;
|
|
int broken;
|
|
int attached;
|
|
|
|
int temp;
|
|
u32int ie;
|
|
u32int *nic;
|
|
|
|
/* 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[8];
|
|
|
|
struct {
|
|
Rendez;
|
|
u32int m;
|
|
u32int w;
|
|
} wait;
|
|
|
|
struct {
|
|
uchar cap;
|
|
u16int rev;
|
|
uchar type;
|
|
|
|
char regdom[4+1];
|
|
|
|
Powergrp pwrgrps[5];
|
|
} eeprom;
|
|
|
|
char maxpwr[256];
|
|
|
|
Shared *shared;
|
|
|
|
FWImage *fw;
|
|
};
|
|
|
|
static void setled(Ctlr *ctlr, int which, int on, int off);
|
|
|
|
#define csr32r(c, r) (*((c)->nic+((r)/4)))
|
|
#define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v))
|
|
|
|
static uint
|
|
get32(uchar *p){
|
|
return *((u32int*)p);
|
|
}
|
|
static uint
|
|
get16(uchar *p)
|
|
{
|
|
return *((u16int*)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 char*
|
|
eepromread(Ctlr *ctlr, void *data, int count, uint off)
|
|
{
|
|
uchar *out = data;
|
|
char *err;
|
|
u32int w = 0;
|
|
int i;
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
|
|
for(; count > 0; count -= 2, off++){
|
|
csr32w(ctlr, Eeprom, off << 2);
|
|
csr32w(ctlr, Eeprom, csr32r(ctlr, Eeprom) & ~(1<<1));
|
|
|
|
for(i = 0; i < 10; i++){
|
|
w = csr32r(ctlr, Eeprom);
|
|
if(w & 1)
|
|
break;
|
|
delay(5);
|
|
}
|
|
if(i == 10)
|
|
break;
|
|
*out++ = w >> 16;
|
|
if(count > 1)
|
|
*out++ = w >> 24;
|
|
}
|
|
nicunlock(ctlr);
|
|
|
|
if(count > 0)
|
|
return "eeprompread: timeout";
|
|
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 nil;
|
|
delay(10);
|
|
}
|
|
return "clockwait: timeout";
|
|
}
|
|
|
|
static char*
|
|
poweron(Ctlr *ctlr)
|
|
{
|
|
char *err;
|
|
|
|
if(ctlr->power)
|
|
return nil;
|
|
|
|
csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | Init);
|
|
/* Disable L0s. */
|
|
csr32w(ctlr, GioChicken, csr32r(ctlr, GioChicken) | L1AnoL0Srx);
|
|
|
|
if((err = clockwait(ctlr)) != nil)
|
|
return err;
|
|
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
|
|
prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt);
|
|
delay(20);
|
|
|
|
/* Disable L1. */
|
|
prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));
|
|
|
|
nicunlock(ctlr);
|
|
|
|
ctlr->power = 1;
|
|
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
poweroff(Ctlr *ctlr)
|
|
{
|
|
int i, j;
|
|
|
|
csr32w(ctlr, Reset, Nevo);
|
|
|
|
/* Disable interrupts. */
|
|
csr32w(ctlr, Imr, 0);
|
|
csr32w(ctlr, Isr, ~0);
|
|
csr32w(ctlr, FhIsr, ~0);
|
|
|
|
if(niclock(ctlr) == nil){
|
|
/* Stop TX scheduler. */
|
|
prphwrite(ctlr, AlmSchedMode, 0);
|
|
prphwrite(ctlr, AlmSchedTxfact, 0);
|
|
|
|
/* Stop all DMA channels */
|
|
for(i = 0; i < 6; i++){
|
|
csr32w(ctlr, FhTxConfig + i*32, 0);
|
|
for(j = 0; j < 100; j++){
|
|
if((csr32r(ctlr, FhTxStatus) & (0x1010000<<i)) == (0x1010000<<i))
|
|
break;
|
|
delay(10);
|
|
}
|
|
}
|
|
nicunlock(ctlr);
|
|
}
|
|
|
|
/* Stop RX ring. */
|
|
if(niclock(ctlr) == nil){
|
|
csr32w(ctlr, FhRxConfig, 0);
|
|
for(j = 0; j < 100; j++){
|
|
if(csr32r(ctlr, FhRxStatus) & (1<<24))
|
|
break;
|
|
delay(10);
|
|
}
|
|
nicunlock(ctlr);
|
|
}
|
|
|
|
if(niclock(ctlr) == nil){
|
|
prphwrite(ctlr, ApmgClkDis, DmaClkRqt);
|
|
nicunlock(ctlr);
|
|
}
|
|
delay(5);
|
|
|
|
csr32w(ctlr, Reset, csr32r(ctlr, Reset) | StopMaster);
|
|
|
|
if((csr32r(ctlr, Gpc) & (7<<24)) != (4<<24)){
|
|
for(j = 0; j < 100; j++){
|
|
if(csr32r(ctlr, Reset) & MasterDisabled)
|
|
break;
|
|
delay(10);
|
|
}
|
|
}
|
|
|
|
csr32w(ctlr, Reset, csr32r(ctlr, Reset) | SW);
|
|
|
|
ctlr->power = 0;
|
|
}
|
|
|
|
static struct {
|
|
u32int addr; /* offset in EEPROM */
|
|
u8int nchan;
|
|
u8int chan[14];
|
|
} bands[5] = {
|
|
{ 0x63, 14,
|
|
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } },
|
|
{ 0x72, 13,
|
|
{ 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 } },
|
|
{ 0x80, 12,
|
|
{ 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 } },
|
|
{ 0x8d, 11,
|
|
{ 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 } },
|
|
{ 0x99, 6,
|
|
{ 145, 149, 153, 157, 161, 165 } }
|
|
};
|
|
|
|
static int
|
|
wpiinit(Ether *edev)
|
|
{
|
|
Ctlr *ctlr;
|
|
char *err;
|
|
uchar b[64];
|
|
int i, j;
|
|
Powergrp *g;
|
|
|
|
ctlr = edev->ctlr;
|
|
if((err = poweron(ctlr)) != nil)
|
|
goto Err;
|
|
if((csr32r(ctlr, EepromGp) & 0x6) == 0){
|
|
err = "bad rom signature";
|
|
goto Err;
|
|
}
|
|
/* Clear HW ownership of EEPROM. */
|
|
csr32w(ctlr, EepromGp, csr32r(ctlr, EepromGp) & ~0x180);
|
|
|
|
if((err = eepromread(ctlr, b, 1, 0x45)) != nil)
|
|
goto Err;
|
|
ctlr->eeprom.cap = b[0];
|
|
if((err = eepromread(ctlr, b, 2, 0x35)) != nil)
|
|
goto Err;
|
|
ctlr->eeprom.rev = get16(b);
|
|
if((err = eepromread(ctlr, b, 1, 0x4a)) != nil)
|
|
goto Err;
|
|
ctlr->eeprom.type = b[0];
|
|
if((err = eepromread(ctlr, b, 4, 0x60)) != nil)
|
|
goto Err;
|
|
strncpy(ctlr->eeprom.regdom, (char*)b, 4);
|
|
ctlr->eeprom.regdom[4] = '\0';
|
|
|
|
print("wpi: %X %X %X %s\n", ctlr->eeprom.cap, ctlr->eeprom.rev, ctlr->eeprom.type, ctlr->eeprom.regdom);
|
|
|
|
if((err = eepromread(ctlr, b, 6, 0x15)) != nil)
|
|
goto Err;
|
|
memmove(edev->ea, b, Eaddrlen);
|
|
|
|
memset(ctlr->maxpwr, 0, sizeof(ctlr->maxpwr));
|
|
for(i = 0; i < nelem(bands); i++){
|
|
if((err = eepromread(ctlr, b, 2*bands[i].nchan, bands[i].addr)) != nil)
|
|
goto Err;
|
|
for(j = 0; j < bands[i].nchan; j++){
|
|
if(!(b[j*2] & 1))
|
|
continue;
|
|
ctlr->maxpwr[bands[i].chan[j]] = b[j*2+1];
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < nelem(ctlr->eeprom.pwrgrps); i++){
|
|
if((err = eepromread(ctlr, b, 64, 0x100 + i*32)) != nil)
|
|
goto Err;
|
|
g = &ctlr->eeprom.pwrgrps[i];
|
|
g->maxpwr = b[60];
|
|
g->chan = b[61];
|
|
g->temp = get16(b+62);
|
|
for(j = 0; j < 5; j++){
|
|
g->samples[j].index = b[j*4];
|
|
g->samples[j].power = b[j*4+1];
|
|
}
|
|
}
|
|
|
|
poweroff(ctlr);
|
|
return 0;
|
|
Err:
|
|
print("wpiinit: %s\n", err);
|
|
poweroff(ctlr);
|
|
return -1;
|
|
}
|
|
|
|
static char*
|
|
crackfw(FWImage *i, uchar *data, uint size)
|
|
{
|
|
uchar *p, *e;
|
|
|
|
memset(i, 0, sizeof(*i));
|
|
if(size < 4*6){
|
|
Tooshort:
|
|
return "firmware image too short";
|
|
}
|
|
p = data;
|
|
e = p + size;
|
|
i->version = get32(p); p += 4;
|
|
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 nil;
|
|
}
|
|
|
|
static FWImage*
|
|
readfirmware(void)
|
|
{
|
|
uchar dirbuf[sizeof(Dir)+100], *data;
|
|
char *err;
|
|
FWImage *fw;
|
|
int n, r;
|
|
Chan *c;
|
|
Dir d;
|
|
|
|
if(!iseve())
|
|
error(Eperm);
|
|
if(!waserror()){
|
|
c = namec("/boot/wpi-3945abg", Aopen, OREAD, 0);
|
|
poperror();
|
|
}else
|
|
c = namec("/lib/firmware/wpi-3945abg", 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)) != 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+127);
|
|
if(b == nil)
|
|
return -1;
|
|
b->rp = b->wp = (uchar*)((((uintptr)b->base+127)&~127));
|
|
memset(b->rp, 0, Rdscsize);
|
|
coherence();
|
|
ctlr->rx.b[i] = b;
|
|
ctlr->rx.p[i] = PCIWADDR(b->rp);
|
|
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, 16 * 1024, 0, 0);
|
|
if(rx->b == nil || rx->p == nil)
|
|
return "no memory for rx ring";
|
|
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->shared == nil)
|
|
ctlr->shared = mallocalign(4096, 4096, 0, 0);
|
|
if(ctlr->shared == nil)
|
|
return "no memory for shared buffer";
|
|
memset(ctlr->shared, 0, 4096);
|
|
|
|
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, 16 * 1024, 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;
|
|
}
|
|
}
|
|
ctlr->shared->txbase[q] = PCIWADDR(tx->d);
|
|
tx->i = 0;
|
|
tx->n = 0;
|
|
tx->lastcmd = 0;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
reset(Ctlr *ctlr)
|
|
{
|
|
uchar rev;
|
|
char *err;
|
|
int i;
|
|
|
|
if(ctlr->power)
|
|
poweroff(ctlr);
|
|
if((err = initring(ctlr)) != nil)
|
|
return err;
|
|
if((err = poweron(ctlr)) != nil)
|
|
return err;
|
|
|
|
/* Select VMAIN power source. */
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
|
|
nicunlock(ctlr);
|
|
/* Spin until VMAIN gets selected. */
|
|
for(i = 0; i < 5000; i++){
|
|
if(csr32r(ctlr, GpioIn) & (1 << 9))
|
|
break;
|
|
delay(10);
|
|
}
|
|
|
|
/* Perform adapter initialization. */
|
|
rev = ctlr->pdev->rid;
|
|
if((rev & 0xc0) == 0x40)
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | AlmMb);
|
|
else if(!(rev & 0x80))
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | AlmMm);
|
|
|
|
if(ctlr->eeprom.cap == 0x80)
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | SkuMrc);
|
|
|
|
if((ctlr->eeprom.rev & 0xf0) == 0xd0)
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RevD);
|
|
else
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~RevD);
|
|
|
|
if(ctlr->eeprom.type > 1)
|
|
csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | TypeB);
|
|
|
|
/* Initialize RX ring. */
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
|
|
coherence();
|
|
csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p));
|
|
csr32w(ctlr, FhRxRptrAddr, PCIWADDR(&ctlr->shared->next));
|
|
csr32w(ctlr, FhRxWptr, 0);
|
|
csr32w(ctlr, FhRxConfig,
|
|
FhRxConfigDmaEna |
|
|
FhRxConfigRdrbdEna |
|
|
FhRxConfigWrstatusEna |
|
|
FhRxConfigMaxfrag |
|
|
(Nrxlog << FhRxConfigNrdbShift) |
|
|
FhRxConfigIrqDstHost |
|
|
(1 << FhRxConfigIrqRbthShift));
|
|
USED(csr32r(ctlr, FhRssrTbl));
|
|
csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7);
|
|
nicunlock(ctlr);
|
|
|
|
/* Initialize TX rings. */
|
|
if((err = niclock(ctlr)) != nil)
|
|
return err;
|
|
prphwrite(ctlr, AlmSchedMode, 2);
|
|
prphwrite(ctlr, AlmSchedArastat, 1);
|
|
prphwrite(ctlr, AlmSchedTxfact, 0x3f);
|
|
prphwrite(ctlr, AlmSchedBP1, 0x10000);
|
|
prphwrite(ctlr, AlmSchedBP2, 0x30002);
|
|
prphwrite(ctlr, AlmSchedTxf4mf, 4);
|
|
prphwrite(ctlr, AlmSchedTxf5mf, 5);
|
|
csr32w(ctlr, FhTxBase, PCIWADDR(ctlr->shared));
|
|
csr32w(ctlr, FhMsgConfig, 0xffff05a5);
|
|
for(i = 0; i < 6; i++){
|
|
csr32w(ctlr, FhCbbcCtrl+i*8, 0);
|
|
csr32w(ctlr, FhCbbcBase+i*8, 0);
|
|
csr32w(ctlr, FhTxConfig+i*32, 0x80200008);
|
|
}
|
|
nicunlock(ctlr);
|
|
USED(csr32r(ctlr, FhTxBase));
|
|
|
|
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);
|
|
|
|
csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
|
|
csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
|
|
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
postboot(Ctlr *);
|
|
|
|
static char*
|
|
boot(Ctlr *ctlr)
|
|
{
|
|
int i, n, size;
|
|
uchar *dma, *p;
|
|
FWImage *fw;
|
|
char *err;
|
|
|
|
fw = ctlr->fw;
|
|
/* 16 byte padding may not be necessary. */
|
|
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));
|
|
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));
|
|
prphwrite(ctlr, BsmDramTextSize, fw->init.text.size);
|
|
|
|
nicunlock(ctlr);
|
|
if((err = niclock(ctlr)) != nil){
|
|
free(dma);
|
|
return err;
|
|
}
|
|
|
|
/* Copy microcode image into NIC memory. */
|
|
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);
|
|
|
|
/* Start boot load now. */
|
|
prphwrite(ctlr, BsmWrCtrl, 1<<31);
|
|
|
|
/* Wait for transfer to complete. */
|
|
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";
|
|
}
|
|
|
|
/* Enable boot after power up. */
|
|
prphwrite(ctlr, BsmWrCtrl, 1<<30);
|
|
nicunlock(ctlr);
|
|
|
|
/* Now press "execute". */
|
|
csr32w(ctlr, Reset, 0);
|
|
|
|
/* Wait at most one second for first alive notification. */
|
|
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));
|
|
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));
|
|
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;
|
|
int pad;
|
|
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;
|
|
|
|
memset(d, 0, Tdscsize);
|
|
|
|
pad = size - 4;
|
|
if(block != nil)
|
|
pad += BLEN(block);
|
|
pad = ((pad + 3) & ~3) - pad;
|
|
|
|
put32(d, (pad << 28) | ((1 + (block != nil)) << 24)), d += 4;
|
|
put32(d, PCIWADDR(c)), d += 4;
|
|
put32(d, size), d += 4;
|
|
|
|
if(block != nil){
|
|
size = BLEN(block);
|
|
put32(d, PCIWADDR(block->rp)), d += 4;
|
|
put32(d, size), d += 4;
|
|
}
|
|
|
|
USED(d);
|
|
|
|
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(islo() && !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((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];
|
|
|
|
memset(c, 0, sizeof(c));
|
|
put32(c, 10000);
|
|
c[4] = which;
|
|
c[5] = on;
|
|
c[6] = off;
|
|
cmd(ctlr, 72, c, sizeof(c));
|
|
}
|
|
|
|
static char*
|
|
btcoex(Ctlr *ctlr)
|
|
{
|
|
uchar c[Tcmdsize], *p;
|
|
|
|
/* configure bluetooth coexistance. */
|
|
p = c;
|
|
*p++ = 3; /* flags WPI_BT_COEX_MODE_4WIRE */
|
|
*p++ = 30; /* lead time */
|
|
*p++ = 5; /* max kill */
|
|
*p++ = 0; /* reserved */
|
|
put32(p, 0), p += 4; /* kill_ack */
|
|
put32(p, 0), p += 4; /* kill_cts */
|
|
return cmd(ctlr, 155, c, p-c);
|
|
}
|
|
|
|
static char*
|
|
powermode(Ctlr *ctlr)
|
|
{
|
|
uchar c[Tcmdsize];
|
|
int capoff, reg;
|
|
|
|
memset(c, 0, sizeof(c));
|
|
capoff = pcicap(ctlr->pdev, PciCapPCIe);
|
|
if(capoff >= 0){
|
|
reg = pcicfgr8(ctlr->pdev, capoff+1);
|
|
if((reg & 1) == 0) /* PCI_PCIE_LCR_ASPM_L0S */
|
|
c[0] |= 1<<3; /* WPI_PS_PCI_PMGT */
|
|
}
|
|
return cmd(ctlr, 119, c, 4*(3+5));
|
|
}
|
|
|
|
static char*
|
|
postboot(Ctlr *ctlr)
|
|
{
|
|
while((ctlr->temp = (int)csr32r(ctlr, UcodeGp2)) == 0)
|
|
delay(10);
|
|
|
|
if(0){
|
|
char *err;
|
|
|
|
if((err = btcoex(ctlr)) != nil)
|
|
print("btcoex: %s\n", err);
|
|
if((err = powermode(ctlr)) != nil)
|
|
print("powermode: %s\n", err);
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static uchar wpirates[] = {
|
|
0x80 | 12,
|
|
0x80 | 18,
|
|
0x80 | 24,
|
|
0x80 | 36,
|
|
0x80 | 48,
|
|
0x80 | 72,
|
|
0x80 | 96,
|
|
0x80 | 108,
|
|
|
|
0x80 | 2,
|
|
0x80 | 4,
|
|
0x80 | 11,
|
|
0x80 | 22,
|
|
|
|
0
|
|
};
|
|
|
|
static struct {
|
|
uchar rate;
|
|
uchar plcp;
|
|
} ratetab[] = {
|
|
{ 12, 0xd },
|
|
{ 18, 0xf },
|
|
{ 24, 0x5 },
|
|
{ 36, 0x7 },
|
|
{ 48, 0x9 },
|
|
{ 72, 0xb },
|
|
{ 96, 0x1 },
|
|
{ 108, 0x3 },
|
|
|
|
{ 2, 10 },
|
|
{ 4, 20 },
|
|
{ 11, 55 },
|
|
{ 22, 110 },
|
|
};
|
|
|
|
static u8int rfgain_2ghz[] = {
|
|
0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xbb, 0xbb, 0xbb,
|
|
0xbb, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xd3, 0xd3, 0xb3, 0xb3, 0xb3,
|
|
0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x73, 0xeb, 0xeb, 0xeb,
|
|
0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xab, 0xab, 0xab, 0x8b,
|
|
0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xc3, 0xc3, 0xc3, 0xc3, 0xa3,
|
|
0xa3, 0xa3, 0xa3, 0x83, 0x83, 0x83, 0x83, 0x63, 0x63, 0x63, 0x63,
|
|
0x43, 0x43, 0x43, 0x43, 0x23, 0x23, 0x23, 0x23, 0x03, 0x03, 0x03,
|
|
0x03
|
|
};
|
|
|
|
static u8int dspgain_2ghz[] = {
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7d, 0x6e, 0x69, 0x62, 0x7d, 0x73, 0x6c,
|
|
0x63, 0x77, 0x6f, 0x69, 0x61, 0x5c, 0x6a, 0x64, 0x78, 0x71, 0x6b,
|
|
0x7d, 0x77, 0x70, 0x6a, 0x65, 0x61, 0x5b, 0x6b, 0x79, 0x73, 0x6d,
|
|
0x7f, 0x79, 0x73, 0x6c, 0x66, 0x60, 0x5c, 0x6e, 0x68, 0x62, 0x74,
|
|
0x7d, 0x77, 0x71, 0x6b, 0x65, 0x60, 0x71, 0x6a, 0x66, 0x5f, 0x71,
|
|
0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f,
|
|
0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66, 0x5f, 0x71, 0x6a, 0x66,
|
|
0x5f
|
|
};
|
|
|
|
static int
|
|
pwridx(Ctlr *ctlr, Powergrp *pwgr, int chan, int rate)
|
|
{
|
|
/* Fixed-point arithmetic division using a n-bit fractional part. */
|
|
#define fdivround(a, b, n) \
|
|
((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n))
|
|
|
|
/* Linear interpolation. */
|
|
#define interpolate(x, x1, y1, x2, y2, n) \
|
|
((y1) + fdivround(((x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n))
|
|
|
|
int pwr;
|
|
Sample *sample;
|
|
int idx;
|
|
|
|
/* Default TX power is group maximum TX power minus 3dB. */
|
|
pwr = pwgr->maxpwr / 2;
|
|
|
|
/* Decrease TX power for highest OFDM rates to reduce distortion. */
|
|
switch(rate){
|
|
case 5: /* WPI_RIDX_OFDM36 */
|
|
pwr -= 0;
|
|
break;
|
|
case 6: /* WPI_RIDX_OFDM48 */
|
|
pwr -=7;
|
|
break;
|
|
case 7: /* WPI_RIDX_OFDM54 */
|
|
pwr -= 9;
|
|
break;
|
|
}
|
|
|
|
/* Never exceed the channel maximum allowed TX power. */
|
|
pwr = MIN(pwr, ctlr->maxpwr[chan]);
|
|
|
|
/* Retrieve TX power index into gain tables from samples. */
|
|
for(sample = pwgr->samples; sample < &pwgr->samples[3]; sample++)
|
|
if(pwr > sample[1].power)
|
|
break;
|
|
/* Fixed-point linear interpolation using a 19-bit fractional part. */
|
|
idx = interpolate(pwr, sample[0].power, sample[0].index,
|
|
sample[1].power, sample[1].index, 19);
|
|
|
|
/*-
|
|
* Adjust power index based on current temperature:
|
|
* - if cooler than factory-calibrated: decrease output power
|
|
* - if warmer than factory-calibrated: increase output power
|
|
*/
|
|
idx -= (ctlr->temp - pwgr->temp) * 11 / 100;
|
|
|
|
/* Decrease TX power for CCK rates (-5dB). */
|
|
if (rate >= 8)
|
|
idx += 10;
|
|
|
|
/* Make sure idx stays in a valid range. */
|
|
if (idx < 0)
|
|
idx = 0;
|
|
else if (idx >= nelem(rfgain_2ghz))
|
|
idx = nelem(rfgain_2ghz)-1;
|
|
return idx;
|
|
#undef fdivround
|
|
#undef interpolate
|
|
}
|
|
|
|
static void
|
|
addnode(Ctlr *ctlr, uchar id, uchar *addr, int plcp, int antenna)
|
|
{
|
|
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 += 2; /* reserved */
|
|
p += 16; /* key */
|
|
put32(p, 4); /* action (4 = set_rate) */
|
|
p += 4;
|
|
p += 4; /* mask */
|
|
p += 2; /* tid */
|
|
*p++ = plcp; /* plcp */
|
|
*p++ = antenna; /* antenna */
|
|
p++; /* add_imm */
|
|
p++; /* del_imm */
|
|
p++; /* add_imm_start */
|
|
cmd(ctlr, 24, c, p - c);
|
|
}
|
|
|
|
static void
|
|
rxon(Ether *edev, Wnode *bss)
|
|
{
|
|
uchar c[Tcmdsize], *p;
|
|
int filter, flags, rate;
|
|
Ctlr *ctlr;
|
|
char *err;
|
|
int idx;
|
|
|
|
ctlr = edev->ctlr;
|
|
filter = FilterNoDecrypt | FilterMulticast;
|
|
if(ctlr->prom){
|
|
filter |= FilterPromisc;
|
|
if(bss != nil)
|
|
ctlr->channel = bss->channel;
|
|
bss = nil;
|
|
}
|
|
flags = RFlagTSF | 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;
|
|
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 */
|
|
|
|
memset(p = c, 0, sizeof(c));
|
|
memmove(p, edev->ea, 6); p += 8; /* myaddr */
|
|
memmove(p, ctlr->bssid, 6); p += 16; /* bssid */
|
|
*p++ = 3; /* mode (STA) */
|
|
p += 3;
|
|
*p++ = 0xff; /* ofdm mask (not yet negotiated) */
|
|
*p++ = 0x0f; /* cck mask (not yet negotiated) */
|
|
put16(p, ctlr->aid & 0x3fff); /* associd */
|
|
p += 2;
|
|
put32(p, flags);
|
|
p += 4;
|
|
put32(p, filter);
|
|
p += 4;
|
|
*p++ = ctlr->channel;
|
|
p += 3;
|
|
|
|
if((err = cmd(ctlr, 16, c, p - c)) != nil){
|
|
print("rxon: %s\n", err);
|
|
return;
|
|
}
|
|
|
|
if(ctlr->maxpwr[ctlr->channel] != 0){
|
|
/* tx power */
|
|
memset(p = c, 0, sizeof(c));
|
|
*p++ = 1; /* band (0 = 5ghz) */
|
|
p++; /* reserved */
|
|
put16(p, ctlr->channel), p += 2;
|
|
for(rate = 0; rate < nelem(ratetab); rate++){
|
|
idx = pwridx(ctlr, &ctlr->eeprom.pwrgrps[0], ctlr->channel, rate);
|
|
*p++ = ratetab[rate].plcp;
|
|
*p++ = rfgain_2ghz[idx]; /* rf_gain */
|
|
*p++ = dspgain_2ghz[idx]; /* dsp_gain */
|
|
p++; /* reservd */
|
|
}
|
|
cmd(ctlr, 151, c, p - c);
|
|
}
|
|
|
|
if(ctlr->bcastnodeid == -1){
|
|
ctlr->bcastnodeid = 24;
|
|
addnode(ctlr, ctlr->bcastnodeid, edev->bcast, ratetab[0].plcp, 3<<6);
|
|
}
|
|
if(ctlr->bssnodeid == -1 && bss != nil && ctlr->aid != 0){
|
|
ctlr->bssnodeid = 0;
|
|
addnode(ctlr, ctlr->bssnodeid, bss->bssid, ratetab[0].plcp, 3<<6);
|
|
}
|
|
}
|
|
|
|
enum {
|
|
TFlagNeedRTS = 1<<1,
|
|
TFlagNeedCTS = 1<<2,
|
|
TFlagNeedACK = 1<<3,
|
|
TFlagFullTxOp = 1<<7,
|
|
TFlagBtDis = 1<<12,
|
|
TFlagAutoSeq = 1<<13,
|
|
TFlagInsertTs = 1<<16,
|
|
};
|
|
|
|
static void
|
|
transmit(Wifi *wifi, Wnode *wn, Block *b)
|
|
{
|
|
uchar c[Tcmdsize], *p;
|
|
Ether *edev;
|
|
Ctlr *ctlr;
|
|
Wifipkt *w;
|
|
int flags, nodeid, rate, timeout;
|
|
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;
|
|
timeout = 3;
|
|
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|TFlagFullTxOp;
|
|
|
|
if((w->fc[0] & 0x0c) == 0x08 && ctlr->bssnodeid != -1){
|
|
timeout = 0;
|
|
nodeid = ctlr->bssnodeid;
|
|
p = wn->actrate;
|
|
}
|
|
}
|
|
if(p >= wifi->rates)
|
|
rate = p - wifi->rates;
|
|
else
|
|
rate = 0;
|
|
qunlock(ctlr);
|
|
|
|
memset(p = c, 0, sizeof(c));
|
|
put16(p, BLEN(b)), p += 2;
|
|
put16(p, 0), p += 2; /* lnext */
|
|
put32(p, flags), p += 4;
|
|
*p++ = ratetab[rate].plcp;
|
|
*p++ = nodeid;
|
|
*p++ = 0; /* tid */
|
|
*p++ = 0; /* security */
|
|
p += 16+8; /* key/iv */
|
|
put32(p, 0), p += 4; /* fnext */
|
|
put32(p, 0xffffffff), p += 4; /* livetime infinite */
|
|
*p++ = 0xff;
|
|
*p++ = 0x0f;
|
|
*p++ = 7;
|
|
*p++ = 15;
|
|
put16(p, timeout), p += 2;
|
|
put16(p, 0), p += 2; /* txop */
|
|
|
|
if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){
|
|
print("transmit: %s\n", err);
|
|
freeb(b);
|
|
}
|
|
}
|
|
|
|
static long
|
|
wpictl(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
|
|
wpiifstat(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
|
|
wpipromiscuous(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
|
|
wpimulticast(void *, uchar*, int)
|
|
{
|
|
}
|
|
|
|
static void
|
|
wpirecover(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
|
|
wpiattach(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);
|
|
ctlr->wifi->rates = wpirates;
|
|
}
|
|
|
|
if(ctlr->fw == nil){
|
|
fw = readfirmware();
|
|
print("#l%d: firmware: %ux, size: %ux+%ux+%ux+%ux+%ux\n",
|
|
edev->ctlrno, fw->version,
|
|
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("wpirecover", wpirecover, edev);
|
|
}
|
|
qunlock(ctlr);
|
|
poperror();
|
|
}
|
|
|
|
static void
|
|
receive(Ctlr *ctlr)
|
|
{
|
|
Block *b, *bb;
|
|
uchar *d;
|
|
RXQ *rx;
|
|
TXQ *tx;
|
|
u32int hw;
|
|
|
|
rx = &ctlr->rx;
|
|
if(ctlr->broken || ctlr->shared == nil || rx->b == nil)
|
|
return;
|
|
|
|
bb = nil;
|
|
for(hw = ctlr->shared->next % 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++;
|
|
idx = *d++;
|
|
qid = *d++;
|
|
|
|
USED(len);
|
|
USED(flags);
|
|
|
|
if(0) iprint("rxdesc[%d] type=%d len=%d idx=%d qid=%d\n", rx->i, type, len, idx, qid);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
switch(type){
|
|
case 1: /* uc ready */
|
|
break;
|
|
|
|
case 24: /* add node done */
|
|
break;
|
|
|
|
case 27: /* rx done */
|
|
if(d + 1 > b->lim)
|
|
break;
|
|
d += d[0];
|
|
d += 8;
|
|
if(d + 6 + 2 > b->lim){
|
|
break;
|
|
}
|
|
len = get16(d+6);
|
|
d += 8;
|
|
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 28: /* tx done */
|
|
if(len <= 8 || d[8] == 1)
|
|
break;
|
|
wifitxfail(ctlr->wifi, bb);
|
|
break;
|
|
|
|
case 130: /* start scan */
|
|
break;
|
|
|
|
case 132: /* stop scan */
|
|
break;
|
|
|
|
case 161: /* state change */
|
|
break;
|
|
}
|
|
}
|
|
csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7);
|
|
if(bb != nil)
|
|
freeb(bb);
|
|
}
|
|
|
|
static void
|
|
wpiinterrupt(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)) || (fhisr & Ifhrx))
|
|
receive(ctlr);
|
|
if(isr & Ierr){
|
|
ctlr->broken = 1;
|
|
iprint("#l%d: fatal firmware error, lastcmd %ud\n", edev->ctlrno, ctlr->tx[4].lastcmd);
|
|
}
|
|
ctlr->wait.m |= isr;
|
|
if(ctlr->wait.m & ctlr->wait.w)
|
|
wakeup(&ctlr->wait);
|
|
done:
|
|
csr32w(ctlr, Imr, ctlr->ie);
|
|
iunlock(ctlr);
|
|
}
|
|
|
|
static void
|
|
wpishutdown(Ether *edev)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = edev->ctlr;
|
|
if(ctlr->power)
|
|
poweroff(ctlr);
|
|
ctlr->broken = 0;
|
|
}
|
|
|
|
static Ctlr *wpihead, *wpitail;
|
|
|
|
static void
|
|
wpipci(void)
|
|
{
|
|
Pcidev *pdev;
|
|
|
|
pdev = nil;
|
|
while(pdev = pcimatch(pdev, 0x8086, 0)){
|
|
Ctlr *ctlr;
|
|
void *mem;
|
|
switch(pdev->did){
|
|
default:
|
|
continue;
|
|
case 0x4227:
|
|
break;
|
|
}
|
|
|
|
/* Clear device-specific "PCI retry timeout" register (41h). */
|
|
if(pcicfgr8(pdev, 0x41) != 0)
|
|
pcicfgw8(pdev, 0x41, 0);
|
|
|
|
ctlr = malloc(sizeof(Ctlr));
|
|
if(ctlr == nil) {
|
|
print("wpi: 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("wpi: can't map %8.8luX\n", pdev->mem[0].bar);
|
|
free(ctlr);
|
|
continue;
|
|
}
|
|
ctlr->nic = mem;
|
|
ctlr->pdev = pdev;
|
|
|
|
if(wpihead != nil)
|
|
wpitail->link = ctlr;
|
|
else
|
|
wpihead = ctlr;
|
|
wpitail = ctlr;
|
|
}
|
|
}
|
|
|
|
static int
|
|
wpipnp(Ether *edev)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
if(wpihead == nil)
|
|
wpipci();
|
|
|
|
again:
|
|
for(ctlr = wpihead; 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 = wpiattach;
|
|
edev->ifstat = wpiifstat;
|
|
edev->ctl = wpictl;
|
|
edev->shutdown = wpishutdown;
|
|
edev->promiscuous = wpipromiscuous;
|
|
edev->multicast = wpimulticast;
|
|
edev->mbps = 54;
|
|
|
|
pcienable(ctlr->pdev);
|
|
if(wpiinit(edev) < 0){
|
|
pcidisable(ctlr->pdev);
|
|
edev->ctlr = nil;
|
|
goto again;
|
|
}
|
|
pcisetbme(ctlr->pdev);
|
|
intrenable(edev->irq, wpiinterrupt, edev, edev->tbdf, edev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
etherwpilink(void)
|
|
{
|
|
addethercard("wpi", wpipnp);
|
|
}
|