code: plan9front

Download patch

ref: ed238e7ef822261efe3b4406e0b5c5e84ddf07fe
parent: 00328b5759f66400aed0e71ccc687250113e61ee
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Jun 28 14:32:54 EDT 2015

etherwpi: Intel PRO Wireless 3945abg driver based on openbsd's if_wpi (thanks aap)

--- a/sys/man/8/plan9.ini
+++ b/sys/man/8/plan9.ini
@@ -438,6 +438,16 @@
 or
 .B /boot. 
 See iwl section above for configuration details.
+.TP
+.B wpi
+Intel PRO Wireless 3945abg PCI/PCI-Express wireless adapters require
+firmware from
+.B http://firmware.openbsd.org/firmware/*/wpi-firmware*.tgz
+to be present on attach in
+.B /lib/firmware
+or
+.B /boot. 
+See iwl section above for configuration details.
 .SS DISKS, TAPES
 (S)ATA controllers are autodetected.
 .SS \fL*nodma=\fP
--- /dev/null
+++ b/sys/src/9/pc/etherwpi.c
@@ -1,0 +1,1860 @@
+#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 {
+	Nrxlog = 6,
+	Nrx    = 1<<Nrxlog,
+	Ntx    = 256,
+
+	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[255];
+
+	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);
+
+	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);
+		print("pwrgroup %d: %d %d %d\n", i, g->chan, g->maxpwr, g->temp);
+		print("samples");
+		for(j = 0; j < 5; j++){
+			g->samples[j].index = b[j*4];
+			g->samples[j].power = b[j*4+1];
+			print(", %d %d", g->samples[j].index, g->samples[j].power);
+		}
+		print("\n");
+	}
+
+	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 < Ntx;
+}
+
+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 >= Ntx && !ctlr->broken){
+		iunlock(ctlr);
+		qlock(q);
+		if(!waserror()){
+			tsleep(q, txqready, q, 10);
+			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;
+	}
+	if(bss != nil){
+		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;
+	}
+	flags = RFlagTSF | RFlag24Ghz | RFlagAuto;
+
+	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;
+	}
+
+	/* 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->maxrate;
+		}
+	}
+	qunlock(ctlr);
+
+	rate = 0;
+	if(p >= wpirates && p < &wpirates[nelem(ratetab)])
+		rate = p - wpirates;
+
+	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)
+{
+	char buf[64], *p;
+	Ctlr *ctlr;
+	int i;
+
+	ctlr = edev->ctlr;
+	for(i = 0; i < edev->nopt; i++){
+		snprint(buf, sizeof(buf), "%s", edev->opt[i]);
+		p = strchr(buf, '=');
+		if(p != nil)
+			*p = 0;
+		if(strcmp(buf, "debug") == 0
+		|| strcmp(buf, "essid") == 0
+		|| strcmp(buf, "bssid") == 0){
+			if(p != nil)
+				*p = ' ';
+			if(!waserror()){
+				wifictl(ctlr->wifi, buf, strlen(buf));
+				poperror();
+			}
+		}
+	}
+}
+
+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
+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){
+			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;
+	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((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);
+				}
+				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 */
+			break;
+
+		case 130:	/* start scan */
+			break;
+
+		case 132:	/* stop scan */
+			break;
+
+		case 161:	/* state change */
+			break;
+		}
+	}
+	csr32w(ctlr, FhRxWptr, ((hw+Nrx-1) % Nrx) & ~7);
+}
+
+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;
+		print("#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);
+
+		pcisetbme(pdev);
+		pcisetpms(pdev, 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->interrupt = wpiinterrupt;
+	edev->attach = wpiattach;
+	edev->ifstat = wpiifstat;
+	edev->ctl = wpictl;
+	edev->shutdown = wpishutdown;
+	edev->promiscuous = wpipromiscuous;
+	edev->multicast = nil;
+	edev->mbps = 54;
+
+	if(wpiinit(edev) < 0){
+		edev->ctlr = nil;
+		goto again;
+	}
+
+	return 0;
+}
+
+void
+etherwpilink(void)
+{
+	addethercard("wpi", wpipnp);
+}
--- a/sys/src/9/pc/pccpuf
+++ b/sys/src/9/pc/pccpuf
@@ -69,6 +69,7 @@
 	etheryuk	pci
 	etherwavelan	wavelan devi82365 cis pci
 	etheriwl	pci wifi
+	etherwpi	pci wifi
 	etherrt2860	pci wifi
 	ethervirtio	pci
 	ethermedium
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -70,6 +70,7 @@
 	etheryuk	pci
 	etherwavelan	wavelan devi82365 cis pci
 	etheriwl	pci wifi
+	etherwpi	pci wifi
 	etherrt2860	pci wifi
 	ethervirtio	pci
 	ethermedium
--- a/sys/src/9/pc64/pc64
+++ b/sys/src/9/pc64/pc64
@@ -68,6 +68,7 @@
 #	etheryuk	pci
 #	etherwavelan	wavelan devi82365 cis pci
 	etheriwl	pci wifi
+	etherwpi	pci wifi
 #	etherrt2860	pci wifi
 	ethervirtio	pci
 	ethermedium