git: 9front

ref: 411fc48b8ca8ebd2ea32d3a26b1be309c6b8ade3
dir: /sys/src/9/pc/etheriwl.c/

View raw version
/*
 * Intel WiFi Link driver.
 *
 * Written without any documentation but Damien Bergaminis
 * OpenBSD iwn(4) driver sources. Requires intel firmware
 * to be present in /lib/firmware/iwn-* on attach.
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "../port/netif.h"

#include "etherif.h"
#include "wifi.h"

enum {

	Ntxlog		= 8,
	Ntx		= 1<<Ntxlog,
	Nrxlog		= 8,
	Nrx		= 1<<Nrxlog,

	Rstatsize	= 16,
	Rbufsize	= 4*1024,
	Rdscsize	= 8,

	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,

	Led		= 0x094,
		LedBsmCtrl	= 1<<5,
		LedOn		= 0x38,
		LedOff		= 0x78,

	UcodeGp1Clr	= 0x05c,
		UcodeGp1RfKill		= 1<<1,
		UcodeGp1CmdBlocked	= 1<<2,
		UcodeGp1CtempStopRf	= 1<<3,

	ShadowRegCtrl	= 0x0a8,

	Giochicken	= 0x100,
		L1AnoL0Srx	= 1<<23,
		DisL0Stimer	= 1<<29,

	AnaPll		= 0x20c,

	Dbghpetmem	= 0x240,

	MemRaddr	= 0x40c,
	MemWaddr	= 0x410,
	MemWdata	= 0x418,
	MemRdata	= 0x41c,

	PrphWaddr	= 0x444,
	PrphRaddr	= 0x448,
	PrphWdata	= 0x44c,
	PrphRdata	= 0x450,

	HbusTargWptr	= 0x460,
};

/*
 * Flow-Handler registers.
 */
enum {
	FhTfbdCtrl0	= 0x1900,	// +q*8
	FhTfbdCtrl1	= 0x1904,	// +q*8

	FhKwAddr	= 0x197c,

	FhSramAddr	= 0x19a4,	// +q*4
	FhCbbcQueue	= 0x19d0,	// +q*4
	FhStatusWptr	= 0x1bc0,
	FhRxBase	= 0x1bc4,
	FhRxWptr	= 0x1bc8,
	FhRxConfig	= 0x1c00,
		FhRxConfigEna		= 1<<31,
		FhRxConfigRbSize8K	= 1<<16,
		FhRxConfigSingleFrame	= 1<<15,
		FhRxConfigIrqDstHost	= 1<<12,
		FhRxConfigIgnRxfEmpty	= 1<<2,

		FhRxConfigNrbdShift	= 20,
		FhRxConfigRbTimeoutShift= 4,

	FhRxStatus	= 0x1c44,

	FhTxConfig	= 0x1d00,	// +q*32
		FhTxConfigDmaCreditEna	= 1<<3,
		FhTxConfigDmaEna	= 1<<31,
		FhTxConfigCirqHostEndTfd= 1<<20,

	FhTxBufStatus	= 0x1d08,	// +q*32
		FhTxBufStatusTbNumShift	= 20,
		FhTxBufStatusTbIdxShift = 12,
		FhTxBufStatusTfbdValid	= 3,

	FhTxChicken	= 0x1e98,
	FhTxStatus	= 0x1eb0,
};

/*
 * NIC internal memory offsets.
 */
enum {
	ApmgClkCtrl	= 0x3000,
	ApmgClkEna	= 0x3004,
	ApmgClkDis	= 0x3008,
		DmaClkRqt	= 1<<9,
		BsmClkRqt	= 1<<11,

	ApmgPs		= 0x300c,
		EarlyPwroffDis	= 1<<22,
		PwrSrcVMain	= 0<<24,
		PwrSrcVAux	= 2<<24,
		PwrSrcMask	= 3<<24,
		ResetReq	= 1<<26,

	ApmgDigitalSvr	= 0x3058,
	ApmgAnalogSvr	= 0x306c,
	ApmgPciStt	= 0x3010,
	BsmWrCtrl	= 0x3400,
	BsmWrMemSrc	= 0x3404,
	BsmWrMemDst	= 0x3408,
	BsmWrDwCount	= 0x340c,
	BsmDramTextAddr	= 0x3490,
	BsmDramTextSize	= 0x3494,
	BsmDramDataAddr	= 0x3498,
	BsmDramDataSize	= 0x349c,
	BsmSramBase	= 0x3800,
};

/*
 * TX scheduler registers.
 */
enum {
	SchedBase		= 0xa02c00,
	SchedSramAddr		= SchedBase,
	SchedDramAddr5000	= SchedBase+0x008,
	SchedDramAddr4965	= SchedBase+0x010,
	SchedTxFact5000		= SchedBase+0x010,
	SchedTxFact4965		= SchedBase+0x01c,
	SchedQueueRdptr4965	= SchedBase+0x064,	// +q*4
	SchedQueueRdptr5000	= SchedBase+0x068,	// +q*4
	SchedQChainSel4965	= SchedBase+0x0d0,
	SchedIntrMask4965	= SchedBase+0x0e4,
	SchedQChainSel5000	= SchedBase+0x0e8,
	SchedQueueStatus4965	= SchedBase+0x104,	// +q*4
	SchedIntrMask5000	= SchedBase+0x108,
	SchedQueueStatus5000	= SchedBase+0x10c,	// +q*4
	SchedAggrSel5000	= SchedBase+0x248,
};

enum {
	SchedCtxOff4965		= 0x380,
	SchedCtxLen4965		= 416,
	SchedTransTblOff4965	= 0x500,

	SchedCtxOff5000		= 0x600,
	SchedCtxLen5000		= 512,
	SchedTransTblOff5000	= 0x7e0,
};

/* controller types */
enum {
	Type4965	= 0,
	Type5300	= 2,
	Type5350	= 3,
	Type5150	= 4,
	Type5100	= 5,
	Type1000	= 6,
	Type6000	= 7,
	Type6050	= 8,
	Type6005	= 11,
};

typedef struct FWInfo FWInfo;
typedef struct FWImage FWImage;
typedef struct FWSect FWSect;

typedef struct TXQ TXQ;
typedef struct RXQ RXQ;

typedef struct Ctlr Ctlr;

struct FWSect
{
	uchar	*data;
	uint	size;
};

struct FWImage
{
	struct {
		FWSect	text;
		FWSect	data;
	} init, main, boot;

	uint	rev;
	uint	build;
	char	descr[64+1];
	uchar	data[];
};

struct FWInfo
{
	uchar	major;
	uchar	minjor;
	uchar	type;
	uchar	subtype;

	u32int	logptr;
	u32int	errptr;
	u32int	tstamp;
	u32int	valid;
};

struct TXQ
{
	uint	n;
	uint	i;
	Block	**b;
	uchar	*d;
	uchar	*c;

	Rendez;
	QLock;
};

struct RXQ
{
	uint	i;
	Block	**b;
	u32int	*p;
	uchar	*s;
};

struct Ctlr {
	Lock;
	QLock;

	Ctlr *link;
	Pcidev *pdev;
	Wifi *wifi;

	int type;
	int port;
	int active;
	int attached;

	u32int ie;

	u32int *nic;
	uchar *kwpage;

	int channel;

	RXQ rx;
	TXQ tx[20];

	struct {
		Rendez;
		u32int	m;
		u32int	w;
		u32int	r;
	} wait;

	struct {
		uchar	type;
		uchar	step;
		uchar	dash;
		uchar	txantmask;
		uchar	rxantmask;
	} rfcfg;

	struct {
		u32int	crystal;
	} eeprom;

	struct {
		u32int	base;
		uchar	*s;
	} sched;

	FWInfo fwinfo;
	FWImage *fw;
};

#define csr32r(c, r)	(*((c)->nic+((r)/4)))
#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))

static uint
get16(uchar *p){
	return *((u16int*)p);
}
static uint
get32(uchar *p){
	return *((u32int*)p);
}
static void
put32(uchar *p, uint v){
	*((u32int*)p) = v;
}
static void
put16(uchar *p, uint v){
	*((u16int*)p) = v;
};

static char*
niclock(Ctlr *ctlr)
{
	int i;

	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | MacAccessReq);
	for(i=0; i<1000; i++){
		if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna)
			return 0;
		delay(10);
	}
	return "niclock: timeout";
}

static void
nicunlock(Ctlr *ctlr)
{
	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq);
}

static u32int
prphread(Ctlr *ctlr, uint off)
{
	csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off);
	coherence();
	return csr32r(ctlr, PrphRdata);
}
static void
prphwrite(Ctlr *ctlr, uint off, u32int data)
{
	csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off);
	coherence();
	csr32w(ctlr, PrphWdata, data);
}

static u32int
memread(Ctlr *ctlr, uint off)
{
	csr32w(ctlr, MemRaddr, off);
	coherence();
	return csr32r(ctlr, MemRdata);
}
static void
memwrite(Ctlr *ctlr, uint off, u32int data)
{
	csr32w(ctlr, MemWaddr, off);
	coherence();
	csr32w(ctlr, MemWdata, data);
}

static void
setfwinfo(Ctlr *ctlr, uchar *d, int len)
{
	FWInfo *i;

	if(len < 32)
		return;
	i = &ctlr->fwinfo;
	i->minjor = *d++;
	i->major = *d++;
	d += 2+8;
	i->type = *d++;
	i->subtype = *d++;
	d += 2;
	i->logptr = get32(d); d += 4;
	i->errptr = get32(d); d += 4;
	i->tstamp = get32(d); d += 4;
	i->valid = get32(d);
};

static void
dumpctlr(Ctlr *ctlr)
{
	u32int dump[13];
	int i;

	if(ctlr->fwinfo.errptr == 0){
		print("no error pointer\n");
		return;
	}
	for(i=0; i<nelem(dump); i++)
		dump[i] = memread(ctlr, ctlr->fwinfo.errptr + i*4);
	print(	"error:\tid %ux, pc %ux,\n"
		"\tbranchlink %.8ux %.8ux, interruptlink %.8ux %.8ux,\n"
		"\terrordata %.8ux %.8ux, srcline %ud, tsf %ux, time %ux\n",
		dump[1], dump[2],
		dump[4], dump[3], dump[6], dump[5],
		dump[7], dump[8], dump[9], dump[10], dump[11]);
}

static char*
eepromlock(Ctlr *ctlr)
{
	int i, j;

	for(i=0; i<100; i++){
		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | EepromLocked);
		for(j=0; j<100; j++){
			if(csr32r(ctlr, Cfg) & EepromLocked)
				return 0;
			delay(10);
		}
	}
	return "eepromlock: timeout";
}
static void
eepromunlock(Ctlr *ctlr)
{
	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked);
}
static char*
eepromread(Ctlr *ctlr, void *data, int count, uint off)
{
	uchar *out = data;
	u32int w;
	int i;

	w = 0;
	for(; count > 0; count -= 2, off++){
		csr32w(ctlr, EepromIo, off << 2);
		for(i=0; i<10; i++){
			w = csr32r(ctlr, EepromIo);
			if(w & 1)
				break;
			delay(5);
		}
		if(i == 10)
			return "eepromread: timeout";
		*out++ = w >> 16;
		if(count > 1)
			*out++ = w >> 24;
	}
	return 0;
}

static char*
handover(Ctlr *ctlr)
{
	int i;

	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
	for(i=0; i<5; i++){
		if(csr32r(ctlr, Cfg) & NicReady)
			return 0;
		delay(10);
	}
	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare);
	for(i=0; i<15000; i++){
		if((csr32r(ctlr, Cfg) & PrepareDone) == 0)
			break;
		delay(10);
	}
	if(i >= 15000)
		return "handover: timeout";
	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
	for(i=0; i<5; i++){
		if(csr32r(ctlr, Cfg) & NicReady)
			return 0;
		delay(10);
	}
	return "handover: timeout";
}

static char*
clockwait(Ctlr *ctlr)
{
	int i;

	/* Set "initialization complete" bit. */
	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | InitDone);
	for(i=0; i<2500; i++){
		if(csr32r(ctlr, Gpc) & MacClockReady)
			return 0;
		delay(10);
	}
	return "clockwait: timeout";
}

static char*
poweron(Ctlr *ctlr)
{
	int capoff;
	char *err;

	/* Disable L0s exit timer (NMI bug workaround). */
	csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | DisL0Stimer);

	/* Don't wait for ICH L0s (ICH bug workaround). */
	csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | L1AnoL0Srx);

	/* Set FH wait threshold to max (HW bug under stress workaround). */
	csr32w(ctlr, Dbghpetmem, csr32r(ctlr, Dbghpetmem) | 0xffff0000);

	/* Enable HAP INTA to move adapter from L1a to L0s. */
	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | HapwakeL1A);

	capoff = pcicap(ctlr->pdev, PciCapPCIe);
	if(capoff != -1){
		/* Workaround for HW instability in PCIe L0->L0s->L1 transition. */
		if(pcicfgr16(ctlr->pdev, capoff + 0x10) & 0x2)	/* LCSR -> L1 Entry enabled. */
			csr32w(ctlr, Gio, csr32r(ctlr, Gio) | EnaL0S);
		else
			csr32w(ctlr, Gio, csr32r(ctlr, Gio) & ~EnaL0S);
	}

	if(ctlr->type != Type4965 && ctlr->type <= Type1000)
		csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | 0x00880300);

	/* Wait for clock stabilization before accessing prph. */
	if((err = clockwait(ctlr)) != nil)
		return err;

	if((err = niclock(ctlr)) != nil)
		return err;

	/* Enable DMA and BSM (Bootstrap State Machine). */
	if(ctlr->type == Type4965)
		prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt);
	else
		prphwrite(ctlr, ApmgClkEna, DmaClkRqt);
	delay(20);

	/* Disable L1-Active. */
	prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));

	nicunlock(ctlr);
	return 0;
}

static int
iwlinit(Ether *edev)
{
	Ctlr *ctlr;
	char *err;
	uchar b[2];
	uint u;

	ctlr = edev->ctlr;
	if((err = handover(ctlr)) != nil)
		goto Err;
	if((err = poweron(ctlr)) != nil)
		goto Err;
	if((csr32r(ctlr, EepromGp) & 0x7) == 0){
		err = "bad rom signature";
		goto Err;
	}
	if((err = eepromlock(ctlr)) != nil)
		goto Err;
	if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){
		eepromunlock(ctlr);
		goto Err;
	}
	if((err = eepromread(ctlr, b, 2, 0x048)) != nil){
		eepromunlock(ctlr);
		goto Err;
	}
	u = get16(b);
	ctlr->rfcfg.type = u & 3;	u >>= 2;
	ctlr->rfcfg.step = u & 3;	u >>= 2;
	ctlr->rfcfg.dash = u & 3;	u >>= 4;
	ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
	ctlr->rfcfg.rxantmask = u & 15;
	if((err = eepromread(ctlr, b, 4, 0x128)) != nil){
		eepromunlock(ctlr);
		goto Err;
	}
	ctlr->eeprom.crystal = get32(b);
	eepromunlock(ctlr);

	ctlr->ie = 0;
	csr32w(ctlr, Isr, ~0);	/* clear pending interrupts */
	csr32w(ctlr, Imr, 0);	/* no interrupts for now */

	return 0;
Err:
	print("iwlinit: %s\n", err);
	return -1;
}

static char*
crackfw(FWImage *i, uchar *data, uint size, int alt)
{
	uchar *p, *e;
	FWSect *s;

	memset(i, 0, sizeof(*i));
	if(size < 4){
Tooshort:
		return "firmware image too short";
	}
	p = data;
	e = p + size;
	i->rev = get32(p); p += 4;
	if(i->rev == 0){
		uvlong altmask;

		if(size < (4+64+4+4+8))
			goto Tooshort;
		if(memcmp(p, "IWL\n", 4) != 0)
			return "bad firmware signature";
		p += 4;
		strncpy(i->descr, (char*)p, 64);
		i->descr[sizeof(i->descr)-1] = 0;
		p += 64;
		i->rev = get32(p); p += 4;
		i->build = get32(p); p += 4;
		altmask = get32(p); p += 4;
		altmask |= (uvlong)get32(p) << 32; p += 4;
		while(alt > 0 && (altmask & (1ULL<<alt)) == 0)
			alt--;
		while(p < e){
			FWSect dummy;

			if((p + 2+2+4) > e)
				goto Tooshort;
			switch(get16(p)){
			case 1:	s = &i->main.text; break;
			case 2: s = &i->main.data; break;
			case 3: s = &i->init.text; break;
			case 4: s = &i->init.data; break;
			case 5: s = &i->boot.text; break;
			default:s = &dummy;
			}
			p += 2;
			if(get16(p) != alt)
				s = &dummy;
			p += 2;
			s->size = get32(p); p += 4;
			s->data = p;
			if((p + s->size) > e)
				goto Tooshort;
			p += (s->size + 3) & ~3;
		}
	} else {
		if(((i->rev>>8) & 0xFF) < 2)
			return "need firmware api >= 2";
		if(((i->rev>>8) & 0xFF) >= 3){
			i->build = get32(p); p += 4;
		}
		if((p + 5*4) > e)
			goto Tooshort;
		i->main.text.size = get32(p); p += 4;
		i->main.data.size = get32(p); p += 4;
		i->init.text.size = get32(p); p += 4;
		i->init.data.size = get32(p); p += 4;
		i->boot.text.size = get32(p); p += 4;
		i->main.text.data = p; p += i->main.text.size;
		i->main.data.data = p; p += i->main.data.size;
		i->init.text.data = p; p += i->init.text.size;
		i->init.data.data = p; p += i->init.data.size;
		i->boot.text.data = p; p += i->boot.text.size;
		if(p > e)
			goto Tooshort;
	}
	return 0;
}

static FWImage*
readfirmware(char *name)
{
	uchar dirbuf[sizeof(Dir)+100], *data;
	char buf[128], *err;
	FWImage *fw;
	int n, r;
	Chan *c;
	Dir d;

	if(!iseve())
		error(Eperm);
	if(!waserror()){
		snprint(buf, sizeof buf, "/boot/%s", name);
		c = namec(buf, Aopen, OREAD, 0);
		poperror();
	} else {
		snprint(buf, sizeof buf, "/lib/firmware/%s", name);
		c = namec(buf, Aopen, OREAD, 0);
	}
	if(waserror()){
		cclose(c);
		nexterror();
	}
	n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
	if(n <= 0)
		error("can't stat firmware");
	convM2D(dirbuf, n, &d, nil);
	fw = smalloc(sizeof(*fw) + 16 + d.length);
	data = (uchar*)(fw+1);
	if(waserror()){
		free(fw);
		nexterror();
	}
	r = 0;
	while(r < d.length){
		n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
		if(n <= 0)
			break;
		r += n;
	}
	if((err = crackfw(fw, data, r, 1)) != nil)
		error(err);
	poperror();
	poperror();
	cclose(c);
	return fw;
}

typedef struct Irqwait Irqwait;
struct Irqwait {
	Ctlr	*ctlr;
	u32int	mask;
};

static int
gotirq(void *arg)
{
	Irqwait *w;
	Ctlr *ctlr;

	w = arg;
	ctlr = w->ctlr;
	ctlr->wait.r = ctlr->wait.m & w->mask;
	if(ctlr->wait.r){
		ctlr->wait.m &= ~ctlr->wait.r;
		return 1;
	}
	ctlr->wait.w = w->mask;
	return 0;
}

static u32int
irqwait(Ctlr *ctlr, u32int mask, int timeout)
{
	Irqwait w;

	w.ctlr = ctlr;
	w.mask = mask;
	tsleep(&ctlr->wait, gotirq, &w, timeout);
	ctlr->wait.w = 0;
	return ctlr->wait.r & mask;
}

static char*
loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size)
{
	uchar *dma;
	char *err;

	dma = mallocalign(size, 16, 0, 0);
	if(dma == nil)
		return "no memory for dma";
	memmove(dma, data, size);
	coherence();
	if((err = niclock(ctlr)) != 0){
		free(dma);
		return err;
	}
	csr32w(ctlr, FhTxConfig + 9*32, 0);
	csr32w(ctlr, FhSramAddr + 9*4, dst);
	csr32w(ctlr, FhTfbdCtrl0 + 9*8, PCIWADDR(dma));
	csr32w(ctlr, FhTfbdCtrl1 + 9*8, size);
	csr32w(ctlr, FhTxBufStatus + 9*32,
		(1<<FhTxBufStatusTbNumShift) |
		(1<<FhTxBufStatusTbIdxShift) |
		FhTxBufStatusTfbdValid);
	csr32w(ctlr, FhTxConfig + 9*32, FhTxConfigDmaEna | FhTxConfigCirqHostEndTfd);
	nicunlock(ctlr);
	if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx){
		free(dma);
		return "dma error / timeout";
	}
	free(dma);
	return 0;
}

static int
txqready(void *arg)
{
	TXQ *q = arg;
	return q->n < Ntx;
}

static void
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 >= Ntx){
		iunlock(ctlr);
		eqlock(q);
		if(waserror()){
			qunlock(q);
			nexterror();
		}
		tsleep(q, txqready, q, 10);
		qunlock(q);
		ilock(ctlr);
	}
	q->n++;

	q->b[q->i] = block;
	c = q->c + q->i * Tcmdsize;
	d = q->d + q->i * Tdscsize;

	/* build command */
	c[0] = code;
	c[1] = 0;	/* flags */
	c[2] = q->i;
	c[3] = qid;

	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);
		if(size > Tbufsize)
			size = Tbufsize;
		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);
}

static void
cmd(Ctlr *ctlr, uint code, uchar *data, int size)
{
	qcmd(ctlr, 4, code, data, size, nil);
}

static void
setled(Ctlr *ctlr, int which, int on, int off)
{
	uchar c[8];

	csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl);

	memset(c, 0, sizeof(c));
	put32(c, 10000);
	c[4] = which;
	c[5] = on;
	c[6] = off;
	cmd(ctlr, 72, c, sizeof(c));
}

/*
 * initialization which runs after the firmware has been booted up
 */
static void
postboot(Ctlr *ctlr)
{
	uchar c[8];
	char *err;
	int i, q;

	/* main led turn on! (verify that firmware processes commands) */
	setled(ctlr, 2, 0, 1);

	if((err = niclock(ctlr)) != nil)
		error(err);
	ctlr->sched.base = prphread(ctlr, SchedSramAddr);
	for(i=0; i < SchedCtxLen5000/4; i++)
		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + i*4, 0);

	prphwrite(ctlr, SchedDramAddr5000, PCIWADDR(ctlr->sched.s)>>10);
	csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2);

	/* Enable chain mode for all queues, except command queue. */
	prphwrite(ctlr, SchedQChainSel5000, 0xfffef);
	prphwrite(ctlr, SchedAggrSel5000, 0);

	for(q=0; q<nelem(ctlr->tx); q++){
		prphwrite(ctlr, SchedQueueRdptr5000 + q*4, 0);
		csr32w(ctlr, HbusTargWptr, q << 8);
		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8, 0);
		/* Set scheduler window size and frame limit. */
		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8 + 4, 64<<16 | 64);
	}

	/* Enable interrupts for all our 20 queues. */
	prphwrite(ctlr, SchedIntrMask5000, 0xfffff);
	/* Identify TX FIFO rings (0-7). */
	prphwrite(ctlr, SchedTxFact5000, 0xff);
	/* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */
	for(q=0; q<7; q++){
		static uchar qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 };
		prphwrite(ctlr, SchedQueueStatus5000 + q*4, 0x00ff0018 | qid2fifo[q]);
	}
	nicunlock(ctlr);

	if(ctlr->type != Type5150){
		memset(c, 0, sizeof(c));
		c[0] = 15;	/* code */
		c[1] = 0;	/* grup */
		c[2] = 1;	/* ngroup */
		c[3] = 1;	/* isvalid */
		put16(c+4, ctlr->eeprom.crystal);
		cmd(ctlr, 176, c, 8);
	}

	if(ctlr->type != Type4965){
		put32(c, ctlr->rfcfg.txantmask & 7);
		cmd(ctlr, 152, c, 4);
	}
}

static void
addnode(Ctlr *ctlr, uchar id, uchar *addr)
{
	uchar c[Tcmdsize], *p;

	memset(p = c, 0, sizeof(c));
	*p++ = 0;	/* control (1 = update) */
	p += 3;		/* reserved */
	memmove(p, addr, 6);
	p += 6;
	p += 2;		/* reserved */
	*p++ = id;	/* node id */
	p++;		/* flags */
	p += 2;		/* reserved */
	p += 2;		/* kflags */
	p++;		/* tcs2 */
	p++;		/* reserved */
	p += 5*2;	/* ttak */
	p++;		/* kid */
	p++;		/* reserved */
	p += 16;	/* key */
	if(ctlr->type != Type4965){
		p += 8;		/* tcs */
		p += 8;		/* rxmic */
		p += 8;		/* txmic */
		p += 4;		/* htflags */
		p += 4;		/* mask */
		p += 2;		/* disable tid */
		p += 2;		/* reserved */
		p++;		/* add ba tid */
		p++;		/* del ba tid */
		p += 2;		/* add ba ssn */
		p += 4;		/* reserved */
	}
	cmd(ctlr, 24, c, p - c);
}

void
rxon(Ether *edev)
{
	uchar c[Tcmdsize], *p;
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	memset(p = c, 0, sizeof(c));
	memmove(p, edev->ea, 6); p += 8;	/* myaddr */
	p += 8;					/* bssid */
	memmove(p, edev->ea, 6); p += 8;	/* wlap */
	*p++ = 3;				/* mode */
	*p++ = 0;				/* air (?) */
	/* rxchain */
	put16(p, ((ctlr->rfcfg.rxantmask & 7)<<1) | (2<<10) | (2<<12));
	p += 2;
	*p++ = 0xff;				/* ofdm mask (not yet negotiated) */
	*p++ = 0x0f;				/* cck mask (not yet negotiated) */
	p += 2;					/* associd (?) */
	put32(p, (1<<15)|(1<<30)|(1<<0));	/* flags (TSF | CTS_TO_SELF | 24GHZ) */
	p += 4;
	put32(p, 4|1);				/* filter (MULTICAST|PROMISC) */
	p += 4;
	*p++ = ctlr->channel;			/* chan */
	p++;					/* reserved */
	*p++ = 0xff;				/* ht single mask */
	*p++ = 0xff;				/* ht dual mask */
	if(ctlr->type != Type4965){
		*p++ = 0xff;			/* ht triple mask */
		p++;				/* reserved */
		put16(p, 0); p += 2;		/* acquisition */
		p += 2;				/* reserved */
	}
	cmd(ctlr, 16, c, p - c);
}

static struct ratetab {
	uchar	rate;
	uchar	plcp;
	uchar	flags;
} ratetab[] = {
	{   2,  10, 1<<1 },
	{   4,  20, 1<<1 },
	{  11,  55, 1<<1 },
	{  22, 110, 1<<1 },
	{  12, 0xd, 0 },
	{  18, 0xf, 0 },
	{  24, 0x5, 0 },
	{  36, 0x7, 0 },
	{  48, 0x9, 0 },
	{  72, 0xb, 0 },
	{  96, 0x1, 0 },
	{ 108, 0x3, 0 },
	{ 120, 0x3, 0 }
};

static void
transmit(Wifi *wifi, Wnode *, Block *b)
{
	uchar c[Tcmdsize], *p;
	Ctlr *ctlr;

	ctlr = wifi->ether->ctlr;

	memset(p = c, 0, sizeof(c));
	put16(p, BLEN(b));
	p += 2;
	p += 2;		/* lnext */
	put32(p, 0);	/* flags */
	p += 4;
	put32(p, 0);
	p += 4;		/* scratch */
	*p++ = ratetab[2].plcp;			/* plcp */
	*p++ = ratetab[2].flags | (1<<6);	/* rflags */
	p += 2;		/* xflags */
	*p++ = 15;	/* id (5000 only) */
	*p++ = 0;	/* security */
	*p++ = 0;	/* linkq */
	p++;		/* reserved */
	p += 16;	/* key */
	p += 2;		/* fnext */
	p += 2;		/* reserved */
	put32(p, ~0);	/* lifetime */
	p += 4;
	/* scratch ptr? not clear what this is for */
	put32(p, PCIWADDR(ctlr->kwpage));
	p += 5;
	*p++ = 60;	/* rts ntries */
	*p++ = 15;	/* data ntries */
	*p++ = 0;	/* tid */
	put16(p, 0);	/* timeout */
	p += 2;
	p += 2;		/* txop */
	qcmd(ctlr, 0, 28, c, p - c, b);
}

static int
rbplant(Ctlr *ctlr, int i)
{
	Block *b;

	b = iallocb(Rbufsize + 256);
	if(b == nil)
		return -1;
	b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, 256);
	memset(b->rp, 0, Rdscsize);
	ctlr->rx.b[i] = b;
	ctlr->rx.p[i] = PCIWADDR(b->rp) >> 8;
	return 0;
}

static long
iwlctl(Ether *edev, void *buf, long n)
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if(ctlr->wifi)
		return wifictl(ctlr->wifi, buf, n);
	return 0;
}

static long
iwlifstat(Ether *edev, void *buf, long n, ulong off)
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if(ctlr->wifi)
		return wifistat(ctlr->wifi, buf, n, off);
	return 0;
}

static void
setoptions(Ether *edev)
{
	Ctlr *ctlr;
	char buf[64];
	int i;

	ctlr = edev->ctlr;
	ctlr->channel = 3;
	for(i = 0; i < edev->nopt; i++){
		if(strncmp(edev->opt[i], "channel=", 8) == 0)
			ctlr->channel = atoi(edev->opt[i]+8);
		else
		if(strncmp(edev->opt[i], "essid=", 6) == 0){
			snprint(buf, sizeof(buf), "essid %s", edev->opt[i]+6);
			if(!waserror()){
				wifictl(ctlr->wifi, buf, strlen(buf));
				poperror();
			}
		}
	}
}

static void
iwlattach(Ether *edev)
{
	FWImage *fw;
	Ctlr *ctlr;
	char *err;
	RXQ *rx;
	TXQ *tx;
	int i, q;

	ctlr = edev->ctlr;
	eqlock(ctlr);
	if(waserror()){
		qunlock(ctlr);
		nexterror();
	}
	if(ctlr->attached == 0){
		if(ctlr->wifi == nil)
			ctlr->wifi = wifiattach(edev, transmit);

		if(ctlr->fw == nil){
			fw = readfirmware("iwn-5000");
			print("#l%d: firmware: rev %ux, build %ud, size %ux+%ux+%ux+%ux+%ux\n",
				edev->ctlrno,
				fw->rev, fw->build,
				fw->main.text.size, fw->main.data.size,
				fw->init.text.size, fw->init.data.size,
				fw->boot.text.size);
			ctlr->fw = fw;
		}

		rx = &ctlr->rx;
		rx->i = 0;
		if(rx->b == nil)
			rx->b = malloc(sizeof(Block*) * Nrx);
		if(rx->p == nil)
			rx->p = mallocalign(sizeof(u32int) * Nrx, 256, 0, 0);
		if(rx->s == nil)
			rx->s = mallocalign(Rstatsize, 16, 0, 0);
		if(rx->b == nil || rx->p == nil || rx->s == nil)
			error("no memory for rx ring");
		memset(rx->s, 0, Rstatsize);
		for(i=0; i<Nrx; i++){
			rx->p[i] = 0;
			if(rx->b[i] != nil){
				freeb(rx->b[i]);
				rx->b[i] = nil;
			}
			if(rbplant(ctlr, i) < 0)
				error("no memory for rx descriptors");
		}

		for(q=0; q<nelem(ctlr->tx); q++){
			tx = &ctlr->tx[q];
			tx->i = 0;
			tx->n = 0;
			if(tx->b == nil)
				tx->b = malloc(sizeof(Block*) * Ntx);
			if(tx->d == nil)
				tx->d = mallocalign(Tdscsize * Ntx, 256, 0, 0);
			if(tx->c == nil)
				tx->c = mallocalign(Tcmdsize * Ntx, 4, 0, 0);
			if(tx->b == nil || tx->d == nil || tx->c == nil)
				error("no memory for tx ring");
			memset(tx->d, 0, Tdscsize * Ntx);
		}

		if(ctlr->sched.s == nil)
			ctlr->sched.s = mallocalign(512 * nelem(ctlr->tx) * 2, 1024, 0, 0);
		if(ctlr->kwpage == nil)
			ctlr->kwpage = mallocalign(4096, 4096, 0, 0);

		if((err = niclock(ctlr)) != nil)
			error(err);
		prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
		nicunlock(ctlr);

		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi);

		if((err = niclock(ctlr)) != nil)
			error(err);
		prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis);
		nicunlock(ctlr);

		if((err = niclock(ctlr)) != nil)
			error(err);
		csr32w(ctlr, FhRxConfig, 0);
		csr32w(ctlr, FhRxWptr, 0);
		csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p) >> 8);
		csr32w(ctlr, FhStatusWptr, PCIWADDR(ctlr->rx.s) >> 4);
		csr32w(ctlr, FhRxConfig,
			FhRxConfigEna | 
			FhRxConfigIgnRxfEmpty |
			FhRxConfigIrqDstHost | 
			FhRxConfigSingleFrame |
			(Nrxlog << FhRxConfigNrbdShift));
		csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7);
		nicunlock(ctlr);

		if((err = niclock(ctlr)) != nil)
			error(err);
		prphwrite(ctlr, SchedTxFact5000, 0);
		csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4);
		for(q=0; q<nelem(ctlr->tx); q++)
			csr32w(ctlr, FhCbbcQueue + q*4, PCIWADDR(ctlr->tx[q].d) >> 8);
		nicunlock(ctlr);
		for(i=0; i<8; i++)
			csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna);
		csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
		csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked);

		ctlr->ie = Idefmask;
		csr32w(ctlr, Imr, ctlr->ie);
		csr32w(ctlr, Isr, ~0);

		if(ctlr->type >= Type6000)
			csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff);

		if((err = loadfirmware1(ctlr, 0x00000000, ctlr->fw->main.text.data, ctlr->fw->main.text.size)) != nil)
			error(err);
		if((err = loadfirmware1(ctlr, 0x00800000, ctlr->fw->main.data.data, ctlr->fw->main.data.size)) != nil)
			error(err);

		csr32w(ctlr, Reset, 0);
		if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
			error("firmware boot failed");

		postboot(ctlr);

		setoptions(edev);

		rxon(edev);
		addnode(ctlr, 15, edev->bcast);

		edev->prom = 1;
		edev->link = 1;
		ctlr->attached = 1;
	}
	qunlock(ctlr);
	poperror();
}

static void
receive(Ctlr *ctlr)
{
	Block *b, *bb;
	uchar *d, *dd, *cc;
	RXQ *rx;
	TXQ *tx;
	uint hw;

	rx = &ctlr->rx;
	if(rx->s == nil || rx->b == nil)
		return;
	for(hw = get16(rx->s) % Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){
		uchar type, flags, idx, qid;
		u32int len;

		b = rx->b[rx->i];
		if(b == nil)
			continue;

		d = b->rp;
		len = get32(d); d += 4;
		type = *d++;
		flags = *d++;
		USED(flags);
		idx = *d++;
		qid = *d++;

		if((qid & 0x80) == 0 && qid < nelem(ctlr->tx)){
			tx = &ctlr->tx[qid];
			if(tx->n > 0){
				bb = tx->b[idx];
				if(bb != nil){
					tx->b[idx] = nil;
					freeb(bb);
				}
				/* paranoia: clear tx descriptors */
				dd = tx->d + idx*Tdscsize;
				cc = tx->c + idx*Tcmdsize;
				memset(dd, 0, Tdscsize);
				memset(cc, 0, Tcmdsize);
				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 */
			break;
		case 102:	/* calibration result (Type5000 only)
			break;
		case 103:	/* calibration done (Type5000 only)
			break;
		case 130:	/* start scan */
			break;
		case 132:	/* stop scan */
			break;
		case 156:	/* rx statistics */
			break;
		case 157:	/* beacon statistics */
			break;
		case 161:	/* state changed */
			break;
		case 162:	/* beacon missed */
			break;
		case 192:	/* rx phy */
			break;
		case 195:	/* rx done */
			if(d + 60 > b->lim)
				break;
			d += 60;
		case 193:	/* mpdu rx done */
			if(d + 4 > b->lim)
				break;
			len = get16(d); d += 4;
			if(d + len + 4 > b->lim)
				break;
			if((get32(d + len) & 3) != 3)
				break;
			if(ctlr->wifi == nil)
				break;
			if(rbplant(ctlr, rx->i) < 0)
				break;
			b->rp = d;
			b->wp = d + len;
			wifiiq(ctlr->wifi, b);
			continue;
		case 197:	/* rx compressed ba */
			break;
		}
		/* paranoia: clear the descriptor */
		memset(b->rp, 0, Rdscsize);
	}
	csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7);
}

static void
iwlinterrupt(Ureg*, void *arg)
{
	u32int isr, fhisr;
	Ether *edev;
	Ctlr *ctlr;

	edev = arg;
	ctlr = edev->ctlr;
	ilock(ctlr);
	csr32w(ctlr, Imr, 0);
	isr = csr32r(ctlr, Isr);
	fhisr = csr32r(ctlr, FhIsr);
	if(isr == 0xffffffff || (isr & 0xfffffff0) == 0xa5a5a5a0){
		iunlock(ctlr);
		return;
	}
	if(isr == 0 && fhisr == 0)
		goto done;
	csr32w(ctlr, Isr, isr);
	csr32w(ctlr, FhIsr, fhisr);
	if((isr & (Iswrx | Ifhrx | Irxperiodic)) || (fhisr & Ifhrx))
		receive(ctlr);
	if(isr & Ierr){
		iprint("#l%d: fatal firmware error\n", edev->ctlrno);
		dumpctlr(ctlr);
	}
	ctlr->wait.m |= isr;
	if(ctlr->wait.m & ctlr->wait.w){
		ctlr->wait.r = ctlr->wait.m & ctlr->wait.w;
		ctlr->wait.m &= ~ctlr->wait.r;
		wakeup(&ctlr->wait);
	}
done:
	csr32w(ctlr, Imr, ctlr->ie);
	iunlock(ctlr);
}

static Ctlr *iwlhead, *iwltail;

static void
iwlpci(void)
{
	Pcidev *pdev;
	
	pdev = nil;
	while(pdev = pcimatch(pdev, 0, 0)) {
		Ctlr *ctlr;
		void *mem;
		
		if(pdev->ccrb != 2 || pdev->ccru != 0x80)
			continue;
		if(pdev->vid != 0x8086)
			continue;

		switch(pdev->did){
		default:
			continue;
		case 0x4236:	/* WiFi Link 5300 AGN */
			break;
		}

		/* Clear device-specific "PCI retry timeout" register (41h). */
		if(pcicfgr8(pdev, 0x41) != 0)
			pcicfgw8(pdev, 0x41, 0);

		/* Clear interrupt disable bit. Hardware bug workaround. */
		if(pdev->pcr & 0x400){
			pdev->pcr &= ~0x400;
			pcicfgw16(pdev, PciPCR, pdev->pcr);
		}

		pcisetbme(pdev);
		pcisetpms(pdev, 0);

		ctlr = malloc(sizeof(Ctlr));
		if(ctlr == nil) {
			print("iwl: unable to alloc Ctlr\n");
			continue;
		}
		ctlr->port = pdev->mem[0].bar & ~0x0F;
		mem = vmap(pdev->mem[0].bar & ~0x0F, pdev->mem[0].size);
		if(mem == nil) {
			print("iwl: can't map %8.8luX\n", pdev->mem[0].bar);
			free(ctlr);
			continue;
		}
		ctlr->nic = mem;
		ctlr->pdev = pdev;
		ctlr->type = (csr32r(ctlr, Rev) >> 4) & 0xF;

		if(iwlhead != nil)
			iwltail->link = ctlr;
		else
			iwlhead = ctlr;
		iwltail = ctlr;
	}
}

static int
iwlpnp(Ether* edev)
{
	Ctlr *ctlr;
	
	if(iwlhead == nil)
		iwlpci();
again:
	for(ctlr = iwlhead; ctlr != nil; ctlr = ctlr->link){
		if(ctlr->active)
			continue;
		if(edev->port == 0 || edev->port == ctlr->port){
			ctlr->active = 1;
			break;
		}
	}

	if(ctlr == nil)
		return -1;

	edev->ctlr = ctlr;
	edev->port = ctlr->port;
	edev->irq = ctlr->pdev->intl;
	edev->tbdf = ctlr->pdev->tbdf;
	edev->arg = edev;
	edev->interrupt = iwlinterrupt;
	edev->attach = iwlattach;
	edev->ifstat = iwlifstat;
	edev->ctl = iwlctl;
	edev->promiscuous = nil;
	edev->multicast = nil;
	edev->mbps = 10;

	if(iwlinit(edev) < 0){
		edev->ctlr = nil;
		goto again;
	}
	
	return 0;
}

void
etheriwllink(void)
{
	addethercard("iwl", iwlpnp);
}