git: 9front

Download patch

ref: 8807f4a2570a00550184c1c0e2300da9e1176ab9
parent: 5c6b45c607c3b76210a8d134a42033183a79d47b
author: cinap_lenrek <cinap_lenrek@gmx.de>
date: Fri Feb 8 22:19:50 EST 2013

etheriwl: experimental intel wifi link driver

--- /dev/null
+++ b/sys/src/9/pc/etheriwl.c
@@ -1,0 +1,1570 @@
+/*
+ * Intel WiFi Link driver.
+ *
+ * Written without any documentation but Damien Bergaminis
+ * OpenBSD iwn(4) driver sources. Requires intel firmware
+ * to be present in /lib/firmware/iwn-* on attach.
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "wifi.h"
+
+enum {
+
+	Ntxlog		= 8,
+	Ntx		= 1<<Ntxlog,
+	Nrxlog		= 8,
+	Nrx		= 1<<Nrxlog,
+
+	Rstatsize	= 16,
+	Rbufsize	= 4*1024,
+	Rdscsize	= 8,
+
+	Tdscsize	= 128,
+	Tcmdsize	= 140,
+};
+
+/* registers */
+enum {
+	Cfg		= 0x000,	/* config register */
+		MacSi		= 1<<8,
+		RadioSi		= 1<<9,
+		EepromLocked	= 1<<21,
+		NicReady	= 1<<22,
+		HapwakeL1A	= 1<<23,
+		PrepareDone	= 1<<25,
+		Prepare		= 1<<27,
+
+	Isr		= 0x008,	/* interrupt status */
+	Imr		= 0x00c,	/* interrupt mask */
+		Ialive		= 1<<0,
+		Iwakeup		= 1<<1,
+		Iswrx		= 1<<3,
+		Ictreached	= 1<<6,
+		Irftoggled	= 1<<7,
+		Iswerr		= 1<<25,
+		Isched		= 1<<26,
+		Ifhtx		= 1<<27,
+		Irxperiodic	= 1<<28,
+		Ihwerr		= 1<<29,
+		Ifhrx		= 1<<31,
+
+		Ierr		= Iswerr | Ihwerr,
+		Idefmask	= Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Ictreached | Irftoggled,
+
+	FhIsr		= 0x010,	/* second interrupt status */
+
+	Reset		= 0x020,
+
+	Rev		= 0x028,	/* hardware revision */
+
+	EepromIo	= 0x02c,	/* EEPROM i/o register */
+	EepromGp	= 0x030,
+	OtpromGp	= 0x034,
+		DevSelOtp	= 1<<16,
+		RelativeAccess	= 1<<17,
+		EccCorrStts	= 1<<20,
+		EccUncorrStts	= 1<<21,
+
+	Gpc		= 0x024,	/* gp cntrl */
+		MacAccessEna	= 1<<0,
+		MacClockReady	= 1<<0,
+		InitDone	= 1<<2,
+		MacAccessReq	= 1<<3,
+		NicSleep	= 1<<4,
+		RfKill		= 1<<27,
+
+	Gio		= 0x03c,
+		EnaL0S		= 1<<1,
+
+	Led		= 0x094,
+		LedBsmCtrl	= 1<<5,
+		LedOn		= 0x38,
+		LedOff		= 0x78,
+
+	UcodeGp1Clr	= 0x05c,
+		UcodeGp1RfKill		= 1<<1,
+		UcodeGp1CmdBlocked	= 1<<2,
+		UcodeGp1CtempStopRf	= 1<<3,
+
+	ShadowRegCtrl	= 0x0a8,
+
+	Giochicken	= 0x100,
+		L1AnoL0Srx	= 1<<23,
+		DisL0Stimer	= 1<<29,
+
+	AnaPll		= 0x20c,
+
+	Dbghpetmem	= 0x240,
+
+	MemRaddr	= 0x40c,
+	MemWaddr	= 0x410,
+	MemWdata	= 0x418,
+	MemRdata	= 0x41c,
+
+	PrphWaddr	= 0x444,
+	PrphRaddr	= 0x448,
+	PrphWdata	= 0x44c,
+	PrphRdata	= 0x450,
+
+	HbusTargWptr	= 0x460,
+};
+
+/*
+ * Flow-Handler registers.
+ */
+enum {
+	FhTfbdCtrl0	= 0x1900,	// +q*8
+	FhTfbdCtrl1	= 0x1904,	// +q*8
+
+	FhKwAddr	= 0x197c,
+
+	FhSramAddr	= 0x19a4,	// +q*4
+	FhCbbcQueue	= 0x19d0,	// +q*4
+	FhStatusWptr	= 0x1bc0,
+	FhRxBase	= 0x1bc4,
+	FhRxWptr	= 0x1bc8,
+	FhRxConfig	= 0x1c00,
+		FhRxConfigEna		= 1<<31,
+		FhRxConfigRbSize8K	= 1<<16,
+		FhRxConfigSingleFrame	= 1<<15,
+		FhRxConfigIrqDstHost	= 1<<12,
+		FhRxConfigIgnRxfEmpty	= 1<<2,
+
+		FhRxConfigNrbdShift	= 20,
+		FhRxConfigRbTimeoutShift= 4,
+
+	FhRxStatus	= 0x1c44,
+
+	FhTxConfig	= 0x1d00,	// +q*32
+		FhTxConfigDmaCreditEna	= 1<<3,
+		FhTxConfigDmaEna	= 1<<31,
+		FhTxConfigCirqHostEndTfd= 1<<20,
+
+	FhTxBufStatus	= 0x1d08,	// +q*32
+		FhTxBufStatusTbNumShift	= 20,
+		FhTxBufStatusTbIdxShift = 12,
+		FhTxBufStatusTfbdValid	= 3,
+
+	FhTxChicken	= 0x1e98,
+	FhTxStatus	= 0x1eb0,
+};
+
+/*
+ * NIC internal memory offsets.
+ */
+enum {
+	ApmgClkCtrl	= 0x3000,
+	ApmgClkEna	= 0x3004,
+	ApmgClkDis	= 0x3008,
+		DmaClkRqt	= 1<<9,
+		BsmClkRqt	= 1<<11,
+
+	ApmgPs		= 0x300c,
+		EarlyPwroffDis	= 1<<22,
+		PwrSrcVMain	= 0<<24,
+		PwrSrcVAux	= 2<<24,
+		PwrSrcMask	= 3<<24,
+		ResetReq	= 1<<26,
+
+	ApmgDigitalSvr	= 0x3058,
+	ApmgAnalogSvr	= 0x306c,
+	ApmgPciStt	= 0x3010,
+	BsmWrCtrl	= 0x3400,
+	BsmWrMemSrc	= 0x3404,
+	BsmWrMemDst	= 0x3408,
+	BsmWrDwCount	= 0x340c,
+	BsmDramTextAddr	= 0x3490,
+	BsmDramTextSize	= 0x3494,
+	BsmDramDataAddr	= 0x3498,
+	BsmDramDataSize	= 0x349c,
+	BsmSramBase	= 0x3800,
+};
+
+/*
+ * TX scheduler registers.
+ */
+enum {
+	SchedBase		= 0xa02c00,
+	SchedSramAddr		= SchedBase,
+	SchedDramAddr5000	= SchedBase+0x008,
+	SchedDramAddr4965	= SchedBase+0x010,
+	SchedTxFact5000		= SchedBase+0x010,
+	SchedTxFact4965		= SchedBase+0x01c,
+	SchedQueueRdptr4965	= SchedBase+0x064,	// +q*4
+	SchedQueueRdptr5000	= SchedBase+0x068,	// +q*4
+	SchedQChainSel4965	= SchedBase+0x0d0,
+	SchedIntrMask4965	= SchedBase+0x0e4,
+	SchedQChainSel5000	= SchedBase+0x0e8,
+	SchedQueueStatus4965	= SchedBase+0x104,	// +q*4
+	SchedIntrMask5000	= SchedBase+0x108,
+	SchedQueueStatus5000	= SchedBase+0x10c,	// +q*4
+	SchedAggrSel5000	= SchedBase+0x248,
+};
+
+enum {
+	SchedCtxOff4965		= 0x380,
+	SchedCtxLen4965		= 416,
+	SchedTransTblOff4965	= 0x500,
+
+	SchedCtxOff5000		= 0x600,
+	SchedCtxLen5000		= 512,
+	SchedTransTblOff5000	= 0x7e0,
+};
+
+/* controller types */
+enum {
+	Type4965	= 0,
+	Type5300	= 2,
+	Type5350	= 3,
+	Type5150	= 4,
+	Type5100	= 5,
+	Type1000	= 6,
+	Type6000	= 7,
+	Type6050	= 8,
+	Type6005	= 11,
+};
+
+typedef struct FWInfo FWInfo;
+typedef struct FWImage FWImage;
+typedef struct FWSect FWSect;
+
+typedef struct TXQ TXQ;
+typedef struct RXQ RXQ;
+
+typedef struct Ctlr Ctlr;
+
+struct FWSect
+{
+	uchar	*data;
+	uint	size;
+};
+
+struct FWImage
+{
+	struct {
+		FWSect	text;
+		FWSect	data;
+	} init, main, boot;
+
+	uint	rev;
+	uint	build;
+	char	descr[64+1];
+	uchar	data[];
+};
+
+struct FWInfo
+{
+	uchar	major;
+	uchar	minjor;
+	uchar	type;
+	uchar	subtype;
+
+	u32int	logptr;
+	u32int	errptr;
+	u32int	tstamp;
+	u32int	valid;
+};
+
+struct TXQ
+{
+	uint	n;
+	uint	i;
+	Block	**b;
+	uchar	*d;
+	uchar	*c;
+
+	Rendez;
+	QLock;
+};
+
+struct RXQ
+{
+	uint	i;
+	Block	**b;
+	u32int	*p;
+	uchar	*s;
+};
+
+struct Ctlr {
+	Lock;
+	QLock;
+
+	Ctlr *link;
+	Pcidev *pdev;
+	Wifi *wifi;
+
+	int type;
+	int port;
+	int active;
+	int attached;
+
+	u32int ie;
+
+	u32int *nic;
+	uchar *kwpage;
+
+	int channel;
+
+	RXQ rx;
+	TXQ tx[20];
+
+	struct {
+		Rendez;
+		u32int	m;
+		u32int	w;
+		u32int	r;
+	} wait;
+
+	struct {
+		uchar	type;
+		uchar	step;
+		uchar	dash;
+		uchar	txantmask;
+		uchar	rxantmask;
+	} rfcfg;
+
+	struct {
+		u32int	crystal;
+	} eeprom;
+
+	struct {
+		u32int	base;
+		uchar	*s;
+	} sched;
+
+	FWInfo fwinfo;
+	FWImage *fw;
+};
+
+#define csr32r(c, r)	(*((c)->nic+((r)/4)))
+#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))
+
+static uint
+get16(uchar *p){
+	return *((u16int*)p);
+}
+static uint
+get32(uchar *p){
+	return *((u32int*)p);
+}
+static void
+put32(uchar *p, uint v){
+	*((u32int*)p) = v;
+}
+static void
+put16(uchar *p, uint v){
+	*((u16int*)p) = v;
+};
+
+static char*
+niclock(Ctlr *ctlr)
+{
+	int i;
+
+	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | MacAccessReq);
+	for(i=0; i<1000; i++){
+		if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna)
+			return 0;
+		delay(10);
+	}
+	return "niclock: timeout";
+}
+
+static void
+nicunlock(Ctlr *ctlr)
+{
+	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq);
+}
+
+static u32int
+prphread(Ctlr *ctlr, uint off)
+{
+	csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off);
+	coherence();
+	return csr32r(ctlr, PrphRdata);
+}
+static void
+prphwrite(Ctlr *ctlr, uint off, u32int data)
+{
+	csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off);
+	coherence();
+	csr32w(ctlr, PrphWdata, data);
+}
+
+static u32int
+memread(Ctlr *ctlr, uint off)
+{
+	csr32w(ctlr, MemRaddr, off);
+	coherence();
+	return csr32r(ctlr, MemRdata);
+}
+static void
+memwrite(Ctlr *ctlr, uint off, u32int data)
+{
+	csr32w(ctlr, MemWaddr, off);
+	coherence();
+	csr32w(ctlr, MemWdata, data);
+}
+
+static void
+setfwinfo(Ctlr *ctlr, uchar *d, int len)
+{
+	FWInfo *i;
+
+	if(len < 32)
+		return;
+	i = &ctlr->fwinfo;
+	i->minjor = *d++;
+	i->major = *d++;
+	d += 2+8;
+	i->type = *d++;
+	i->subtype = *d++;
+	d += 2;
+	i->logptr = get32(d); d += 4;
+	i->errptr = get32(d); d += 4;
+	i->tstamp = get32(d); d += 4;
+	i->valid = get32(d);
+};
+
+static void
+dumpctlr(Ctlr *ctlr)
+{
+	u32int dump[13];
+	int i;
+
+	if(ctlr->fwinfo.errptr == 0){
+		print("no error pointer\n");
+		return;
+	}
+	for(i=0; i<nelem(dump); i++)
+		dump[i] = memread(ctlr, ctlr->fwinfo.errptr + i*4);
+	print(	"error:\tid %ux, pc %ux,\n"
+		"\tbranchlink %.8ux %.8ux, interruptlink %.8ux %.8ux,\n"
+		"\terrordata %.8ux %.8ux, srcline %ud, tsf %ux, time %ux\n",
+		dump[1], dump[2],
+		dump[4], dump[3], dump[6], dump[5],
+		dump[7], dump[8], dump[9], dump[10], dump[11]);
+}
+
+static char*
+eepromlock(Ctlr *ctlr)
+{
+	int i, j;
+
+	for(i=0; i<100; i++){
+		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | EepromLocked);
+		for(j=0; j<100; j++){
+			if(csr32r(ctlr, Cfg) & EepromLocked)
+				return 0;
+			delay(10);
+		}
+	}
+	return "eepromlock: timeout";
+}
+static void
+eepromunlock(Ctlr *ctlr)
+{
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked);
+}
+static char*
+eepromread(Ctlr *ctlr, void *data, int count, uint off)
+{
+	uchar *out = data;
+	u32int w;
+	int i;
+
+	w = 0;
+	for(; count > 0; count -= 2, off++){
+		csr32w(ctlr, EepromIo, off << 2);
+		for(i=0; i<10; i++){
+			w = csr32r(ctlr, EepromIo);
+			if(w & 1)
+				break;
+			delay(5);
+		}
+		if(i == 10)
+			return "eepromread: timeout";
+		*out++ = w >> 16;
+		if(count > 1)
+			*out++ = w >> 24;
+	}
+	return 0;
+}
+
+static char*
+handover(Ctlr *ctlr)
+{
+	int i;
+
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
+	for(i=0; i<5; i++){
+		if(csr32r(ctlr, Cfg) & NicReady)
+			return 0;
+		delay(10);
+	}
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare);
+	for(i=0; i<15000; i++){
+		if((csr32r(ctlr, Cfg) & PrepareDone) == 0)
+			break;
+		delay(10);
+	}
+	if(i >= 15000)
+		return "handover: timeout";
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
+	for(i=0; i<5; i++){
+		if(csr32r(ctlr, Cfg) & NicReady)
+			return 0;
+		delay(10);
+	}
+	return "handover: timeout";
+}
+
+static char*
+clockwait(Ctlr *ctlr)
+{
+	int i;
+
+	/* Set "initialization complete" bit. */
+	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | InitDone);
+	for(i=0; i<2500; i++){
+		if(csr32r(ctlr, Gpc) & MacClockReady)
+			return 0;
+		delay(10);
+	}
+	return "clockwait: timeout";
+}
+
+static char*
+poweron(Ctlr *ctlr)
+{
+	int capoff;
+	char *err;
+
+	/* Disable L0s exit timer (NMI bug workaround). */
+	csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | DisL0Stimer);
+
+	/* Don't wait for ICH L0s (ICH bug workaround). */
+	csr32w(ctlr, Giochicken, csr32r(ctlr, Giochicken) | L1AnoL0Srx);
+
+	/* Set FH wait threshold to max (HW bug under stress workaround). */
+	csr32w(ctlr, Dbghpetmem, csr32r(ctlr, Dbghpetmem) | 0xffff0000);
+
+	/* Enable HAP INTA to move adapter from L1a to L0s. */
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | HapwakeL1A);
+
+	capoff = pcicap(ctlr->pdev, PciCapPCIe);
+	if(capoff != -1){
+		/* Workaround for HW instability in PCIe L0->L0s->L1 transition. */
+		if(pcicfgr16(ctlr->pdev, capoff + 0x10) & 0x2)	/* LCSR -> L1 Entry enabled. */
+			csr32w(ctlr, Gio, csr32r(ctlr, Gio) | EnaL0S);
+		else
+			csr32w(ctlr, Gio, csr32r(ctlr, Gio) & ~EnaL0S);
+	}
+
+	if(ctlr->type != Type4965 && ctlr->type <= Type1000)
+		csr32w(ctlr, AnaPll, csr32r(ctlr, AnaPll) | 0x00880300);
+
+	/* Wait for clock stabilization before accessing prph. */
+	if((err = clockwait(ctlr)) != nil)
+		return err;
+
+	if((err = niclock(ctlr)) != nil)
+		return err;
+
+	/* Enable DMA and BSM (Bootstrap State Machine). */
+	if(ctlr->type == Type4965)
+		prphwrite(ctlr, ApmgClkEna, DmaClkRqt | BsmClkRqt);
+	else
+		prphwrite(ctlr, ApmgClkEna, DmaClkRqt);
+	delay(20);
+
+	/* Disable L1-Active. */
+	prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));
+
+	nicunlock(ctlr);
+	return 0;
+}
+
+static int
+iwlinit(Ether *edev)
+{
+	Ctlr *ctlr;
+	char *err;
+	uchar b[2];
+	uint u;
+
+	ctlr = edev->ctlr;
+	if((err = handover(ctlr)) != nil)
+		goto Err;
+	if((err = poweron(ctlr)) != nil)
+		goto Err;
+	if((csr32r(ctlr, EepromGp) & 0x7) == 0){
+		err = "bad rom signature";
+		goto Err;
+	}
+	if((err = eepromlock(ctlr)) != nil)
+		goto Err;
+	if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){
+		eepromunlock(ctlr);
+		goto Err;
+	}
+	if((err = eepromread(ctlr, b, 2, 0x048)) != nil){
+		eepromunlock(ctlr);
+		goto Err;
+	}
+	u = get16(b);
+	ctlr->rfcfg.type = u & 3;	u >>= 2;
+	ctlr->rfcfg.step = u & 3;	u >>= 2;
+	ctlr->rfcfg.dash = u & 3;	u >>= 4;
+	ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
+	ctlr->rfcfg.rxantmask = u & 15;
+	if((err = eepromread(ctlr, b, 4, 0x128)) != nil){
+		eepromunlock(ctlr);
+		goto Err;
+	}
+	ctlr->eeprom.crystal = get32(b);
+	eepromunlock(ctlr);
+
+	ctlr->ie = 0;
+	csr32w(ctlr, Isr, ~0);	/* clear pending interrupts */
+	csr32w(ctlr, Imr, 0);	/* no interrupts for now */
+
+	return 0;
+Err:
+	print("iwlinit: %s\n", err);
+	return -1;
+}
+
+static char*
+crackfw(FWImage *i, uchar *data, uint size, int alt)
+{
+	uchar *p, *e;
+	FWSect *s;
+
+	memset(i, 0, sizeof(*i));
+	if(size < 4){
+Tooshort:
+		return "firmware image too short";
+	}
+	p = data;
+	e = p + size;
+	i->rev = get32(p); p += 4;
+	if(i->rev == 0){
+		uvlong altmask;
+
+		if(size < (4+64+4+4+8))
+			goto Tooshort;
+		if(memcmp(p, "IWL\n", 4) != 0)
+			return "bad firmware signature";
+		p += 4;
+		strncpy(i->descr, (char*)p, 64);
+		i->descr[sizeof(i->descr)-1] = 0;
+		p += 64;
+		i->rev = get32(p); p += 4;
+		i->build = get32(p); p += 4;
+		altmask = get32(p); p += 4;
+		altmask |= (uvlong)get32(p) << 32; p += 4;
+		while(alt > 0 && (altmask & (1ULL<<alt)) == 0)
+			alt--;
+		while(p < e){
+			FWSect dummy;
+
+			if((p + 2+2+4) > e)
+				goto Tooshort;
+			switch(get16(p)){
+			case 1:	s = &i->main.text; break;
+			case 2: s = &i->main.data; break;
+			case 3: s = &i->init.text; break;
+			case 4: s = &i->init.data; break;
+			case 5: s = &i->boot.text; break;
+			default:s = &dummy;
+			}
+			p += 2;
+			if(get16(p) != alt)
+				s = &dummy;
+			p += 2;
+			s->size = get32(p); p += 4;
+			s->data = p;
+			if((p + s->size) > e)
+				goto Tooshort;
+			p += (s->size + 3) & ~3;
+		}
+	} else {
+		if(((i->rev>>8) & 0xFF) < 2)
+			return "need firmware api >= 2";
+		if(((i->rev>>8) & 0xFF) >= 3){
+			i->build = get32(p); p += 4;
+		}
+		if((p + 5*4) > e)
+			goto Tooshort;
+		i->main.text.size = get32(p); p += 4;
+		i->main.data.size = get32(p); p += 4;
+		i->init.text.size = get32(p); p += 4;
+		i->init.data.size = get32(p); p += 4;
+		i->boot.text.size = get32(p); p += 4;
+		i->main.text.data = p; p += i->main.text.size;
+		i->main.data.data = p; p += i->main.data.size;
+		i->init.text.data = p; p += i->init.text.size;
+		i->init.data.data = p; p += i->init.data.size;
+		i->boot.text.data = p; p += i->boot.text.size;
+		if(p > e)
+			goto Tooshort;
+	}
+	return 0;
+}
+
+static FWImage*
+readfirmware(char *name)
+{
+	uchar dirbuf[sizeof(Dir)+100], *data;
+	char buf[128], *err;
+	FWImage *fw;
+	int n, r;
+	Chan *c;
+	Dir d;
+
+	if(!iseve())
+		error(Eperm);
+	if(!waserror()){
+		snprint(buf, sizeof buf, "/boot/%s", name);
+		c = namec(buf, Aopen, OREAD, 0);
+		poperror();
+	} else {
+		snprint(buf, sizeof buf, "/lib/firmware/%s", name);
+		c = namec(buf, Aopen, OREAD, 0);
+	}
+	if(waserror()){
+		cclose(c);
+		nexterror();
+	}
+	n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
+	if(n <= 0)
+		error("can't stat firmware");
+	convM2D(dirbuf, n, &d, nil);
+	fw = smalloc(sizeof(*fw) + 16 + d.length);
+	data = (uchar*)(fw+1);
+	if(waserror()){
+		free(fw);
+		nexterror();
+	}
+	r = 0;
+	while(r < d.length){
+		n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
+		if(n <= 0)
+			break;
+		r += n;
+	}
+	if((err = crackfw(fw, data, r, 1)) != nil)
+		error(err);
+	poperror();
+	poperror();
+	cclose(c);
+	return fw;
+}
+
+typedef struct Irqwait Irqwait;
+struct Irqwait {
+	Ctlr	*ctlr;
+	u32int	mask;
+};
+
+static int
+gotirq(void *arg)
+{
+	Irqwait *w;
+	Ctlr *ctlr;
+
+	w = arg;
+	ctlr = w->ctlr;
+	ctlr->wait.r = ctlr->wait.m & w->mask;
+	if(ctlr->wait.r){
+		ctlr->wait.m &= ~ctlr->wait.r;
+		return 1;
+	}
+	ctlr->wait.w = w->mask;
+	return 0;
+}
+
+static u32int
+irqwait(Ctlr *ctlr, u32int mask, int timeout)
+{
+	Irqwait w;
+
+	w.ctlr = ctlr;
+	w.mask = mask;
+	tsleep(&ctlr->wait, gotirq, &w, timeout);
+	ctlr->wait.w = 0;
+	return ctlr->wait.r & mask;
+}
+
+static char*
+loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size)
+{
+	uchar *dma;
+	char *err;
+
+	dma = mallocalign(size, 16, 0, 0);
+	if(dma == nil)
+		return "no memory for dma";
+	memmove(dma, data, size);
+	coherence();
+	if((err = niclock(ctlr)) != 0){
+		free(dma);
+		return err;
+	}
+	csr32w(ctlr, FhTxConfig + 9*32, 0);
+	csr32w(ctlr, FhSramAddr + 9*4, dst);
+	csr32w(ctlr, FhTfbdCtrl0 + 9*8, PCIWADDR(dma));
+	csr32w(ctlr, FhTfbdCtrl1 + 9*8, size);
+	csr32w(ctlr, FhTxBufStatus + 9*32,
+		(1<<FhTxBufStatusTbNumShift) |
+		(1<<FhTxBufStatusTbIdxShift) |
+		FhTxBufStatusTfbdValid);
+	csr32w(ctlr, FhTxConfig + 9*32, FhTxConfigDmaEna | FhTxConfigCirqHostEndTfd);
+	nicunlock(ctlr);
+	if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx){
+		free(dma);
+		return "dma error / timeout";
+	}
+	free(dma);
+	return 0;
+}
+
+static int
+txqready(void *arg)
+{
+	TXQ *q = arg;
+	return q->n < Ntx-8;
+}
+
+static void
+qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block)
+{
+	uchar *d, *c;
+	TXQ *q;
+
+	ilock(ctlr);
+	q = &ctlr->tx[qid];
+	while(q->n >= Ntx){
+		iunlock(ctlr);
+		eqlock(q);
+		if(waserror()){
+			qunlock(q);
+			nexterror();
+		}
+		tsleep(q, txqready, q, 10);
+		qunlock(q);
+		ilock(ctlr);
+	}
+	q->n++;
+
+	q->b[q->i] = block;
+	c = q->c + q->i * Tcmdsize;
+	d = q->d + q->i * Tdscsize;
+
+	/* build command */
+	c[0] = code;
+	c[1] = 0;	/* flags */
+	c[2] = q->i;
+	c[3] = qid;
+
+	assert(size <= Tcmdsize-4);
+	memmove(c+4, data, size);
+
+	size += 4;
+
+	/* build descriptor */
+	*d++ = 0;
+	*d++ = 0;
+	*d++ = 0;
+	*d++ = 1 + (block != nil); /* nsegs */
+	put32(d, PCIWADDR(c));	d += 4;
+	put16(d, size << 4); d += 2;
+	if(block != nil){
+		put32(d, PCIWADDR(block->rp));	d += 4;
+		put16(d, BLEN(block) << 4);
+	}
+
+	coherence();
+
+	q->i = (q->i+1) % Ntx;
+	csr32w(ctlr, HbusTargWptr, (qid<<8) | q->i);
+
+	iunlock(ctlr);
+}
+
+static void
+cmd(Ctlr *ctlr, uint code, uchar *data, int size)
+{
+	qcmd(ctlr, 4, code, data, size, nil);
+}
+
+static void
+setled(Ctlr *ctlr, int which, int on, int off)
+{
+	uchar c[8];
+
+	csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl);
+
+	memset(c, 0, sizeof(c));
+	put32(c, 10000);
+	c[4] = which;
+	c[5] = on;
+	c[6] = off;
+	cmd(ctlr, 72, c, sizeof(c));
+}
+
+/*
+ * initialization which runs after the firmware has been booted up
+ */
+static void
+postboot(Ctlr *ctlr)
+{
+	uchar c[8];
+	char *err;
+	int i, q;
+
+	/* main led turn on! (verify that firmware processes commands) */
+	setled(ctlr, 2, 0, 1);
+
+	if((err = niclock(ctlr)) != nil)
+		error(err);
+	ctlr->sched.base = prphread(ctlr, SchedSramAddr);
+	for(i=0; i < SchedCtxLen5000/4; i++)
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + i*4, 0);
+
+	prphwrite(ctlr, SchedDramAddr5000, PCIWADDR(ctlr->sched.s)>>10);
+	csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2);
+
+	/* Enable chain mode for all queues, except command queue. */
+	prphwrite(ctlr, SchedQChainSel5000, 0xfffef);
+	prphwrite(ctlr, SchedAggrSel5000, 0);
+
+	for(q=0; q<nelem(ctlr->tx); q++){
+		prphwrite(ctlr, SchedQueueRdptr5000 + q*4, 0);
+		csr32w(ctlr, HbusTargWptr, q << 8);
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8, 0);
+		/* Set scheduler window size and frame limit. */
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff5000 + q*8 + 4, 64<<16 | 64);
+	}
+
+	/* Enable interrupts for all our 20 queues. */
+	prphwrite(ctlr, SchedIntrMask5000, 0xfffff);
+	/* Identify TX FIFO rings (0-7). */
+	prphwrite(ctlr, SchedTxFact5000, 0xff);
+	/* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */
+	for(q=0; q<7; q++){
+		static uchar qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 };
+		prphwrite(ctlr, SchedQueueStatus5000 + q*4, 0x00ff0018 | qid2fifo[q]);
+	}
+	nicunlock(ctlr);
+
+	if(ctlr->type != Type5150){
+		c[0] = 15;	/* code */
+		c[1] = 0;	/* grup */
+		c[2] = 1;	/* ngroup */
+		c[3] = 1;	/* isvalid */
+		put16(c+4, ctlr->eeprom.crystal);
+		cmd(ctlr, 176, c, 8);
+	}
+
+	if(ctlr->type != Type4965){
+		put32(c, ctlr->rfcfg.txantmask & 7);
+		cmd(ctlr, 152, c, 4);
+	}
+}
+
+static void
+addnode(Ctlr *ctlr, uchar id, uchar *addr)
+{
+	uchar c[Tcmdsize], *p;
+
+	memset(p = c, 0, sizeof(c));
+	*p++ = 0;	/* control (1 = update) */
+	p += 3;		/* reserved */
+
+	memmove(p, addr, 6);
+	p += 6;
+
+	p += 2;		/* reserved */
+
+	*p++ = id;	/* node id */
+
+	p++;		/* flags */
+	p += 2;		/* reserved */
+	p += 2;		/* kflags */
+	p++;		/* tcs2 */
+	p++;		/* reserved */
+	p += 5*2;	/* ttak */
+	p++;		/* kid */
+	p++;		/* reserved */
+	p += 16;	/* key */
+	if(ctlr->type != Type4965){
+		p += 8;		/* tcs */
+		p += 8;		/* rxmic */
+		p += 8;		/* txmic */
+		p += 4;		/* htflags */
+		p += 4;		/* mask */
+		p += 2;		/* disable tid */
+		p += 2;		/* reserved */
+		p++;		/* add ba tid */
+		p++;		/* del ba tid */
+		p += 2;		/* add ba ssn */
+		p += 4;		/* reserved */
+	}
+	cmd(ctlr, 24, c, p - c);
+}
+
+void
+rxon(Ether *edev)
+{
+	Ctlr *ctlr;
+	uchar b[128-4], *p;
+
+	ctlr = edev->ctlr;
+	memset(p = b, 0, sizeof(b));
+	memmove(p, edev->ea, 6); p += 8;	/* myaddr */
+	p += 8;					/* bssid */
+	memmove(p, edev->ea, 6); p += 8;	/* wlap */
+	*p++ = 3;				/* mode */
+	*p++ = 0;				/* air (?) */
+	/* rxchain */
+	put16(p, ((ctlr->rfcfg.rxantmask & 7)<<1) | (2<<10) | (2<<12));
+	p += 2;
+	*p++ = 0xff;				/* ofdm mask (not yet negotiated) */
+	*p++ = 0x0f;				/* cck mask (not yet negotiated) */
+	p += 2;					/* associd (?) */
+	put32(p, (1<<15)|(1<<30)|(1<<0));	/* flags (TSF | CTS_TO_SELF | 24GHZ) */
+	p += 4;
+	put32(p, 4|1);				/* filter (MULTICAST|PROMISC) */
+	p += 4;
+	*p++ = ctlr->channel;			/* chan */
+	p++;					/* reserved */
+	*p++ = 0xff;				/* ht single mask */
+	*p++ = 0xff;				/* ht dual mask */
+	if(ctlr->type != Type4965){
+		*p++ = 0xff;			/* ht triple mask */
+		p++;				/* reserved */
+		put16(p, 0); p += 2;		/* acquisition */
+		p += 2;				/* reserved */
+	}
+	cmd(ctlr, 16, b, p - b);
+}
+
+static struct ratetab {
+	uchar	rate;
+	uchar	plcp;
+	uchar	flags;
+} ratetab[] = {
+	{   2,  10, 1<<1 },
+	{   4,  20, 1<<1 },
+	{  11,  55, 1<<1 },
+	{  22, 110, 1<<1 },
+	{  12, 0xd, 0 },
+	{  18, 0xf, 0 },
+	{  24, 0x5, 0 },
+	{  36, 0x7, 0 },
+	{  48, 0x9, 0 },
+	{  72, 0xb, 0 },
+	{  96, 0x1, 0 },
+	{ 108, 0x3, 0 },
+	{ 120, 0x3, 0 }
+};
+
+static void
+transmit(Wifi *wifi, Wnode *, Block *b)
+{
+	uchar c[Tcmdsize], *p;
+	Ctlr *ctlr;
+
+	ctlr = wifi->ether->ctlr;
+
+	memset(p = c, 0, sizeof(c));
+	put16(p, BLEN(b));
+	p += 2;
+	p += 2;		/* lnext */
+	put32(p, 0);	/* flags */
+	p += 4;
+	put32(p, 0);
+	p += 4;		/* scratch */
+	*p++ = ratetab[2].plcp;			/* plcp */
+	*p++ = ratetab[2].flags | (1<<6);	/* rflags */
+	p += 2;		/* xflags */
+	*p++ = 15;	/* id (5000 only) */
+	*p++ = 0;	/* security */
+	*p++ = 0;	/* linkq */
+	p++;		/* reserved */
+	p += 16;	/* key */
+	p += 2;		/* fnext */
+	p += 2;		/* reserved */
+	put32(p, ~0);	/* lifetime */
+	p += 4;
+	/* scratch ptr? not clear what this is for */
+	put32(p, PCIWADDR(ctlr->kwpage));
+	p += 5;
+	*p++ = 60;	/* rts ntries */
+	*p++ = 15;	/* data ntries */
+	*p++ = 0;	/* tid */
+	put16(p, 0);	/* timeout */
+	p += 2;
+	p += 2;		/* txop */
+	qcmd(ctlr, 0, 28, c, p - c, b);
+}
+
+static int
+rbplant(Ctlr *ctlr, int i)
+{
+	Block *b;
+
+	b = iallocb(Rbufsize + 256);
+	if(b == nil)
+		return -1;
+	b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, 256);
+	memset(b->rp, 0, Rdscsize);
+	ctlr->rx.b[i] = b;
+	ctlr->rx.p[i] = PCIWADDR(b->rp) >> 8;
+	return 0;
+}
+
+static long
+iwlctl(Ether *edev, void *buf, long n)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr->wifi)
+		return wifictl(ctlr->wifi, buf, n);
+	return 0;
+}
+
+static long
+iwlifstat(Ether *edev, void *buf, long n, ulong off)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr->wifi)
+		return wifistat(ctlr->wifi, buf, n, off);
+	return 0;
+}
+
+static void
+setoptions(Ether *edev)
+{
+	Ctlr *ctlr;
+	char buf[64];
+	int i;
+
+	ctlr = edev->ctlr;
+	ctlr->channel = 3;
+	for(i = 0; i < edev->nopt; i++){
+		if(strncmp(edev->opt[i], "channel=", 8) == 0)
+			ctlr->channel = atoi(edev->opt[i]+8);
+		else
+		if(strncmp(edev->opt[i], "essid=", 6) == 0){
+			snprint(buf, sizeof(buf), "essid %s", edev->opt[i]+6);
+			if(!waserror()){
+				wifictl(ctlr->wifi, buf, strlen(buf));
+				poperror();
+			}
+		}
+	}
+}
+
+static void
+iwlattach(Ether *edev)
+{
+	FWImage *fw;
+	Ctlr *ctlr;
+	char *err;
+	RXQ *rx;
+	TXQ *tx;
+	int i, q;
+
+	ctlr = edev->ctlr;
+	eqlock(ctlr);
+	if(waserror()){
+		qunlock(ctlr);
+		nexterror();
+	}
+	if(ctlr->attached == 0){
+		if(ctlr->wifi == nil)
+			ctlr->wifi = wifiattach(edev, transmit);
+
+		if(ctlr->fw == nil){
+			fw = readfirmware("iwn-5000");
+			print("#l%d: firmware: rev %ux, build %ud, size %ux+%ux+%ux+%ux+%ux\n",
+				edev->ctlrno,
+				fw->rev, fw->build,
+				fw->main.text.size, fw->main.data.size,
+				fw->init.text.size, fw->init.data.size,
+				fw->boot.text.size);
+			ctlr->fw = fw;
+		}
+
+		rx = &ctlr->rx;
+		rx->i = 0;
+		if(rx->b == nil)
+			rx->b = malloc(sizeof(Block*) * Nrx);
+		if(rx->p == nil)
+			rx->p = mallocalign(sizeof(u32int) * Nrx, 256, 0, 0);
+		if(rx->s == nil)
+			rx->s = mallocalign(Rstatsize, 16, 0, 0);
+		if(rx->b == nil || rx->p == nil || rx->s == nil)
+			error("no memory for rx ring");
+		memset(rx->s, 0, Rstatsize);
+		for(i=0; i<Nrx; i++){
+			rx->p[i] = 0;
+			if(rx->b[i] != nil){
+				freeb(rx->b[i]);
+				rx->b[i] = nil;
+			}
+			if(rbplant(ctlr, i) < 0)
+				error("no memory for rx descriptors");
+		}
+
+		for(q=0; q<nelem(ctlr->tx); q++){
+			tx = &ctlr->tx[q];
+			tx->i = 0;
+			tx->n = 0;
+			if(tx->b == nil)
+				tx->b = malloc(sizeof(Block*) * Ntx);
+			if(tx->d == nil)
+				tx->d = mallocalign(Tdscsize * Ntx, 256, 0, 0);
+			if(tx->c == nil)
+				tx->c = mallocalign(Tcmdsize * Ntx, 4, 0, 0);
+			if(tx->b == nil || tx->d == nil || tx->c == nil)
+				error("no memory for tx ring");
+			memset(tx->d, 0, Tdscsize * Ntx);
+		}
+
+		if(ctlr->sched.s == nil)
+			ctlr->sched.s = mallocalign(512 * nelem(ctlr->tx) * 2, 1024, 0, 0);
+		if(ctlr->kwpage == nil)
+			ctlr->kwpage = mallocalign(4096, 4096, 0, 0);
+
+		if((err = niclock(ctlr)) != nil)
+			error(err);
+		prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
+		nicunlock(ctlr);
+
+		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi);
+
+		if((err = niclock(ctlr)) != nil)
+			error(err);
+		prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis);
+		nicunlock(ctlr);
+
+		if((err = niclock(ctlr)) != nil)
+			error(err);
+		csr32w(ctlr, FhRxConfig, 0);
+		csr32w(ctlr, FhRxWptr, 0);
+		csr32w(ctlr, FhRxBase, PCIWADDR(ctlr->rx.p) >> 8);
+		csr32w(ctlr, FhStatusWptr, PCIWADDR(ctlr->rx.s) >> 4);
+		csr32w(ctlr, FhRxConfig,
+			FhRxConfigEna | 
+			FhRxConfigIgnRxfEmpty |
+			FhRxConfigIrqDstHost | 
+			FhRxConfigSingleFrame |
+			(Nrxlog << FhRxConfigNrbdShift));
+		csr32w(ctlr, FhRxWptr, (Nrx-1) & ~7);
+		nicunlock(ctlr);
+
+		if((err = niclock(ctlr)) != nil)
+			error(err);
+		prphwrite(ctlr, SchedTxFact5000, 0);
+		csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4);
+		for(q=0; q<nelem(ctlr->tx); q++)
+			csr32w(ctlr, FhCbbcQueue + q*4, PCIWADDR(ctlr->tx[q].d) >> 8);
+		nicunlock(ctlr);
+		for(i=0; i<8; i++)
+			csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna);
+		csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
+		csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked);
+
+		ctlr->ie = Idefmask;
+		csr32w(ctlr, Imr, ctlr->ie);
+		csr32w(ctlr, Isr, ~0);
+
+		if(ctlr->type >= Type6000)
+			csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff);
+
+		if((err = loadfirmware1(ctlr, 0x00000000, ctlr->fw->main.text.data, ctlr->fw->main.text.size)) != nil)
+			error(err);
+		if((err = loadfirmware1(ctlr, 0x00800000, ctlr->fw->main.data.data, ctlr->fw->main.data.size)) != nil)
+			error(err);
+
+		csr32w(ctlr, Reset, 0);
+		if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
+			error("firmware boot failed");
+
+		postboot(ctlr);
+
+		setoptions(edev);
+
+		rxon(edev);
+		addnode(ctlr, 15, edev->bcast);
+
+		edev->prom = 1;
+		edev->link = 1;
+		ctlr->attached = 1;
+	}
+	qunlock(ctlr);
+	poperror();
+}
+
+static void
+receive(Ctlr *ctlr)
+{
+	Block *b, *bb;
+	uchar *d;
+	RXQ *rx;
+	TXQ *tx;
+	uint hw;
+
+	rx = &ctlr->rx;
+	if(rx->s == nil || rx->b == nil)
+		return;
+	for(hw = get16(rx->s) % Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){
+		uchar type, flags, idx, qid;
+		u32int len;
+
+		b = rx->b[rx->i];
+		if(b == nil)
+			continue;
+
+		d = b->rp;
+		len = get32(d); d += 4;
+		type = *d++;
+		flags = *d++;
+		USED(flags);
+		idx = *d++;
+		qid = *d++;
+
+		len &= 0x3fff;
+		if(len < 4 || type == 0)
+			continue;
+
+		len -= 4;
+
+		switch(type){
+		case 1:		/* microcontroller ready */
+			setfwinfo(ctlr, d, len);
+			break;
+		case 24:	/* add node done */
+			break;
+		case 28:	/* tx done */
+			if(qid >= nelem(ctlr->tx))
+				break;
+			tx = &ctlr->tx[qid];
+			if(tx->n == 0)
+				break;
+			bb = tx->b[idx];
+			if(bb != nil){
+				tx->b[idx] = nil;
+				freeb(bb);
+			}
+			tx->n--;
+			break;
+		case 102:	/* calibration result (Type5000 only)
+			break;
+		case 103:	/* calibration done (Type5000 only)
+			break;
+		case 130:	/* start scan */
+			break;
+		case 132:	/* stop scan */
+			break;
+		case 156:	/* rx statistics */
+			break;
+		case 157:	/* beacon statistics */
+			break;
+		case 161:	/* state changed */
+			break;
+		case 162:	/* beacon missed */
+			break;
+		case 192:	/* rx phy */
+			break;
+		case 195:	/* rx done */
+			if(d + 60 > b->lim)
+				break;
+			d += 60;
+		case 193:	/* mpdu rx done */
+			if(d + 4 > b->lim)
+				break;
+			len = get16(d); d += 4;
+			if(d + len + 4 > b->lim)
+				break;
+			if((get32(d + len) & 3) != 3)
+				break;
+			if(ctlr->wifi == nil)
+				break;
+			if(rbplant(ctlr, rx->i) < 0)
+				break;
+			b->rp = d;
+			b->wp = d + len;
+			wifiiq(ctlr->wifi, b);
+			continue;
+		case 197:	/* rx compressed ba */
+			break;
+		}
+		/* paranoia: clear the descriptor */
+		memset(b->rp, 0, Rdscsize);
+	}
+	csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7);
+}
+
+static void
+iwlinterrupt(Ureg*, void *arg)
+{
+	u32int isr, fhisr;
+	Ether *edev;
+	Ctlr *ctlr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	ilock(ctlr);
+	csr32w(ctlr, Imr, 0);
+	isr = csr32r(ctlr, Isr);
+	fhisr = csr32r(ctlr, FhIsr);
+	if(isr == 0xffffffff || (isr & 0xfffffff0) == 0xa5a5a5a0){
+		iunlock(ctlr);
+		return;
+	}
+	if(isr == 0 && fhisr == 0)
+		goto done;
+	csr32w(ctlr, Isr, isr);
+	csr32w(ctlr, FhIsr, fhisr);
+	if((isr & (Iswrx | Ifhrx | Irxperiodic)) || (fhisr & Ifhrx))
+		receive(ctlr);
+	if(isr & Ierr){
+		iprint("#l%d: fatal firmware error\n", edev->ctlrno);
+		dumpctlr(ctlr);
+	}
+	ctlr->wait.m |= isr;
+	if(ctlr->wait.m & ctlr->wait.w){
+		ctlr->wait.r = ctlr->wait.m & ctlr->wait.w;
+		ctlr->wait.m &= ~ctlr->wait.r;
+		wakeup(&ctlr->wait);
+	}
+done:
+	csr32w(ctlr, Imr, ctlr->ie);
+	iunlock(ctlr);
+}
+
+static Ctlr *iwlhead, *iwltail;
+
+static void
+iwlpci(void)
+{
+	Pcidev *pdev;
+	
+	pdev = nil;
+	while(pdev = pcimatch(pdev, 0, 0)) {
+		Ctlr *ctlr;
+		void *mem;
+		
+		if(pdev->ccrb != 2 || pdev->ccru != 0x80)
+			continue;
+		if(pdev->vid != 0x8086)
+			continue;
+
+		switch(pdev->did){
+		default:
+			continue;
+		case 0x4236:	/* WiFi Link 5300 AGN */
+			break;
+		}
+
+		/* Clear device-specific "PCI retry timeout" register (41h). */
+		if(pcicfgr8(pdev, 0x41) != 0)
+			pcicfgw8(pdev, 0x41, 0);
+
+		/* Clear interrupt disable bit. Hardware bug workaround. */
+		if(pdev->pcr & 0x400){
+			pdev->pcr &= ~0x400;
+			pcicfgw16(pdev, PciPCR, pdev->pcr);
+		}
+
+		pcisetbme(pdev);
+		pcisetpms(pdev, 0);
+
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil) {
+			print("iwl: unable to alloc Ctlr\n");
+			continue;
+		}
+		ctlr->port = pdev->mem[0].bar & ~0x0F;
+		mem = vmap(pdev->mem[0].bar & ~0x0F, pdev->mem[0].size);
+		if(mem == nil) {
+			print("iwl: can't map %8.8luX\n", pdev->mem[0].bar);
+			free(ctlr);
+			continue;
+		}
+		ctlr->nic = mem;
+		ctlr->pdev = pdev;
+		ctlr->type = (csr32r(ctlr, Rev) >> 4) & 0xF;
+
+		if(iwlhead != nil)
+			iwltail->link = ctlr;
+		else
+			iwlhead = ctlr;
+		iwltail = ctlr;
+	}
+}
+
+static int
+iwlpnp(Ether* edev)
+{
+	Ctlr *ctlr;
+	
+	if(iwlhead == nil)
+		iwlpci();
+again:
+	for(ctlr = iwlhead; ctlr != nil; ctlr = ctlr->link){
+		if(ctlr->active)
+			continue;
+		if(edev->port == 0 || edev->port == ctlr->port){
+			ctlr->active = 1;
+			break;
+		}
+	}
+
+	if(ctlr == nil)
+		return -1;
+
+	edev->ctlr = ctlr;
+	edev->port = ctlr->port;
+	edev->irq = ctlr->pdev->intl;
+	edev->tbdf = ctlr->pdev->tbdf;
+	edev->arg = edev;
+	edev->interrupt = iwlinterrupt;
+	edev->attach = iwlattach;
+	edev->ifstat = iwlifstat;
+	edev->ctl = iwlctl;
+	edev->promiscuous = nil;
+	edev->multicast = nil;
+	edev->mbps = 10;
+
+	if(iwlinit(edev) < 0){
+		edev->ctlr = nil;
+		goto again;
+	}
+	
+	return 0;
+}
+
+void
+etheriwllink(void)
+{
+	addethercard("iwl", iwlpnp);
+}
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -120,6 +120,7 @@
 trap.$O:			/sys/include/tos.h
 uartaxp.$O:			uartaxp.i
 etherm10g.$O:			etherm10g2k.i etherm10g4k.i
+etheriwl.$O:			wifi.h
 
 init.h:D:		../port/initcode.c init9.c
 	$CC ../port/initcode.c
--- a/sys/src/9/pc/pccpuf
+++ b/sys/src/9/pc/pccpuf
@@ -67,6 +67,7 @@
 	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
+	etheriwl	pci wifi
 	ethermedium
 	netdevmedium
 	loopbackmedium
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -68,6 +68,7 @@
 	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
+	etheriwl	pci wifi
 	ethermedium
 	pcmciamodem
 	netdevmedium
--- /dev/null
+++ b/sys/src/9/pc/wifi.c
@@ -1,0 +1,424 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "pool.h"
+#include "ureg.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "wifi.h"
+
+typedef struct SNAP SNAP;
+struct SNAP
+{
+	uchar	dsap;
+	uchar	ssap;
+	uchar	control;
+	uchar	orgcode[3];
+	uchar	type[2];
+};
+
+enum {
+	SNAPHDRSIZE = 8,
+};
+
+static char Snone[] = "new";
+static char Sconn[] = "connecting";
+static char Sauth[] = "authenticated";
+static char Sunauth[] = "unauthentictaed";
+static char Sassoc[] = "associated";
+static char Sunassoc[] = "unassociated";
+
+void
+wifiiq(Wifi *wifi, Block *b)
+{
+	SNAP s;
+	Wifipkt w;
+	Etherpkt *e;
+
+	if(BLEN(b) < WIFIHDRSIZE)
+		goto drop;
+	memmove(&w, b->rp, WIFIHDRSIZE);
+	switch(w.fc[0] & 0x0c){
+	case 0x00:	/* management */
+		if((w.fc[1] & 3) != 0x00)	/* STA->STA */
+			break;
+		qpass(wifi->iq, b);
+		return;
+	case 0x04:	/* control */
+		break;
+	case 0x08:	/* data */
+		b->rp += WIFIHDRSIZE;
+		switch(w.fc[0] & 0xf0){
+		case 0x80:
+			b->rp += 2;
+			if(w.fc[1] & 0x80)
+				b->rp += 4;
+		case 0x00:
+			break;
+		default:
+			goto drop;
+		}
+		if(BLEN(b) < SNAPHDRSIZE || b->rp[0] != 0xAA || b->rp[1] != 0xAA || b->rp[2] != 0x03)
+			break;
+		memmove(&s, b->rp, SNAPHDRSIZE);
+		b->rp += SNAPHDRSIZE-ETHERHDRSIZE;
+		e = (Etherpkt*)b->rp;
+		switch(w.fc[1] & 0x03){
+		case 0x00:	/* STA->STA */
+			memmove(e->d, w.a1, Eaddrlen);
+			memmove(e->s, w.a2, Eaddrlen);
+			break;
+		case 0x01:	/* STA->AP */
+			memmove(e->d, w.a3, Eaddrlen);
+			memmove(e->s, w.a2, Eaddrlen);
+			break;
+		case 0x02:	/* AP->STA */
+			memmove(e->d, w.a1, Eaddrlen);
+			memmove(e->s, w.a3, Eaddrlen);
+			break;
+		case 0x03:	/* AP->AP */
+			goto drop;
+		}
+		memmove(e->type, s.type, 2);
+		etheriq(wifi->ether, b, 1);
+		return;
+	}
+drop:
+	freeb(b);
+}
+
+static void
+wifitx(Wifi *wifi, Block *b)
+{
+	Wifipkt *w;
+	uint seq;
+
+	seq = wifi->txseq++;
+	seq <<= 4;
+
+	w = (Wifipkt*)b->rp;
+	w->dur[0] = 0;
+	w->dur[1] = 0;
+	w->seq[0] = seq;
+	w->seq[1] = seq>>8;
+
+	(*wifi->transmit)(wifi, wifi->bss, b);
+}
+
+
+static Wnode*
+nodelookup(Wifi *wifi, uchar *bssid, int new)
+{
+	Wnode *wn, *nn;
+
+	if(memcmp(bssid, wifi->ether->bcast, Eaddrlen) == 0)
+		return nil;
+	for(wn = nn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
+		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0){
+			wn->lastseen = MACHP(0)->ticks;
+			return wn;
+		}
+		if(wn != wifi->bss && wn->lastseen < nn->lastseen)
+			nn = wn;
+	}
+	if(!new)
+		return nil;
+	memmove(nn->bssid, bssid, Eaddrlen);
+	nn->lastseen = MACHP(0)->ticks;
+	return nn;
+}
+
+static void
+sendauth(Wifi *wifi, Wnode *bss)
+{
+	Wifipkt *w;
+	Block *b;
+	uchar *p;
+
+	b = allocb(WIFIHDRSIZE + 3*2);
+	w = (Wifipkt*)b->wp;
+	w->fc[0] = 0xB0;	/* auth request */
+	w->fc[1] = 0x00;	/* STA->STA */
+	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
+	memmove(w->a2, wifi->ether->ea, Eaddrlen);
+	memmove(w->a3, bss->bssid, Eaddrlen);
+	b->wp += WIFIHDRSIZE;
+	p = b->wp;
+	*p++ = 0;	/* alg */
+	*p++ = 0;
+	*p++ = 1;	/* seq */
+	*p++ = 0;
+	*p++ = 0;	/* status */
+	*p++ = 0;
+	b->wp = p;
+	wifitx(wifi, b);
+}
+
+static void
+sendassoc(Wifi *wifi, Wnode *bss)
+{
+	Wifipkt *w;
+	Block *b;
+	uchar *p;
+
+	b = allocb(WIFIHDRSIZE + 128);
+	w = (Wifipkt*)b->wp;
+	w->fc[0] = 0x00;	/* assoc request */
+	w->fc[1] = 0x00;	/* STA->STA */
+	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
+	memmove(w->a2, wifi->ether->ea, Eaddrlen);
+	memmove(w->a3, bss->bssid, Eaddrlen);
+	b->wp += WIFIHDRSIZE;
+	p = b->wp;
+	*p++ = 1;	/* capinfo */
+	*p++ = 0;
+	*p++ = 16;	/* interval */
+	*p++ = 16>>8;
+	*p++ = 0;	/* SSID */
+	*p = strlen(bss->ssid);
+	memmove(p+1, bss->ssid, *p);
+	p += 1+*p;
+	*p++ = 1;	/* RATES */
+	*p++ = 1;
+	*p++ = 0x96;
+	b->wp = p;
+	wifitx(wifi, b);
+}
+
+static void
+recvassoc(Wifi *wifi, Wnode *wn, uchar *d, int len)
+{
+	uint s;
+
+	if(len < 2+2+2)
+		return;
+
+	d += 2;	/* caps */
+	s = d[0] | d[1]<<8;
+	d += 2;
+	switch(s){
+	case 0x00:
+		wn->aid = d[0] | d[1]<<8;
+		wifi->status = Sassoc;
+		break;
+	default:
+		wifi->status = Sunassoc;
+		return;
+	}
+}
+
+static void
+recvbeacon(Wifi *wifi, Wnode *wn, uchar *d, int len)
+{
+	uchar *e, *x;
+
+	if(len < 8+2+2)
+		return;
+
+	d += 8;	/* timestamp */
+	wn->ival = d[0] | d[1]<<8;
+	d += 2;
+	wn->cap = d[0] | d[1]<<8;
+	d += 2;
+
+	for(e = d + len; d+2 <= e; d = x){
+		d += 2;
+		x = d + d[-1];
+		switch(d[-2]){
+		case 0:	/* SSID */
+			len = 0;
+			while(len < 32 && d+len < x && d[len] != 0)
+				len++;
+			if(len == 0)
+				continue;
+			if(len != strlen(wn->ssid) || strncmp(wn->ssid, (char*)d, len) != 0){
+				strncpy(wn->ssid, (char*)d, len);
+				wn->ssid[len] = 0;
+				if(wifi->bss == nil && strcmp(wifi->essid, wn->ssid) == 0){
+					wifi->bss = wn;
+					wifi->status = Sconn;
+					sendauth(wifi, wn);
+				}
+			}
+			return;
+		}
+	}
+}
+
+static void
+wifiproc(void *arg)
+{
+	Wifi *wifi;
+	Wifipkt *w;
+	Wnode *wn;
+	Block *b;
+
+	b = nil;
+	wifi = arg;
+	for(;;){
+		if(b != nil)
+			freeb(b);
+		if((b = qbread(wifi->iq, 100000)) == nil)
+			break;
+		w = (Wifipkt*)b->rp;
+		switch(w->fc[0] & 0xf0){
+		case 0x50:	/* probe response */
+		case 0x80:	/* beacon */
+			if((wn = nodelookup(wifi, w->a3, 1)) == nil)
+				continue;
+			b->rp += WIFIHDRSIZE;
+			recvbeacon(wifi, wn, b->rp, BLEN(b));
+			continue;
+		}
+		if((wn = nodelookup(wifi, w->a3, 0)) == nil)
+			continue;
+		if(wn != wifi->bss)
+			continue;
+		switch(w->fc[0] & 0xf0){
+		case 0x10:	/* assoc response */
+		case 0x30:	/* reassoc response */
+			b->rp += WIFIHDRSIZE;
+			recvassoc(wifi, wn, b->rp, BLEN(b));
+			break;
+		case 0xb0:	/* auth */
+			wifi->status = Sauth;
+			sendassoc(wifi, wn);
+			break;
+		case 0xc0:	/* deauth */
+			wifi->status = Sunauth;
+			break;
+		}
+	}
+	pexit("wifi in queue closed", 0);
+}
+
+static void
+wifietheroq(Wifi *wifi, Block *b)
+{
+	Etherpkt e;
+	Wifipkt *w;
+	SNAP *s;
+
+	if(BLEN(b) < ETHERHDRSIZE){
+		freeb(b);
+		return;
+	}
+	memmove(&e, b->rp, ETHERHDRSIZE);
+
+	b->rp += ETHERHDRSIZE;
+	b = padblock(b, WIFIHDRSIZE + SNAPHDRSIZE);
+
+	w = (Wifipkt*)b->rp;
+	w->fc[0] = 0x08;	/* data */
+	w->fc[1] = 0x01;	/* STA->AP */
+	memmove(w->a1, wifi->bss ? wifi->bss->bssid : wifi->ether->bcast, Eaddrlen);
+	memmove(w->a2, e.s, Eaddrlen);
+	memmove(w->a3, e.d, Eaddrlen);
+
+	s = (SNAP*)(b->rp + WIFIHDRSIZE);
+	s->dsap = s->ssap = 0xAA;
+	s->control = 0x03;
+	s->orgcode[0] = 0;
+	s->orgcode[1] = 0;
+	s->orgcode[2] = 0;
+	memmove(s->type, e.type, 2);
+
+	wifitx(wifi, b);
+}
+
+static void
+wifoproc(void *arg)
+{
+	Ether *ether;
+	Wifi *wifi;
+	Block *b;
+
+	wifi = arg;
+	ether = wifi->ether;
+	while((b = qbread(ether->oq, 1000000)) != nil)
+		wifietheroq(wifi, b);
+	pexit("ether out queue closed", 0);
+}
+
+Wifi*
+wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*))
+{
+	Wifi *wifi;
+
+	wifi = malloc(sizeof(Wifi));
+	wifi->ether = ether;
+	wifi->iq = qopen(8*1024, 0, 0, 0);
+	wifi->transmit = transmit;
+	wifi->status = Snone;
+
+	kproc("wifi", wifiproc, wifi);
+	kproc("wifo", wifoproc, wifi);
+
+	return wifi;
+}
+
+long
+wifictl(Wifi *wifi, void *buf, long n)
+{
+	Cmdbuf *cb;
+	Wnode *wn;
+
+	cb = nil;
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	cb = parsecmd(buf, n);
+	if(cb->f[0] && strcmp(cb->f[0], "essid") == 0){
+		if(cb->f[1] == nil){
+			/* TODO senddeauth(wifi); */
+			wifi->essid[0] = 0;
+			wifi->bss = nil;
+		} else {
+			strncpy(wifi->essid, cb->f[1], 32);
+			wifi->essid[32] = 0;
+			for(wn=wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++)
+				if(strcmp(wifi->essid, wn->ssid) == 0){
+					wifi->bss = wn;
+					wifi->status = Sconn;
+					sendauth(wifi, wn);
+					break;
+				}
+		}
+	}
+	poperror();
+	free(cb);
+	return n;
+}
+
+long
+wifistat(Wifi *wifi, void *buf, long n, ulong off)
+{
+	static uchar zeros[Eaddrlen];
+	char *s, *p, *e;
+	Wnode *wn;
+	long now;
+
+	p = s = smalloc(4096);
+	e = s + 4096;
+
+	p = seprint(p, e, "status: %s\n", wifi->status);
+	p = seprint(p, e, "essid: %s\n", wifi->essid);
+	p = seprint(p, e, "bssid: %E\n", wifi->bss ? wifi->bss->bssid : zeros);
+
+	now = MACHP(0)->ticks;
+	for(wn=wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
+		if(wn->lastseen == 0)
+			continue;
+		p = seprint(p, e, "node: %E %.4x %d %ld %s\n",
+			wn->bssid, wn->cap, wn->ival, TK2MS(now - wn->lastseen), wn->ssid);
+	}
+	n = readstr(off, buf, n, s);
+	free(s);
+	return n;
+}
--- /dev/null
+++ b/sys/src/9/pc/wifi.h
@@ -1,0 +1,52 @@
+typedef struct Wnode Wnode;
+typedef struct Wifi Wifi;
+
+typedef struct Wifipkt Wifipkt;
+
+struct Wifipkt
+{
+	uchar	fc[2];
+	uchar	dur[2];
+	uchar	a1[Eaddrlen];
+	uchar	a2[Eaddrlen];
+	uchar	a3[Eaddrlen];
+	uchar	seq[2];
+};
+
+enum {
+	WIFIHDRSIZE = 2+2+3*6+2,
+};
+
+struct Wnode
+{
+	uchar	bssid[Eaddrlen];
+	char	ssid[32+2];
+	int	ival;
+	int	cap;
+
+	long	lastseen;
+
+	int	aid;
+};
+
+struct Wifi
+{
+	Ether	*ether;
+
+	Queue	*iq;
+	char	*status;
+	void	(*transmit)(Wifi*, Wnode*, Block*);
+
+	Wnode	node[16];
+	Wnode	*bss;
+
+	uint	txseq;
+	char	essid[32+2];
+};
+
+Wifi *wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*));
+void wifiiq(Wifi*, Block*);
+
+long wifistat(Wifi*, void*, long, ulong);
+long wifictl(Wifi*, void*, long);
+
--