shithub: riscv

Download patch

ref: de727cdb3b617b44ec223261044b9046350e0e61
parent: 6118f598e0caff4cba0d5838afa98631b1acf0e1
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Wed Apr 19 15:49:11 EDT 2023

imx8: make etheriwl work on MNT Reform, move it to port/ (thanks cinap); tested with 6205

--- a/sys/src/9/imx8/reform
+++ b/sys/src/9/imx8/reform
@@ -28,6 +28,7 @@
 link
 	usbxhciimx	usbxhci
 	etherimx	ethermii
+	etheriwl	pci wifi
 	ethermedium
 	loopbackmedium
 	netdevmedium
--- a/sys/src/9/pc/etheriwl.c
+++ /dev/null
@@ -1,4543 +1,0 @@
-/*
- * Intel WiFi Link driver.
- *
- * Written without any documentation but Damien Bergamini's
- * iwn(4) and Stefan Sperling's iwm(4) OpenBSD driver sources.
- * Requires Intel firmware to be present in /lib/firmware/iw[nm]-*
- * on attach.
- */
-
-#include "u.h"
-#include "../port/lib.h"
-#include "mem.h"
-#include "dat.h"
-#include "fns.h"
-#include "io.h"
-#include "../port/pci.h"
-#include "../port/error.h"
-#include "../port/netif.h"
-#include "../port/etherif.h"
-#include "../port/wifi.h"
-
-enum {
-	MaxQueue	= 24*1024,	/* total buffer is 2*MaxQueue: 48k at 22Mbit ≅ 20ms */
-
-	Ntxlog		= 8,
-	Ntx		= 1<<Ntxlog,
-	Ntxqmax		= MaxQueue/1500,
-
-	Nrxlog		= 8,
-	Nrx		= 1<<Nrxlog,
-
-	Rstatsize	= 16,
-
-	Rbufsize	= 4*1024,
-	Rdscsize	= 8,
-
-	Tdscsize	= 128,
-	Tcmdsize	= 140,
-
-	FWPageshift	= 12,
-	FWPagesize	= 1<<FWPageshift,
-	FWBlockshift	= 3,
-	FWBlockpages	= 1<<FWBlockshift,
-	FWBlocksize	= 1<<(FWBlockshift + FWPageshift),
-};
-
-/* registers */
-enum {
-	Cfg		= 0x000,	/* config register */
-		CfgMacDashShift	= 0,
-		CfgMacDashMask	= 3<<CfgMacDashShift,
-		CfgMacStepShift	= 2,
-		CfgMacStepMask	= 3<<CfgMacStepShift,
-
-		MacSi		= 1<<8,
-		RadioSi		= 1<<9,
-
-		CfgPhyTypeShift	= 10,
-		CfgPhyTypeMask	= 3<<CfgPhyTypeShift,
-		CfgPhyDashShift	= 12,
-		CfgPhyDashMask	= 3<<CfgPhyDashShift,
-		CfgPhyStepShift	= 14,
-		CfgPhyStepMask	= 3<<CfgPhyStepShift,
-
-		EepromLocked	= 1<<21,
-		NicReady	= 1<<22,
-		HapwakeL1A	= 1<<23,
-		PrepareDone	= 1<<25,
-		Prepare		= 1<<27,
-		EnablePme	= 1<<28,
-
-	Isr		= 0x008,	/* interrupt status */
-	Imr		= 0x00c,	/* interrupt mask */
-		Ialive		= 1<<0,
-		Iwakeup		= 1<<1,
-		Iswrx		= 1<<3,
-		Ictreached	= 1<<6,
-		Irftoggled	= 1<<7,
-		Iswerr		= 1<<25,
-		Isched		= 1<<26,
-		Ifhtx		= 1<<27,
-		Irxperiodic	= 1<<28,
-		Ihwerr		= 1<<29,
-		Ifhrx		= 1<<31,
-
-		Ierr		= Iswerr | Ihwerr,
-		Idefmask	= Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Ictreached | Irftoggled,
-
-	FhIsr		= 0x010,	/* second interrupt status */
-
-	Reset		= 0x020,
-		
-	Rev		= 0x028,	/* hardware revision */
-
-	EepromIo	= 0x02c,	/* EEPROM i/o register */
-	EepromGp	= 0x030,
-		
-	OtpromGp	= 0x034,
-		DevSelOtp	= 1<<16,
-		RelativeAccess	= 1<<17,
-		EccCorrStts	= 1<<20,
-		EccUncorrStts	= 1<<21,
-
-	Gpc		= 0x024,	/* gp cntrl */
-		MacAccessEna	= 1<<0,
-		MacClockReady	= 1<<0,
-		InitDone	= 1<<2,
-		MacAccessReq	= 1<<3,
-		NicSleep	= 1<<4,
-		RfKill		= 1<<27,
-
-	Gio		= 0x03c,
-		EnaL0S		= 1<<1,
-
-	GpDrv	= 0x050,
-		GpDrvCalV6	= 1<<2,
-		GpDrv1X2	= 1<<3,
-		GpDrvRadioIqInvert	= 1<<7, 
-
-	Led		= 0x094,
-		LedBsmCtrl	= 1<<5,
-		LedOn		= 0x38,
-		LedOff		= 0x78,
-
-	UcodeGp1Clr	= 0x05c,
-		UcodeGp1RfKill		= 1<<1,
-		UcodeGp1CmdBlocked	= 1<<2,
-		UcodeGp1CtempStopRf	= 1<<3,
-
-	ShadowRegCtrl	= 0x0a8,
-
-	MboxSet	= 0x088,
-		MboxSetOsAlive	= 1<<5,
-
-	Giochicken	= 0x100,
-		L1AnoL0Srx	= 1<<23,
-		DisL0Stimer	= 1<<29,
-
-	AnaPll		= 0x20c,
-
-	Dbghpetmem	= 0x240,
-	Dbglinkpwrmgmt	= 0x250,
-
-	MemRaddr	= 0x40c,
-	MemWaddr	= 0x410,
-	MemWdata	= 0x418,
-	MemRdata	= 0x41c,
-
-	PrphWaddr	= 0x444,
-	PrphRaddr	= 0x448,
-	PrphWdata	= 0x44c,
-	PrphRdata	= 0x450,
-
-	HbusTargWptr	= 0x460,
-
-	UcodeLoadStatus	= 0x1af0,
-};
-
-/*
- * Flow-Handler registers.
- */
-enum {
-	FhTfbdCtrl0	= 0x1900,	// +q*8
-	FhTfbdCtrl1	= 0x1904,	// +q*8
-
-	FhKwAddr	= 0x197c,
-
-	FhSramAddr	= 0x19a4,	// +q*4
-
-	FhCbbcQueue0	= 0x19d0,	// +q*4
-	FhCbbcQueue16	= 0x1bf0,	// +q*4
-	FhCbbcQueue20	= 0x1b20,	// +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,
-
-	FhRxQ0Wptr	= 0x1c80,	// +q*4 (9000 mqrx)
-
-	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,
-	FhTxErrors	= 0x1eb8,
-};
-
-/*
- * 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,
-
-	/* 8000 family */
-	ReleaseCpuReset	= 0x300c,
-		CpuResetBit = 0x1000000,
-
-	LmpmChick	= 0xa01ff8,
-		ExtAddr = 1,
-
-	SbCpu1Status	= 0xa01e30,
-	SbCpu2Status	= 0xa01e34,
-	OscClk		= 0xa04068,
-		OscClkCtrl	= 1<<3,
-	UregChick	= 0xa05c00,
-		UregChickMsiEnable	= 1<<24,
-
-	FhUcodeLoadStatus=0xa05c40,
-};
-
-/*
- * RX ring for mqrx 9000
-*/
-enum {
-	RfhQ0FreeBase	= 0xa08000,	// +q*8
-	RfhQ0FreeWptr	= 0xa08080,	// +q*4
-	RfhQ0FreeRptr	= 0xa080c0,	// +q*4
-
-	RfhQ0UsedBase	= 0xa08100,	// +q*8
-	RfhQ0UsedWptr	= 0xa08180,	// +q*4
-
-	RfhQ0SttsBase	= 0xa08200,	// +q*8
-
-	RfhGenCfg	= 0xa09800,
-		RfhGenServiceDmaSnoop	= 1<<0,
-		RfhGenRfhDmaSnoop	= 1<<1,
-		RfhGenRbChunkSize64	= 0<<4,
-		RfhGenRbChunkSize128	= 1<<4,
-
-	RfhGenStatus	= 0xa09808,
-		RfhGenStatusDmaIdle	= 1<<31,
-
-	RfhRxqActive	= 0xa0980c,
-
-	RfhDmaCfg	= 0xa09820,
-		RfhDma1KSizeShift	= 16,
-		RfhDmaNrbdShift		= 20,
-		RfhDmaMinRbSizeShift	= 24,
-		RfhDmaDropTooLarge	= 1<<26,
-		RfhDmaEnable		= 1<<31,
-};
-
-/*
- * TX scheduler registers.
- */
-enum {
-	SchedBase		= 0xa02c00,
-	SchedSramAddr		= SchedBase,
-
-	SchedDramAddr4965	= SchedBase+0x010,
-	SchedTxFact4965		= SchedBase+0x01c,
-	SchedQueueRdptr4965	= SchedBase+0x064,	// +q*4
-	SchedQChainSel4965	= SchedBase+0x0d0,
-	SchedIntrMask4965	= SchedBase+0x0e4,
-	SchedQueueStatus4965	= SchedBase+0x104,	// +q*4
-
-	SchedDramAddr		= SchedBase+0x008,
-	SchedTxFact		= SchedBase+0x010,
-	SchedQueueWrptr		= SchedBase+0x018,	// +q*4
-	SchedQueueRdptr		= SchedBase+0x068,	// +q*4
-	SchedQChainSel		= SchedBase+0x0e8,
-	SchedIntrMask		= SchedBase+0x108,
-
-	SchedQueueStatus	= SchedBase+0x10c,	// +q*4
-
-	SchedGpCtrl		= SchedBase+0x1a8,
-		Enable31Queues	= 1<<0,
-		AutoActiveMode	= 1<<18,
-
-	SchedChainExtEn		= SchedBase+0x244,
-	SchedAggrSel		= SchedBase+0x248,
-	SchedEnCtrl		= SchedBase+0x254,
-
-	SchedQueueRdptr20	= SchedBase+0x2b4,	// +q*4
-	SchedQueueStatus20	= SchedBase+0x334,	// +q*4
-};
-
-enum {
-	SchedCtxOff4965		= 0x380,
-	SchedCtxLen4965		= 416,
-
-	SchedCtxOff		= 0x600,		// +q*8
-
-	SchedSttsOff		= 0x6A0,		// +q*16
-
-	SchedTransTblOff	= 0x7E0,		// +q*2
-};
-
-/*
- * uCode TLV api
- */
-enum {
-	/* api[0] */
-	UcodeApiSta	= 1<<30,
-};
-
-/*
- * uCode capabilities
- */
-enum {
-	/* capa[0] */
-	UcodeCapLar	= 1<<1,
-
-	/* capa[1] */
-	UcodeCapQuota	= 1<<12,
-	
-	/* capa[2] */
-	UcodeCapLar2	= 1<<9,
-};
-
-enum {
-	FilterPromisc		= 1<<0,
-	FilterCtl		= 1<<1,
-	FilterMulticast		= 1<<2,
-	FilterNoDecrypt		= 1<<3,
-	FilterNoDecryptMcast	= 1<<4,
-	FilterBSS		= 1<<5,
-	FilterBeacon		= 1<<6,
-};
-
-enum {
-	RFlag24Ghz		= 1<<0,
-	RFlagCCK		= 1<<1,
-	RFlagAuto		= 1<<2,
-	RFlagShSlot		= 1<<4,
-	RFlagShPreamble		= 1<<5,
-	RFlagNoDiversity	= 1<<7,
-	RFlagAntennaA		= 1<<8,
-	RFlagAntennaB		= 1<<9,
-	RFlagTSF		= 1<<15,
-	RFlagCTSToSelf		= 1<<30,
-};
-
-enum {
-	TFlagNeedProtection	= 1<<0,
-		TFlagNeedRTS		= 1<<1,
-		TFlagNeedCTS		= 1<<2,
-	TFlagNeedACK		= 1<<3,
-	TFlagLinkq		= 1<<4,
-	TFlagImmBa		= 1<<6,
-	TFlagFullTxOp		= 1<<7,
-	TFlagBtDis		= 1<<12,
-	TFlagAutoSeq		= 1<<13,
-	TFlagMoreFrag		= 1<<14,
-	TFlagInsertTs		= 1<<16,
-	TFlagNeedPadding	= 1<<20,
-};
-
-enum {
-	CmdAdd = 1,
-	CmdModify,
-	CmdRemove,
-};
-
-typedef struct FWInfo FWInfo;
-typedef struct FWImage FWImage;
-typedef struct FWSect FWSect;
-typedef struct FWBlock FWBlock;
-typedef struct FWMem FWMem;
-
-typedef struct TXQ TXQ;
-typedef struct RXQ RXQ;
-
-typedef struct Station Station;
-
-typedef struct Ctlr Ctlr;
-
-struct FWSect
-{
-	uchar	*data;
-	uint	addr;
-	uint	size;
-};
-
-struct FWImage
-{
-	struct {
-		int	nsect;
-		union {
-			struct {
-				FWSect	text;
-				FWSect	data;
-			};
-			FWSect	sect[16];
-		};
-		struct {
-			u32int	flowmask;
-			u32int	eventmask;
-		} defcalib;
-	} init, main;
-
-	struct {
-		FWSect	text;
-	} boot;
-
-	uint	rev;
-	uint	build;
-	char	descr[64+1];
-
-	u32int	flags;
-	u32int	capa[4];
-	u32int	api[4];
-
-	u32int	physku;
-
-	u32int	pagedmemsize;
-
-	uchar	data[];
-};
-
-struct FWInfo
-{
-	int	valid;
-
-	u16int	status;
-	u16int	flags;
-
-	u32int	major;
-	u32int	minor;
-	uchar	type;
-	uchar	subtype;
-
-	u32int	scdptr;
-	u32int	regptr;
-	u32int	logptr;
-	u32int	errptr;
-	u32int	tstamp;
-
-	struct {
-		u32int	major;
-		u32int	minor;
-		u32int	errptr;
-		u32int	logptr;
-	} umac;
-};
-
-struct FWBlock
-{
-	uint	size;
-	uchar	*p;
-};
-
-struct FWMem
-{
-	uchar	*css;
-
-	uint	npage;
-	uint	nblock;
-
-	FWBlock	block[32];
-};
-
-struct TXQ
-{
-	uint	n;
-	uint	i;
-	Block	**b;
-	uchar	*d;
-	uchar	*c;
-
-	uint	lastcmd;
-
-	Rendez;
-	QLock;
-};
-
-struct RXQ
-{
-	uint	i;
-	Block	**b;
-	uchar	*s;
-	uchar	*p;
-	uchar	*u;
-};
-
-struct Station
-{
-	int	id;
-};
-
-struct Ctlr {
-	Lock;
-	QLock;
-
-	Ctlr *link;
-	uvlong port;
-	Pcidev *pdev;
-	Ether *edev;
-	Wifi *wifi;
-
-	char *fwname;
-	int mqrx;
-	int family;
-	int type;
-	uint step;
-	uint dash;
-
-	int power;
-	int broken;
-	int attached;
-
-	u32int ie;
-
-	u32int *nic;
-	uchar *kwpage;
-
-	/* assigned sta ids in hardware sta table or -1 if unassigned */
-	Station bcast;
-	Station bss;
-
-	u32int rxflags;
-	u32int rxfilter;
-
-	int phyid;
-	int macid;
-	int bindid;
-
-	/* current receiver settings */
-	uchar bssid[Eaddrlen];
-	int channel;
-	int prom;
-	int aid;
-
-	struct {
-		Rendez;
-		int id;
-		int active;
-	} te;
-
-	uvlong systime;
-
-	RXQ rx;
-	TXQ tx[7];
-
-	int ndma;
-	int ntxq;
-
-	struct {
-		Rendez;
-		u32int	m;
-		u32int	w;
-	} wait;
-
-	struct {
-		uchar	type;
-		uchar	step;
-		uchar	dash;
-		uchar	pnum;
-		uchar	txantmask;
-		uchar	rxantmask;
-	} rfcfg;
-
-	struct {
-		int	otp;
-		uint	off;
-
-		uchar	version;
-		uchar	type;
-		u16int	volt;
-		u16int	temp;
-		u16int	rawtemp;
-
-		char	regdom[4+1];
-
-		u32int	crystal;
-	} eeprom;
-
-	struct {
-		u32int	version;
-
-		void	*buf;
-		int	len;
-
-		int	off;
-		int	ret;
-		int	type;
-		int	sts;
-	} nvm;
-
-	struct {
-		union {
-			Block	*cmd[21];
-			struct {
-				Block *cfg;
-				Block *nch;
-				Block *papd[9];
-				Block *txp[9];
-			};
-		};
-		int	done;
-	} calib;
-
-	struct {
-		u32int	base;
-		uchar	*s;
-	} sched;
-
-	FWInfo fwinfo;
-	FWImage *fw;
-
-	FWMem fwmem;
-};
-
-/* controller types */
-enum {
-	Type4965	= 0,
-	Type5300	= 2,
-	Type5350	= 3,
-	Type5150	= 4,
-	Type5100	= 5,
-	Type1000	= 6,
-	Type6000	= 7,
-	Type6050	= 8,
-	Type6005	= 11,	/* also Centrino Advanced-N 6030, 6235 */
-	Type2030	= 12,
-	Type2000	= 16,
-
-	Type7260	= 20,
-};
-
-static struct ratetab {
-	uchar	rate;
-	uchar	plcp;
-	uchar	flags;
-} ratetab[] = {
-	{   2,  10, RFlagCCK },
-	{   4,  20, RFlagCCK },
-	{  11,  55, RFlagCCK },
-	{  22, 110, RFlagCCK },
-
-	{  12, 0xd, 0 },
-	{  18, 0xf, 0 },
-	{  24, 0x5, 0 },
-	{  36, 0x7, 0 },
-	{  48, 0x9, 0 },
-	{  72, 0xb, 0 },
-	{  96, 0x1, 0 },
-	{ 108, 0x3, 0 },
-	{ 120, 0x3, 0 }
-};
-
-static uchar iwlrates[] = {
-	0x80 | 2,
-	0x80 | 4,
-	0x80 | 11,
-	0x80 | 22,
-
-	0x80 | 12,
-	0x80 | 18,
-	0x80 | 24,
-	0x80 | 36,
-	0x80 | 48,
-	0x80 | 72,
-	0x80 | 96,
-	0x80 | 108,
-	0x80 | 120,
-	0
-};
-
-static char *fwname[32] = {
-	[Type4965] "iwn-4965",
-	[Type5300] "iwn-5000",
-	[Type5350] "iwn-5000",
-	[Type5150] "iwn-5150",
-	[Type5100] "iwn-5000",
-	[Type1000] "iwn-1000",
-	[Type6000] "iwn-6000",
-	[Type6050] "iwn-6050",
-	[Type6005] "iwn-6005", /* see in iwlattach() below */
-	[Type2030] "iwn-2030",
-	[Type2000] "iwn-2000",
-};
-
-static char *qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block);
-static char *flushq(Ctlr *ctlr, uint qid);
-static char *cmd(Ctlr *ctlr, uint code, uchar *data, int size);
-
-#define csr32r(c, r)	(*((c)->nic+((r)/4)))
-#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))
-
-static uint
-get16(uchar *p){
-	return *((u16int*)p);
-}
-static uint
-get32(uchar *p){
-	return *((u32int*)p);
-}
-static void
-put32(uchar *p, uint v){
-	*((u32int*)p) = v;
-}
-static void
-put64(uchar *p, uvlong v)
-{
-	*((u64int*)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);
-	if(ctlr->family >= 8000)
-		microdelay(2);
-	for(i=0; i<1500; i++){
-		if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna)
-			return 0;
-		microdelay(10);
-	}
-	return "niclock: timeout";
-}
-
-static void
-nicunlock(Ctlr *ctlr)
-{
-	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq);
-}
-
-static u32int
-prphread(Ctlr *ctlr, uint off)
-{
-	off &= 0xfffff;
-	csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off);
-	coherence();
-	return csr32r(ctlr, PrphRdata);
-}
-static void
-prphwrite(Ctlr *ctlr, uint off, u32int data)
-{
-	off &= 0xfffff;
-	csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off);
-	coherence();
-	csr32w(ctlr, PrphWdata, data);
-}
-
-static void
-prphwrite64(Ctlr *ctlr, uint off, u64int data)
-{
-	prphwrite(ctlr, off, data & 0xFFFFFFFF);
-	prphwrite(ctlr, off+4, data >> 32);
-}
-
-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;
-
-	i = &ctlr->fwinfo;
-	switch(len){
-	case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4:
-	case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4 + 4+4 + 1+1 + 2 + 4+4:
-		i->status = get16(d); d += 2;
-		i->flags = get16(d); d += 2;
-
-		i->minor = *d++;
-		i->major = *d++;
-		d += 2;					// id
-		d++;					// api minor
-		d++;					// api major
-		i->subtype = *d++;
-		i->type = *d++;
-		d++;					// mac
-		d++;					// opt
-		d += 2;					// reserved2
-
-		i->tstamp = get32(d); d += 4;
-		i->errptr = get32(d); d += 4;
-		i->logptr = get32(d); d += 4;
-		i->regptr = get32(d); d += 4;
-		d += 4;					// dbgm_config_ptr
-		d += 4;					// alive counter ptr
-
-		i->scdptr = get32(d); d += 4;
-
-		if(len < 1+1+2+1+1+1+1+1+1+2+4+4+4+4+4 + 4+4+4+1+1+2+4+4)
-			break;
-
-		d += 4;					// fwrd addr
-		d += 4;					// fwrd size
-
-		i->umac.minor = *d++;
-		i->umac.major = *d++;
-		d++;					// id
-		d += 2;
-		i->umac.errptr = get32(d); d += 4;
-		i->umac.logptr = get32(d); d += 4;
-
-		i->valid = (i->status == 0xcafe);
-		break;
-
-	case 2+2 + 4+4 + 1+1 + 1+1 + 4+4+4+4+4+4 + 4 + 4+4 + 4+4+4+4:
-		i->status = get16(d); d += 2;
-		i->flags = get16(d); d += 2;
-
-		i->minor = get32(d);
-		d += 4;
-		i->major = get32(d);
-		d += 4;
-		i->subtype = *d++;
-		i->type = *d++;
-		d++;					// mac
-		d++;					// opt
-
-		i->tstamp = get32(d); d += 4;
-		i->errptr = get32(d); d += 4;
-		i->logptr = get32(d); d += 4;
-		i->regptr = get32(d); d += 4;
-		d += 4;					// dbgm_config_ptr
-		d += 4;					// alive counter ptr
-
-		i->scdptr = get32(d);
-		d += 4;
-
-		d += 4;					// fwrd addr
-		d += 4;					// fwrd size
-
-		i->umac.minor = get32(d); d += 4;
-		i->umac.major = get32(d); d += 4;
-		i->umac.errptr = get32(d); d += 4;
-		i->umac.logptr = get32(d); d += 4;
-
-		i->valid = (i->status == 0xcafe);
-		break;
-	
-	default:
-		if(len < 32)
-			break;
-		i->minor = *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 = 1;
-	}
-	USED(d);
-}
-
-static void
-printfwinfo(Ctlr *ctlr)
-{
-	FWInfo *i = &ctlr->fwinfo;
-
-	print("fwinfo: status=%.4ux flags=%.4ux\n",
-		i->status, i->flags);
-
-	print("fwinfo: ver %ud.%ud type %ud.%ud\n",
-		i->major, i->minor, i->type, i->subtype);
-	print("fwinfo: scdptr=%.8ux\n", i->scdptr);
-	print("fwinfo: regptr=%.8ux\n", i->regptr);
-	print("fwinfo: logptr=%.8ux\n", i->logptr);
-	print("fwinfo: errptr=%.8ux\n", i->errptr);
-
-	print("fwinfo: ts=%.8ux\n", i->tstamp);
-
-	print("fwinfo: umac ver %ud.%ud\n", i->umac.major, i->umac.minor);
-	print("fwinfo: umac errptr %.8ux\n", i->umac.errptr);
-	print("fwinfo: umac logptr %.8ux\n", i->umac.logptr);
-}
-
-static void
-dumpctlr(Ctlr *ctlr)
-{
-	u32int dump[13];
-	int i;
-
-	print("lastcmd: %ud (0x%ux)\n", ctlr->tx[4].lastcmd,  ctlr->tx[4].lastcmd);
-
-	if(!ctlr->fwinfo.valid || 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);
-
-	if(ctlr->family >= 7000){
-		print(	"error:\tid %ux, trm_hw_status %.8ux %.8ux,\n"
-			"\tbranchlink2 %.8ux, interruptlink %.8ux %.8ux,\n"
-			"\terrordata %.8ux %.8ux %.8ux\n",
-			dump[1], dump[2], dump[3],
-			dump[4], dump[5], dump[6],
-			dump[7], dump[8], dump[9]);
-	} else {
-		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;
-			microdelay(10);
-		}
-	}
-	return "eepromlock: timeout";
-}
-static void
-eepromunlock(Ctlr *ctlr)
-{
-	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked);
-}
-static char*
-eepromread(Ctlr *ctlr, void *data, int count, uint off)
-{
-	uchar *out = data;
-	u32int w, s;
-	int i;
-
-	w = 0;
-	off += ctlr->eeprom.off;
-	for(; count > 0; count -= 2, off++){
-		csr32w(ctlr, EepromIo, off << 2);
-		for(i=0; i<10; i++){
-			w = csr32r(ctlr, EepromIo);
-			if(w & 1)
-				break;
-			microdelay(5);
-		}
-		if(i == 10)
-			return "eepromread: timeout";
-		if(ctlr->eeprom.otp){
-			s = csr32r(ctlr, OtpromGp);
-			if(s & EccUncorrStts)
-				return "eepromread: otprom ecc error";
-			if(s & EccCorrStts)
-				csr32w(ctlr, OtpromGp, s);
-		}
-		*out++ = w >> 16;
-		if(count > 1)
-			*out++ = w >> 24;
-	}
-	return 0;
-}
-
-static char*
-handover(Ctlr *ctlr)
-{
-	int i, j;
-
-	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
-	for(i=0; i<5; i++){
-		if(csr32r(ctlr, Cfg) & NicReady)
-			goto Ready;
-		microdelay(10);
-	}
-
-	if(ctlr->family >= 7000){
-		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
-		delay(1);
-	}
-
-	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare);
-	for(i=0; i<750; i++){
-		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
-		for(j=0; j<5; j++){
-			if(csr32r(ctlr, Cfg) & NicReady)
-				goto Ready;
-			microdelay(10);
-		}
-		microdelay(200);
-	}
-
-	return "handover: timeout";
-Ready:
-	if(ctlr->family >= 7000)
-		csr32w(ctlr, MboxSet, csr32r(ctlr, MboxSet) | MboxSetOsAlive);
-	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 0;
-		microdelay(10);
-	}
-	return "clockwait: timeout";
-}
-
-static char*
-poweron(Ctlr *ctlr)
-{
-	int capoff;
-	char *err;
-
-	if(ctlr->family >= 7000){
-		/* Reset entire device */
-		csr32w(ctlr, Reset, (1<<7));
-		delay(5);
-	}
-
-	if(ctlr->family < 8000){
-		/* 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->family < 7000){
-		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(ctlr->mqrx){
-		/* Newer cards default to MSIX? */
-		if((err = niclock(ctlr)) != nil)
-			return err;
-		prphwrite(ctlr, UregChick, UregChickMsiEnable);
-		nicunlock(ctlr);
-	}
-
-	/* Enable the oscillator to count wake up time for L1 exit. (weird W/A) */
-	if(ctlr->type == Type7260){
-		if((err = niclock(ctlr)) != nil)
-			return err;
-
-		prphread(ctlr, OscClk);
-		prphread(ctlr, OscClk);
-		microdelay(20);
-
-		prphwrite(ctlr, OscClk, prphread(ctlr, OscClk) | OscClkCtrl);
-
-		prphread(ctlr, OscClk);
-		prphread(ctlr, OscClk);
-
-		nicunlock(ctlr);
-	}
-
-	if(ctlr->family < 8000){
-		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);
-		microdelay(20);
-
-		/* Disable L1-Active. */
-		prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));
-
-		nicunlock(ctlr);
-	}
-
-	ctlr->power = 1;
-
-	return 0;
-}
-
-static void
-poweroff(Ctlr *ctlr)
-{
-	int i, j;
-
-	csr32w(ctlr, Reset, 1);
-
-	/* Disable interrupts */
-	ctlr->ie = 0;
-	csr32w(ctlr, Imr, 0);
-	csr32w(ctlr, Isr, ~0);
-	csr32w(ctlr, FhIsr, ~0);
-
-	/* Stop scheduler */
-	if(ctlr->family >= 7000 || ctlr->type != Type4965)
-		prphwrite(ctlr, SchedTxFact, 0);
-	else
-		prphwrite(ctlr, SchedTxFact4965, 0);
-
-	/* Stop TX ring */
-	if(niclock(ctlr) == nil){
-		for(i = 0; i < ctlr->ndma; i++){
-			csr32w(ctlr, FhTxConfig + i*32, 0);
-			for(j = 0; j < 200; j++){
-				if(csr32r(ctlr, FhTxStatus) & (0x10000<<i))
-					break;
-				microdelay(20);
-			}
-		}
-		nicunlock(ctlr);
-	}
-
-	/* Stop RX ring */
-	if(niclock(ctlr) == nil){
-		if(ctlr->mqrx){
-			prphwrite(ctlr, RfhDmaCfg, 0);
-			for(j = 0; j < 1000; j++){
-				if(prphread(ctlr, RfhGenStatus) & RfhGenStatusDmaIdle)
-					break;
-				microdelay(10);
-			}
-		} else {
-			csr32w(ctlr, FhRxConfig, 0);
-			for(j = 0; j < 1000; j++){
-				if(csr32r(ctlr, FhRxStatus) & 0x1000000)
-					break;
-				microdelay(10);
-			}
-		}
-		nicunlock(ctlr);
-	}
-
-	if(ctlr->family <= 7000){
-		/* Disable DMA */
-		if(niclock(ctlr) == nil){
-			prphwrite(ctlr, ApmgClkDis, DmaClkRqt);
-			nicunlock(ctlr);
-		}
-		microdelay(5);
-	}
-
-	if(ctlr->family >= 7000){
-		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
-		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare|EnablePme);
-		delay(1);
-		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) & ~(1<<31));
-		delay(5);
-	}
-
-	/* Stop busmaster DMA activity. */
-	csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<9));
-	for(j = 0; j < 100; j++){
-		if(csr32r(ctlr, Reset) & (1<<8))
-			break;
-		microdelay(10);
-	}
-
-	/* Reset the entire device. */
-	csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<7));
-	delay(5);
-
-	/* Clear "initialization complete" bit. */
-	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~InitDone);
-
-	ctlr->power = 0;
-}
-
-static char*
-rominit(Ctlr *ctlr)
-{
-	uint prev, last;
-	uchar buf[2];
-	char *err;
-	int i;
-
-	ctlr->eeprom.otp = 0;
-	ctlr->eeprom.off = 0;
-	if(ctlr->type < Type1000 || (csr32r(ctlr, OtpromGp) & DevSelOtp) == 0)
-		return nil;
-
-	/* Wait for clock stabilization before accessing prph. */
-	if((err = clockwait(ctlr)) != nil)
-		return err;
-
-	if((err = niclock(ctlr)) != nil)
-		return err;
-	prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | ResetReq);
-	microdelay(5);
-	prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) & ~ResetReq);
-	nicunlock(ctlr);
-
-	/* Set auto clock gate disable bit for HW with OTP shadow RAM. */
-	if(ctlr->type != Type1000)
-		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
-
-	csr32w(ctlr, EepromGp, csr32r(ctlr, EepromGp) & ~0x00000180);
-
-	/* Clear ECC status. */
-	csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) | (EccCorrStts | EccUncorrStts));
-
-	ctlr->eeprom.otp = 1;
-	if(ctlr->type != Type1000)
-		return nil;
-
-	/* Switch to absolute addressing mode. */
-	csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) & ~RelativeAccess);
-
-	/*
-	 * Find the block before last block (contains the EEPROM image)
-	 * for HW without OTP shadow RAM.
-	 */
-	prev = last = 0;
-	for(i=0; i<3; i++){
-		if((err = eepromread(ctlr, buf, 2, last)) != nil)
-			return err;
-		if(get16(buf) == 0)
-			break;
-		prev = last;
-		last = get16(buf);
-	}
-	if(i == 0 || i >= 3)
-		return "rominit: missing eeprom image";
-
-	ctlr->eeprom.off = prev+1;
-	return nil;
-}
-
-static int
-iwlinit(Ether *edev)
-{
-	Ctlr *ctlr;
-	char *err;
-	uchar b[4];
-	uint u, caloff, regoff;
-
-	ctlr = edev->ctlr;
-
-	/* Clear device-specific "PCI retry timeout" register (41h). */
-	if(pcicfgr8(ctlr->pdev, 0x41) != 0)
-		pcicfgw8(ctlr->pdev, 0x41, 0);
-
-	/* Clear interrupt disable bit. Hardware bug workaround. */
-	if(ctlr->pdev->pcr & 0x400){
-		ctlr->pdev->pcr &= ~0x400;
-		pcicfgw16(ctlr->pdev, PciPCR, ctlr->pdev->pcr);
-	}
-
-	ctlr->type = csr32r(ctlr, Rev);
-	if(ctlr->family >= 8000){
-		ctlr->type &= 0xFFFF;
-		ctlr->dash = 0;
-		ctlr->step = ctlr->type & 15, ctlr->type >>= 4;
-	} else {
-		ctlr->type &= 0x1FF;
-		ctlr->dash = ctlr->type & 3, ctlr->type >>= 2;
-		ctlr->step = ctlr->type & 3, ctlr->type >>= 2;
-		if(ctlr->fwname == nil && fwname[ctlr->type] == nil){
-			print("iwl: unsupported controller type %d\n", ctlr->type);
-			return -1;
-		}
-	}
-
-	if((err = handover(ctlr)) != nil)
-		goto Err;
-
-	/* >= 7000 family needs firmware loaded to access NVM */
-	if(ctlr->family >= 7000)
-		return 0;
-
-	if((err = poweron(ctlr)) != nil)
-		goto Err;
-
-	if((csr32r(ctlr, EepromGp) & 0x7) == 0){
-		err = "bad rom signature";
-		goto Err;
-	}
-	if((err = eepromlock(ctlr)) != nil)
-		goto Err;
-	if((err = rominit(ctlr)) != nil)
-		goto Err2;
-	if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){
-		eepromunlock(ctlr);
-		goto Err;
-	}
-	if((err = eepromread(ctlr, b, 2, 0x048)) != nil){
-	Err2:
-		eepromunlock(ctlr);
-		goto Err;
-	}
-	u = get16(b);
-	ctlr->rfcfg.type = u & 3;	u >>= 2;
-	ctlr->rfcfg.step = u & 3;	u >>= 2;
-	ctlr->rfcfg.dash = u & 3;	u >>= 4;
-	ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
-	ctlr->rfcfg.rxantmask = u & 15;
-	if((err = eepromread(ctlr, b, 2, 0x66)) != nil)
-		goto Err2;
-	regoff = get16(b);
-	if((err = eepromread(ctlr, b, 4, regoff+1)) != nil)
-		goto Err2;
-	strncpy(ctlr->eeprom.regdom, (char*)b, 4);
-	ctlr->eeprom.regdom[4] = 0;
-	if((err = eepromread(ctlr, b, 2, 0x67)) != nil)
-		goto Err2;
-	caloff = get16(b);
-	if((err = eepromread(ctlr, b, 4, caloff)) != nil)
-		goto Err2;
-	ctlr->eeprom.version = b[0];
-	ctlr->eeprom.type = b[1];
-	ctlr->eeprom.volt = get16(b+2);
-
-	ctlr->eeprom.temp = 0;
-	ctlr->eeprom.rawtemp = 0;
-	if(ctlr->type == Type2030 || ctlr->type == Type2000){
-		if((err = eepromread(ctlr, b, 2, caloff + 0x12a)) != nil)
-			goto Err2;
-		ctlr->eeprom.temp = get16(b);
-		if((err = eepromread(ctlr, b, 2, caloff + 0x12b)) != nil)
-			goto Err2;
-		ctlr->eeprom.rawtemp = get16(b);
-	}
-
-	if(ctlr->type != Type4965 && ctlr->type != Type5150){
-		if((err = eepromread(ctlr, b, 4, caloff + 0x128)) != nil)
-			goto Err2;
-		ctlr->eeprom.crystal = get32(b);
-	}
-	eepromunlock(ctlr);
-
-	switch(ctlr->type){
-	case Type4965:
-		ctlr->rfcfg.txantmask = 3;
-		ctlr->rfcfg.rxantmask = 7;
-		break;
-	case Type5100:
-		ctlr->rfcfg.txantmask = 2;
-		ctlr->rfcfg.rxantmask = 3;
-		break;
-	case Type6000:
-		if(ctlr->pdev->did == 0x422c || ctlr->pdev->did == 0x4230){
-			ctlr->rfcfg.txantmask = 6;
-			ctlr->rfcfg.rxantmask = 6;
-		}
-		break;
-	}
-	poweroff(ctlr);
-	return 0;
-Err:
-	print("iwlinit: %s\n", err);
-	poweroff(ctlr);
-	return -1;
-}
-
-static char*
-crackfw(FWImage *i, uchar *data, uint size, int alt)
-{
-	uchar *p, *e;
-	FWSect *s;
-	uint t, l;
-
-	memset(i, 0, sizeof(*i));
-	if(size < 4){
-Tooshort:
-		return "firmware image too short";
-	}
-	p = data;
-	e = p + size;
-	i->rev = get32(p); p += 4;
-	if(i->rev == 0){
-		uvlong altmask;
-
-		if(size < (4+64+4+4+8))
-			goto Tooshort;
-		if(memcmp(p, "IWL\n", 4) != 0)
-			return "bad firmware signature";
-		p += 4;
-		strncpy(i->descr, (char*)p, 64);
-		i->descr[64] = 0;
-		p += 64;
-		i->rev = get32(p); p += 4;
-		i->build = get32(p); p += 4;
-		altmask = get32(p); p += 4;
-		altmask |= (uvlong)get32(p) << 32; p += 4;
-		while(alt > 0 && (altmask & (1ULL<<alt)) == 0)
-			alt--;
-		for(;p < e; p += (l + 3) & ~3){
-			if(p + 8 > e)
-				goto Tooshort;
-
-			t = get32(p), p += 4;
-			l = get32(p), p += 4;
-			if(p + l > e)
-				goto Tooshort;
-
-			if((t >> 16) != 0 && (t >> 16) != alt)
-				continue;
-
-			switch(t & 0xFFFF){
-			case 1:
-				s = &i->main.text;
-				if(i->main.nsect < 1)
-					i->main.nsect = 1;
-				s->addr = 0x00000000;
-				goto Sect;
-			case 2:
-				s = &i->main.data;
-				if(i->main.nsect < 2)
-					i->main.nsect = 2;
-				s->addr = 0x00800000;
-				goto Sect;
-			case 3:
-				s = &i->init.text;
-				if(i->init.nsect < 1)
-					i->init.nsect = 1;
-				s->addr = 0x00000000;
-				goto Sect;
-			case 4:
-				s = &i->init.data;
-				if(i->init.nsect < 2)
-					i->init.nsect = 2;
-				s->addr = 0x00800000;
-				goto Sect;
-			case 5:
-				s = &i->boot.text;
-				s->addr = 0x00000000;
-				goto Sect;
-			case 18:
-				if(l < 4)
-					goto Tooshort;
-				i->flags = get32(p);
-				break;
-			case 19:
-				if(i->main.nsect >= nelem(i->main.sect))
-					return "too many main sections";
-				s = &i->main.sect[i->main.nsect++];
-				goto Chunk;
-			case 20:
-				if(i->init.nsect >= nelem(i->init.sect))
-					return "too many init sections";
-				s = &i->init.sect[i->init.nsect++];
-			Chunk:
-				if(l < 4)
-					goto Tooshort;
-				s->addr = get32(p);
-				p += 4, l -= 4;
-			Sect:
-				s->size = l;
-				s->data = p;
-				break;
-			case 22:
-				if(l < 12)
-					goto Tooshort;
-				switch(get32(p)){
-				case 0:
-					i->main.defcalib.flowmask = get32(p+4);
-					i->main.defcalib.eventmask = get32(p+8);
-					break;
-				case 1:
-					i->init.defcalib.flowmask = get32(p+4);
-					i->init.defcalib.eventmask = get32(p+8);
-					break;
-				}
-				break;
-			case 23:
-				if(l < 4)
-					goto Tooshort;
-				i->physku = get32(p);
-				break;
-			case 29:
-				if(l < 8)
-					goto Tooshort;
-				t = get32(p);
-				if(t >= nelem(i->api))
-					goto Tooshort;
-				i->api[t] = get32(p+4);
-				break;
-			case 30:
-				if(l < 8)
-					goto Tooshort;
-				t = get32(p);
-				if(t >= nelem(i->capa))
-					goto Tooshort;
-				i->capa[t] = get32(p+4);
-				break;
-			case 32:
-				if(l < 4)
-					goto Tooshort;
-				i->pagedmemsize = get32(p) & -FWPagesize;
-				break;
-			}
-		}
-	} 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;
-
-		i->main.nsect = 2;
-		i->init.nsect = 2;
-		i->main.text.addr = 0x00000000;
-		i->main.data.addr = 0x00800000;
-		i->init.text.addr = 0x00000000;
-		i->init.data.addr = 0x00800000;
-	}
-	return 0;
-}
-
-static FWImage*
-readfirmware(char *name)
-{
-	uchar dirbuf[sizeof(Dir)+100], *data;
-	char buf[128], *err;
-	FWImage *fw;
-	int n, r;
-	Chan *c;
-	Dir d;
-
-	if(!iseve())
-		error(Eperm);
-	if(!waserror()){
-		snprint(buf, sizeof buf, "/boot/%s", name);
-		c = namec(buf, Aopen, OREAD, 0);
-		poperror();
-	} else {
-		snprint(buf, sizeof buf, "/lib/firmware/%s", name);
-		c = namec(buf, Aopen, OREAD, 0);
-	}
-	if(waserror()){
-		cclose(c);
-		nexterror();
-	}
-	n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
-	if(n <= 0)
-		error("can't stat firmware");
-	convM2D(dirbuf, n, &d, nil);
-	fw = smalloc(sizeof(*fw) + 16 + d.length);
-	data = (uchar*)(fw+1);
-	if(waserror()){
-		free(fw);
-		nexterror();
-	}
-	r = 0;
-	while(r < d.length){
-		n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
-		if(n <= 0)
-			break;
-		r += n;
-	}
-	if((err = crackfw(fw, data, r, 1)) != nil)
-		error(err);
-	poperror();
-	poperror();
-	cclose(c);
-	return fw;
-}
-
-
-static int
-gotirq(void *arg)
-{
-	Ctlr *ctlr = arg;
-	return (ctlr->wait.m & ctlr->wait.w) != 0;
-}
-
-static u32int
-irqwait(Ctlr *ctlr, u32int mask, int timeout)
-{
-	u32int r;
-
-	ilock(ctlr);
-	r = ctlr->wait.m & mask;
-	if(r == 0){
-		ctlr->wait.w = mask;
-		iunlock(ctlr);
-		if(!waserror()){
-			tsleep(&ctlr->wait, gotirq, ctlr, timeout);
-			poperror();
-		}
-		ilock(ctlr);
-		ctlr->wait.w = 0;
-		r = ctlr->wait.m & mask;
-	}
-	ctlr->wait.m &= ~r;
-	iunlock(ctlr);
-	return r;
-}
-
-static int
-rbplant(Ctlr *ctlr, uint i)
-{
-	Block *b;
-
-	assert(i < Nrx);
-
-	b = iallocb(Rbufsize*2);
-	if(b == nil)
-		return -1;
-	b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, Rbufsize);
-	memset(b->rp, 0, Rdscsize);
-
-	ctlr->rx.b[i] = b;
-
-	if(ctlr->mqrx)
-		put64(ctlr->rx.p + (i<<3), PCIWADDR(b->rp));
-	else
-		put32(ctlr->rx.p + (i<<2), PCIWADDR(b->rp) >> 8);
-
-	return 0;
-}
-
-static char*
-initmem(Ctlr *ctlr)
-{
-	RXQ *rx;
-	TXQ *tx;
-	int i, q;
-
-	if(ctlr->fw->pagedmemsize > 0){
-		ctlr->fwmem.npage = ctlr->fw->pagedmemsize >> FWPageshift;
-		ctlr->fwmem.nblock = ctlr->fwmem.npage >> FWBlockshift;
-		if(ctlr->fwmem.nblock >= nelem(ctlr->fwmem.block)-1)
-			return "paged memory size too big";
-		for(i = 0; i < ctlr->fwmem.nblock; i++)
-			ctlr->fwmem.block[i].size = FWBlocksize;
-		ctlr->fwmem.block[i].size = (ctlr->fwmem.npage % FWBlockpages) << FWPageshift;
-		if(ctlr->fwmem.block[i].size != 0)
-			ctlr->fwmem.nblock++;
-		for(i = 0; i < ctlr->fwmem.nblock; i++){
-			if(ctlr->fwmem.block[i].p == nil){
-				ctlr->fwmem.block[i].p = mallocalign(ctlr->fwmem.block[i].size, FWPagesize, 0, 0);
-				if(ctlr->fwmem.block[i].p == nil)
-					return "no memory for firmware block";
-			}
-		}
-		if(ctlr->fwmem.css == nil){
-			if((ctlr->fwmem.css = mallocalign(FWPagesize, FWPagesize, 0, 0)) == nil)
-				return "no memory for firmware css page";
-		}
-	}
-
-	rx = &ctlr->rx;
-	if(ctlr->mqrx){
-		if(rx->u == nil)
-			rx->u = mallocalign(4 * Nrx, 4096, 0, 0);
-		if(rx->p == nil)
-			rx->p = mallocalign(8 * Nrx, 4096, 0, 0);
-		if(rx->u == nil || rx->p == nil)
-			return "no memory for rx rings";
-		memset(rx->u, 0, 4 * Nrx);
-		memset(rx->p, 0, 8 * Nrx);
-	} else {
-		rx->u = nil;
-		if(rx->p == nil)
-			rx->p = mallocalign(4 * Nrx, 256, 0, 0);
-		if(rx->p == nil)
-			return "no memory for rx rings";
-		memset(rx->p, 0, 4 * Nrx);
-	}
-	if(rx->s == nil)
-		rx->s = mallocalign(Rstatsize, 4096, 0, 0);
-	if(rx->b == nil)
-		rx->b = malloc(sizeof(Block*) * Nrx);
-	if(rx->b == nil || rx->s == nil)
-		return "no memory for rx ring";
-	memset(rx->s, 0, Rstatsize);
-	for(i=0; i<Nrx; i++){
-		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;
-
-	ctlr->ndma = 8;
-	ctlr->ntxq = 20;
-	if(ctlr->family >= 7000) {
-		ctlr->ntxq = 31;
-	} else {
-		if(ctlr->type == Type4965) {
-			ctlr->ndma = 7;
-			ctlr->ntxq = 16;
-		}
-	}
-
-	if(ctlr->sched.s == nil)
-		ctlr->sched.s = mallocalign((256+64)*2 * ctlr->ntxq, 4096, 0, 0);
-	if(ctlr->sched.s == nil)
-		return "no memory for sched buffer";
-	memset(ctlr->sched.s, 0, (256+64)*2 * ctlr->ntxq);
-
-	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, 4096, 0, 0);
-		if(tx->c == nil)
-			tx->c = mallocalign(Tcmdsize * Ntx, 4096, 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){
-				freeblist(tx->b[i]);
-				tx->b[i] = nil;
-			}
-		}
-		tx->i = 0;
-		tx->n = 0;
-		tx->lastcmd = 0;
-	}
-
-	if(ctlr->kwpage == nil)
-		ctlr->kwpage = mallocalign(4096, 4096, 0, 0);
-	if(ctlr->kwpage == nil)
-		return "no memory for kwpage";		
-	memset(ctlr->kwpage, 0, 4096);
-
-	return nil;
-}
-
-static char*
-reset(Ctlr *ctlr)
-{
-	char *err;
-	int q, i;
-
-	if(ctlr->power)
-		poweroff(ctlr);
-	if((err = initmem(ctlr)) != nil)
-		return err;
-	if((err = poweron(ctlr)) != nil)
-		return err;
-
-	if(ctlr->family <= 7000){
-		if((err = niclock(ctlr)) != nil)
-			return err;
-		prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
-		nicunlock(ctlr);
-	}
-
-	if(ctlr->family >= 7000){
-		u32int u;
-
-		u = csr32r(ctlr, Cfg);
-
-		u &= ~(RadioSi|MacSi|CfgMacDashMask|CfgMacStepMask|CfgPhyTypeMask|CfgPhyStepMask|CfgPhyDashMask);
-
-		u |= (ctlr->step << CfgMacStepShift) & CfgMacStepMask;
-		u |= (ctlr->dash << CfgMacDashShift) & CfgMacDashMask;
-
-		u |= ctlr->rfcfg.type << CfgPhyTypeShift;
-		u |= ctlr->rfcfg.step << CfgPhyStepShift;
-		u |= ctlr->rfcfg.dash << CfgPhyDashShift;
-
-		csr32w(ctlr, Cfg, u);
-
-	} else {
-		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi);
-	}
-
-	if(ctlr->family < 8000){
-		if((err = niclock(ctlr)) != nil)
-			return err;
-		if(ctlr->family == 7000 || ctlr->type != Type4965)
-			prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis);
-		nicunlock(ctlr);
-	}
-	if(ctlr->family < 7000){
-		if((err = niclock(ctlr)) != nil)
-			return err;
-		if(ctlr->type == Type1000){
-			/*
-			 * Select first Switching Voltage Regulator (1.32V) to
-			 * solve a stability issue related to noisy DC2DC line
-			 * in the silicon of 1000 Series.
-			 */
-			prphwrite(ctlr, ApmgDigitalSvr, 
-				(prphread(ctlr, ApmgDigitalSvr) & ~(0xf<<5)) | (3<<5));
-		}
-		if((ctlr->type == Type6005 || ctlr->type == Type6050) && ctlr->eeprom.version == 6)
-			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvCalV6);
-		if(ctlr->type == Type6005)
-			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrv1X2);
-		if(ctlr->type == Type2030 || ctlr->type == Type2000)
-			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvRadioIqInvert);
-		nicunlock(ctlr);
-	}
-
-	if((err = niclock(ctlr)) != nil)
-		return err;
-
-	if(ctlr->mqrx){
-		/* Stop RX DMA. */
-		prphwrite(ctlr, RfhDmaCfg, 0);
-		/* Disable RX used and free queue operation. */
-		prphwrite(ctlr, RfhRxqActive, 0);
-
-		prphwrite64(ctlr, RfhQ0SttsBase, PCIWADDR(ctlr->rx.s));
-		prphwrite64(ctlr, RfhQ0FreeBase, PCIWADDR(ctlr->rx.p));
-		prphwrite64(ctlr, RfhQ0UsedBase, PCIWADDR(ctlr->rx.u));
-
-		prphwrite(ctlr, RfhQ0FreeWptr, 0);
-		prphwrite(ctlr, RfhQ0FreeRptr, 0);
-		prphwrite(ctlr, RfhQ0UsedWptr, 0);
-
-		/* Enable RX DMA */
-		prphwrite(ctlr, RfhDmaCfg,
-			RfhDmaEnable |
-			RfhDmaDropTooLarge |
-			((Rbufsize/1024) << RfhDma1KSizeShift) |
-			(3 << RfhDmaMinRbSizeShift) |
-			(Nrxlog << RfhDmaNrbdShift));
-
-		/* Enable RX DMA snooping. */
-		prphwrite(ctlr, RfhGenCfg,
-			RfhGenServiceDmaSnoop |
-			RfhGenRfhDmaSnoop |
-			RfhGenRbChunkSize128);
-
-		/* Enable Q0 */
-		prphwrite(ctlr, RfhRxqActive, (1 << 16) | 1);
-		delay(1);
-
-		csr32w(ctlr, FhRxQ0Wptr, (Nrx-1) & ~7);
-		delay(1);
-	} else {
-		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);
-	}
-
-	for(i = 0; i < ctlr->ndma; i++)
-		csr32w(ctlr, FhTxConfig + i*32, 0);
-
-	if(ctlr->family >= 7000 || ctlr->type != Type4965)
-		prphwrite(ctlr, SchedTxFact, 0);
-	else
-		prphwrite(ctlr, SchedTxFact4965, 0);
-
-	if(ctlr->family >= 7000){
-		prphwrite(ctlr, SchedEnCtrl, 0);
-		prphwrite(ctlr, SchedGpCtrl, prphread(ctlr, SchedGpCtrl)
-			| Enable31Queues*(ctlr->ntxq == 31)
-			| AutoActiveMode);
-		for(q = 0; q < ctlr->ntxq; q++)
-			prphwrite(ctlr, (q<20? SchedQueueStatus: SchedQueueStatus20) + q*4, 1 << 19);
-	}
-
-	csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4);
-	for(q = 0; q < ctlr->ntxq; q++){
-		i = q < nelem(ctlr->tx) ? q : nelem(ctlr->tx)-1;
-		if(q < 16)
-			csr32w(ctlr, FhCbbcQueue0 + q*4, PCIWADDR(ctlr->tx[i].d) >> 8);
-		else if(q < 20)
-			csr32w(ctlr, FhCbbcQueue16 + (q-16)*4, PCIWADDR(ctlr->tx[i].d) >> 8);
-		else
-			csr32w(ctlr, FhCbbcQueue20 + (q-20)*4, PCIWADDR(ctlr->tx[i].d) >> 8);
-	}
-
-	if(ctlr->family >= 7000 || ctlr->type >= Type6000)
-		csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff);
-
-	nicunlock(ctlr);
-
-	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
-	csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked);
-
-	ctlr->systime = 0;
-
-	ctlr->broken = 0;
-	ctlr->wait.m = 0;
-	ctlr->wait.w = 0;
-
-	ctlr->bcast.id = -1;
-	ctlr->bss.id = -1;
-
-	ctlr->phyid = -1;
-	ctlr->macid = -1;
-	ctlr->bindid = -1;
-	ctlr->te.id = -1;
-	ctlr->te.active = 0;
-	ctlr->aid = 0;
-
-	if(ctlr->family >= 9000)
-		csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | 0x4000000);
-
-	ctlr->ie = Idefmask;
-	csr32w(ctlr, Imr, ctlr->ie);
-	csr32w(ctlr, Isr, ~0);
-
-	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
-	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
-	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
-
-	return nil;
-}
-
-static char*
-sendmccupdate(Ctlr *ctlr, char *mcc)
-{
-	uchar c[2+1+1+4+5*4], *p;
-
-	memset(p = c, 0, sizeof(c));
-	*p++ = mcc[1];
-	*p++ = mcc[0];
-	*p++ = 0;
-	*p++ = 0;	// reserved
-	if(ctlr->fw->capa[2] & UcodeCapLar2){
-		p += 4;
-		p += 5*4;
-	}
-	return cmd(ctlr, 200, c, p - c);
-}
-
-static char*
-sendbtcoexadv(Ctlr *ctlr)
-{
-	static u32int btcoex3wire[12] = {
-		0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa,
-		0xcc00ff28, 0x0000aaaa, 0xcc00aaaa, 0x0000aaaa,
-		0xc0004000, 0x00004000, 0xf0005000, 0xf0005000,
-	};
-
-	uchar c[Tcmdsize], *p;
-	char *err;
-	int i;
-
-	/* set BT config */
-	memset(c, 0, sizeof(c));
-	p = c;
-
-	if(ctlr->family >= 7000){
-		put32(p, 3);
-		p += 4;
-		put32(p, (1<<4));
-		p += 4;
-	} else if(ctlr->type == Type2030){
-		*p++ = 145; /* flags */
-		p++; /* lead time */
-		*p++ = 5; /* max kill */
-		*p++ = 1; /* bt3 t7 timer */
-		put32(p, 0xffff0000); /* kill ack */
-		p += 4;
-		put32(p, 0xffff0000); /* kill cts */
-		p += 4;
-		*p++ = 2; /* sample time */
-		*p++ = 0xc; /* bt3 t2 timer */
-		p += 2; /* bt4 reaction */
-		for (i = 0; i < nelem(btcoex3wire); i++){
-			put32(p, btcoex3wire[i]);
-			p += 4;
-		}
-		p += 2; /* bt4 decision */
-		put16(p, 0xff); /* valid */
-		p += 2;
-		put32(p, 0xf0); /* prio boost */
-		p += 4;
-		p++; /* reserved */
-		p++; /* tx prio boost */
-		p += 2; /* rx prio boost */
-	}
-	if((err = cmd(ctlr, 155, c, p-c)) != nil)
-		return err;
-
-	if(ctlr->family >= 7000)
-		return nil;
-
-	/* set BT priority */
-	memset(c, 0, sizeof(c));
-	p = c;
-
-	*p++ = 0x6; /* init1 */
-	*p++ = 0x7; /* init2 */
-	*p++ = 0x2; /* periodic low1 */
-	*p++ = 0x3; /* periodic low2 */
-	*p++ = 0x4; /* periodic high1 */
-	*p++ = 0x5; /* periodic high2 */
-	*p++ = 0x6; /* dtim */
-	*p++ = 0x8; /* scan52 */
-	*p++ = 0xa; /* scan24 */
-	p += 7; /* reserved */
-	if((err = cmd(ctlr, 204, c, p-c)) != nil)
-		return err;
-
-	/* force BT state machine change */
-	memset(c, 0, sizeof(c));
-	p = c;
-
-	*p++ = 1; /* open */
-	*p++ = 1; /* type */
-	p += 2; /* reserved */
-	if((err = cmd(ctlr, 205, c, p-c)) != nil)
-		return err;
-
-	c[0] = 0; /* open */
-	return cmd(ctlr, 205, c, p-c);
-}
-
-static char*
-sendpagingcmd(Ctlr *ctlr)
-{
-	uchar c[3*4 + 4 + 32*4], *p;
-	int i;
-
-	p = c;
-	put32(p, (3<<8) | (ctlr->fwmem.npage % FWBlockpages));
-	p += 4;
-	put32(p, FWPageshift + FWBlockshift);
-	p += 4;
-	put32(p, ctlr->fwmem.nblock);
-	p += 4;
-
-	put32(p, PCIWADDR(ctlr->fwmem.css) >> FWPageshift);
-	p += 4;
-
-	for(i = 0; i < ctlr->fwmem.nblock; i++){
-		put32(p, PCIWADDR(ctlr->fwmem.block[i].p) >> FWPageshift);
-		p += 4;
-	}
-
-	for(; i < 32; i++){
-		put32(p, 0);
-		p += 4;
-	}
-
-	return cmd(ctlr, 79 | (1<<8), c, p-c);
-}
-
-static char*
-enablepaging(Ctlr *ctlr)
-{
-	FWSect *sect;
-	int nsect;
-	int i, j, o, n;
-
-	if(ctlr->fwmem.css == nil)
-		return nil;
-
-	if(1){
-		/* clear everything */
-		memset(ctlr->fwmem.css, 0, FWPagesize);
-		for(i = 0; i < ctlr->fwmem.nblock; i++)
-			memset(ctlr->fwmem.block[i].p, 0, ctlr->fwmem.block[i].size);
-	}
-
-	if(ctlr->calib.done == 0){
-		sect = ctlr->fw->init.sect;
-		nsect = ctlr->fw->init.nsect;
-	} else {
-		sect = ctlr->fw->main.sect;
-		nsect = ctlr->fw->main.nsect;
-	}
-
-	/* first CSS segment */
-	for(i = 0; i < nsect; i++) {
-		if(sect[i].addr == 0xAAAABBBB){
-			i++;
-			break;
-		}
-	}
-
-	if(i+1 >= nsect)
-		return "firmware misses CSS+paging sections";
-
-	if(sect[i].size > FWPagesize)
-		return "CSS section too big";
-	if(sect[i+1].size > (ctlr->fwmem.npage << FWPageshift))
-		return "paged section too big";
-
-	memmove(ctlr->fwmem.css, sect[i].data, sect[i].size);
-
-	for(j = 0, o = 0; o < sect[i+1].size; o += n, j++){
-		n = sect[i+1].size - o;
-		if(n > ctlr->fwmem.block[j].size)
-			n = ctlr->fwmem.block[j].size;
-		memmove(ctlr->fwmem.block[j].p, sect[i+1].data + o, n);
-	}
-
-	return sendpagingcmd(ctlr);
-}
-
-static int
-readnvmsect1(Ctlr *ctlr, int type, void *data, int len, int off)
-{
-	uchar c[2+2+2+2], *p;
-	char *err;
-
-	p = c;
-	*p++ = 0; // read op
-	*p++ = 0; // target
-	put16(p, type);
-	p += 2;
-	put16(p, off);
-	p += 2;
-	put16(p, len);
-	p += 2;
-
-	ctlr->nvm.off = -1;
-	ctlr->nvm.ret = -1;
-	ctlr->nvm.type = -1;
-	ctlr->nvm.sts = -1;
-
-	ctlr->nvm.buf = data;
-	ctlr->nvm.len = len;
-
-	if((err = cmd(ctlr, 136, c, p - c)) != nil){
-		ctlr->nvm.buf = nil;
-		ctlr->nvm.len = 0;
-		print("readnvmsect: %s\n", err);
-		return -1;
-	}
-
-	if(ctlr->nvm.ret < len)
-		len = ctlr->nvm.ret;
-
-	if(ctlr->nvm.sts != 0 || ctlr->nvm.off != off || (ctlr->nvm.type & 0xFF) != type)
-		return -1;
-
-	return len;
-}
-
-static int
-readnvmsect(Ctlr *ctlr, int type, void *data, int len, int off)
-{
-	int n, r, o;
-
-	for(o = 0; o < len; o += n){
-		r = len - o;
-		if(r > 256)
-			r = 256;
-		if((n = readnvmsect1(ctlr, type, (char*)data + o, r, o+off)) < 0)
-			return -1;
-		if(n < r){
-			o += n;
-			break;
-		}
-	}
-	return o;
-}
-
-static char*
-readnvmconfig(Ctlr *ctlr)
-{
-	uchar *ea = ctlr->edev->ea;
-	uchar buf[8];
-	uint u;
-	char *err;
-
-	if(readnvmsect(ctlr, 1, buf, 8, 0) != 8)
-		return "can't read nvm version";
-
-	ctlr->nvm.version = get16(buf);
-	if (ctlr->family == 7000) {
-		u = get16(buf + 2);
-
-		ctlr->rfcfg.type = (u >> 4) & 3;
-		ctlr->rfcfg.step = (u >> 2) & 3;
-		ctlr->rfcfg.dash = (u >> 0) & 3;
-		ctlr->rfcfg.pnum = (u >> 6) & 3;
-	} else {
-		if(readnvmsect(ctlr, 12, buf, 8, 0) != 8)
-			return "can't read nvm phy config";
-
-		u = get32(buf);
-
-		ctlr->rfcfg.type = (u >> 12) & 0xFFF;
-		ctlr->rfcfg.step = (u >> 8) & 15;
-		ctlr->rfcfg.dash = (u >> 4) & 15;
-		ctlr->rfcfg.pnum = (u >> 6) & 3;
-
-		ctlr->rfcfg.txantmask = (u >> 24) & 15;
-		ctlr->rfcfg.rxantmask = (u >> 28) & 15;
-	}
-	if(ctlr->family >= 8000){
-		if(readnvmsect(ctlr, 11, ea, Eaddrlen, 0x01<<1) != Eaddrlen){
-			u32int a0, a1;
-
-			if((err = niclock(ctlr)) != nil)
-				return err;
-			a0 = prphread(ctlr, 0xa03080);
-			a1 = prphread(ctlr, 0xa03084);
-			nicunlock(ctlr);
-
-			ea[0] = a0 >> 24;
-			ea[1] = a0 >> 16;
-			ea[2] = a0 >> 8;
-			ea[3] = a0 >> 0;
-			ea[4] = a1 >> 8;
-			ea[5] = a1 >> 0;
-		}
-	} else {
-		/*
-		 * 7260 gets angry if we read 6 bytes from 0x15*2.
-		 * reading 8 bytes from 0x14*2 works fine.
-		 */
-		if(readnvmsect(ctlr, 0, buf, 8, 0x14<<1) != 8)
-			return "can't read ea from nvm";
-
-		/* byte order is 16 bit little endian. */
-		ea[0] = buf[3];
-		ea[1] = buf[2];
-		ea[2] = buf[5];
-		ea[3] = buf[4];
-		ea[4] = buf[7];
-		ea[5] = buf[6];
-	}
-	memmove(ctlr->edev->addr, ea, Eaddrlen);
-
-	return nil;
-}
-
-static char*
-sendtxantconfig(Ctlr *ctlr, uint val)
-{
-	uchar c[4];
-
-	put32(c, val);
-	return cmd(ctlr, 152, c, 4);
-}
-
-static char*
-sendphyconfig(Ctlr *ctlr, u32int physku, u32int flowmask, u32int eventmask)
-{
-	uchar c[3*4];
-
-	put32(c+0, physku);
-	put32(c+4, flowmask);
-	put32(c+8, eventmask);
-	return cmd(ctlr, 106, c, 3*4);
-}
-
-static char*
-delstation(Ctlr *ctlr, Station *sta)
-{
-	uchar c[4], *p;
-	char *err;
-
-	if(sta->id < 0)
-		return nil;
-
-	memset(p = c, 0, sizeof(c));
-	*p = sta->id;
-
-	if((err = cmd(ctlr, 25, c, 4)) != nil)
-		return err;
-
-	sta->id = -1;
-	return nil;
-}
-
-enum {
-	StaTypeLink = 0,
-	StaTypeGeneralPurpose,
-	StaTypeMulticast,
-	StaTypeTdlsLink,
-	StaTypeAux,
-};
-
-static char*
-setstation(Ctlr *ctlr, int id, int type, uchar addr[6], Station *sta)
-{
-	uchar c[Tcmdsize], *p;
-	char *err;
-
-	memset(p = c, 0, sizeof(c));
-
-	*p++ = 0;			/* control (1 = update) */
-	p++;				/* reserved */
-	if(ctlr->family >= 7000){
-		put16(p, 0xffff);
-		p += 2;
-		put32(p, ctlr->macid);
-		p += 4;
-	} else {
-		p += 2;			/* reserved */
-	}
-
-	memmove(p, addr, 6);
-	p += 8;
-
-	*p++ = id;			/* sta id */
-
-	if(ctlr->family >= 7000){
-		*p++ = 1 << 1;		/* modify mask */
-		p += 2;			/* reserved */
-
-		put32(p, 0<<26 | 0<<28);
-		p += 4;			/* station_flags */
-
-		put32(p, 3<<26 | 3<<28);
-		p += 4;			/* station_flags_mask */
-
-		p++;			/* add_immediate_ba_tid */
-		p++;			/* remove_immediate_ba_tid */
-		p += 2;			/* add_immediate_ba_ssn */
-		p += 2;			/* sleep_tx_count */
-		p++;			/* sleep state flags */
-
-		*p++ = ctlr->fw->api[0] & UcodeApiSta ? type : 0;		/* station_type */
-
-		p += 2;			/* assoc id */
-
-		p += 2;			/* beamform flags */
-
-		put32(p, 1<<0);
-		p += 4;			/* tfd_queue_mask */
-
-		if(ctlr->fw->api[0] & UcodeApiSta){
-			p += 2;		/* rx_ba_window */
-			p++;		/* sp_length */
-			p++;		/* uapsd_acs */
-		}
-	} else {
-		p += 3;
-		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 */
-	}
-
-	if((err = cmd(ctlr, 24, c, p - c)) != nil)
-		return err;
-	sta->id = id;
-	return nil;
-}
-
-static char*
-setphycontext(Ctlr *ctlr, int amr)
-{
-	uchar c[Tcmdsize], *p;
-	int phyid;
-	char *err;
-
-	phyid = ctlr->phyid;
-	if(phyid < 0){
-		if(amr == CmdRemove)
-			return nil;
-		amr = CmdAdd;
-		phyid = 0;
-	} else if(amr == CmdAdd)
-		amr = CmdModify;
-
-	memset(p = c, 0, sizeof(c));
-	put32(p, phyid);	// id and color
-	p += 4;
-	put32(p, amr);
-	p += 4;
-	put32(p, 0);		// apply time 0 = immediate
-	p += 4;
-	put32(p, 0);		// tx param color ????
-	p += 4;
-
-	*p++ = (ctlr->rxflags & RFlag24Ghz) != 0;
-	*p++ = ctlr->channel;	// channel number
-	*p++ = 0;		// channel width (20MHz<<val)
-	*p++ = 0;		// pos1 below 
-
-	put32(p, ctlr->rfcfg.txantmask);
-	p += 4;
-	put32(p, ctlr->rfcfg.rxantmask<<1 | (1<<10) | (1<<12));
-	p += 4;
-	put32(p, 0);		// acquisition_data ????
-	p += 4;
-	put32(p, 0);		// dsp_cfg_flags
-	p += 4;
-
-	if((err = cmd(ctlr, 8, c, p - c)) != nil)
-		return err;
-
-	if(amr == CmdRemove)
-		phyid = -1;
-	ctlr->phyid = phyid;
-	return nil;
-}
-
-static u32int
-reciprocal(u32int v)
-{
-	return v != 0 ? 0xFFFFFFFFU / v : 0;
-}
-
-static char*
-setmaccontext(Ether *edev, Ctlr *ctlr, int amr, Wnode *bss)
-{
-	uchar c[4+4 + 4+4 + 8+8 + 4+4+4+4+4+4+4 + 5*8 + 12*4], *p;
-	int macid, i;
-	char *err;
-
-	macid = ctlr->macid;
-	if(macid < 0){
-		if(amr == CmdRemove)
-			return nil;
-		amr = CmdAdd;
-		macid = 0;
-	} else if(amr == CmdAdd)
-		amr = CmdModify;
-
-	memset(p = c, 0, sizeof(c));
-	put32(p, macid);
-	p += 4;
-	put32(p, amr);
-	p += 4;
-
-	put32(p, 5);	// mac type 5 = bss
-	p += 4;
-
-	put32(p, 0);	// tsf id ???
-	p += 4;
-
-	memmove(p, edev->ea, 6);
-	p += 8;
-
-	memmove(p, ctlr->bssid, 6);
-	p += 8;
-
-	put32(p, bss == nil? 0xF : (bss->validrates & 0xF));
-	p += 4;
-	put32(p, bss == nil? 0xFF : (bss->validrates >> 4));
-	p += 4;
-
-	put32(p, 0);	// protection flags
-	p += 4;
-
-	put32(p, ctlr->rxflags & RFlagShPreamble);
-	p += 4;
-	put32(p, ctlr->rxflags & RFlagShSlot);
-	p += 4;
-	put32(p, ctlr->rxfilter);
-	p += 4;
-
-	put32(p, 0);	// qos flags
-	p += 4;
-
-	for(i = 0; i < 4; i++){
-		put16(p, 0x07);		// cw_min
-		p += 2;
-		put16(p, 0x0f);		// cw_max
-		p += 2;
-		*p++ = 2;		// aifsn
-		*p++ = (1<<i);		// fifos_mask
-		put16(p, 102*32);	// edca_txop
-		p += 2;
-	}
-	p += 8;
-
-	if(bss != nil){
-		int dtimoff = bss->ival * (int)bss->dtimcount * 1024;
-
-		/* is assoc */
-		put32(p, bss->aid != 0);
-		p += 4;
-
-		/* dtim time (system time) */
-		put32(p, bss->rs + dtimoff);
-		p += 4;
-
-		/* dtim tsf */
-		put64(p, bss->ts + dtimoff);
-		p += 8;
-
-		/* beacon interval */
-		put32(p, bss->ival);
-		p += 4;
-		put32(p, reciprocal(bss->ival));
-		p += 4;
-
-		/* dtim interval */
-		put32(p, bss->ival * bss->dtimperiod);
-		p += 4;
-		put32(p, reciprocal(bss->ival * bss->dtimperiod));
-		p += 4;
-
-		/* listen interval */
-		put32(p, 10);
-		p += 4;
-
-		/* assoc id */
-		put32(p, bss->aid & 0x3fff);
-		p += 4;
-
-		/* assoc beacon arrive time */
-		put32(p, bss->rs);
-		p += 4;
-	}
-	USED(p);
-
-	if((err = cmd(ctlr, 40, c, sizeof(c))) != nil)
-		return err;
-
-	if(amr == CmdRemove)
-		macid = -1;
-	ctlr->macid = macid;
-
-	return nil;
-}
-
-static char*
-setbindingcontext(Ctlr *ctlr, int amr)
-{
-	uchar c[Tcmdsize], *p;
-	int bindid;
-	char *err;
-	int i;
-
-	bindid = ctlr->bindid;
-	if(bindid < 0){
-		if(amr == CmdRemove)
-			return nil;
-		amr = CmdAdd;
-		bindid = 0;
-	} else if(amr == CmdAdd)
-		amr = CmdModify;
-
-	if(ctlr->phyid < 0)
-		return "setbindingcontext: no phyid";
-	if(ctlr->macid < 0)
-		return "setbindingcontext: no macid";
-
-	p = c;
-	put32(p, bindid);
-	p += 4;
-	put32(p, amr);
-	p += 4;
-
-	i = 0;
-	if(amr != CmdRemove){
-		put32(p, ctlr->macid);
-		p += 4;
-		i++;
-	}
-	for(; i < 3; i++){
-		put32(p, -1);
-		p += 4;
-	}
-	put32(p, ctlr->phyid);
-	p += 4;
-
-	if((err = cmd(ctlr, 43, c, p - c)) != nil)
-		return err;
-
-	if(amr == CmdRemove)
-		bindid = -1;
-	ctlr->bindid = bindid;
-	return nil;
-}
-
-static int
-timeeventdone(void *arg)
-{
-	Ctlr *ctlr = arg;
-	return ctlr->te.id == -1 || ctlr->te.active != 0;
-}
-
-static char*
-settimeevent(Ctlr *ctlr, int amr, int ival)
-{
-	int timeid;
-	uchar c[9*4], *p;
-	char *err;
-
-	switch(amr){
-	case CmdAdd:
-		timeid = ctlr->te.id;
-		if(timeid == -1)
-			timeid = 0;
-		else {
-			if(ctlr->te.active)
-				return nil;
-			amr = CmdModify;
-		}
-		break;
-	default:
-		timeid = ctlr->te.id;
-		if(timeid == -1)
-			return nil;
-		break;
-	}
-
-	memset(p = c, 0, sizeof(c));
-	put32(p, ctlr->macid);
-	p += 4;
-	put32(p, amr);
-	p += 4;
-	put32(p, timeid);
-	p += 4;
-
-	if(amr == CmdRemove)
-		p += 6*4;
-	else{
-		put32(p, 0);			// apply time
-		p += 4;
-		put32(p, ival/2);		// max delay
-		p += 4;
-		put32(p, 0);			// depends on
-		p += 4;
-		put32(p, 1);			// interval
-		p += 4;
-		put32(p, ival? ival*2: 1024);	// duration
-		p += 4;
-		*p++ = 1;			// repeat
-		*p++ = 0;			// max frags
-		put16(p, 1<<0 | 1<<1 | 1<<11);	// policy
-		p += 2;
-	}
-
-	ctlr->te.active = 0;
-	if((err = cmd(ctlr, 41, c, p - c)) != nil)
-		return err;
-
-	if(amr == CmdRemove){
-		ctlr->te.active = 0;
-		ctlr->te.id = -1;
-		return nil;
-	}
-	tsleep(&ctlr->te, timeeventdone, ctlr, 100);
-	return ctlr->te.active? nil: "timeevent did not start";
-}
-
-
-static char*
-setbindingquotas(Ctlr *ctlr, int bindid)
-{
-	uchar c[4*(3*4)], *p;
-	int i;
-
-	if((ctlr->fw->capa[1] & UcodeCapQuota) == 0)
-		return nil;
-
-	i = 0;
-	p = c;
-
-	if(bindid != -1){
-		put32(p, bindid);
-		p += 4;
-		put32(p, 128);
-		p += 4;
-		put32(p, 0);
-		p += 4;
-		i++;
-	}
-	for(; i < 4; i++){
-		put32(p, -1);
-		p += 4;
-		put32(p, 0);
-		p += 4;
-		put32(p, 0);
-		p += 4;
-	}
-
-	return cmd(ctlr, 44, c, p - c);
-}
-
-static char*
-setmcastfilter(Ctlr *ctlr)
-{
-	uchar *p;
-	char *err;
-	Block *b;
-
-	b = allocb(4+6+2);
-	p = b->rp;
-
-	*p++ = 1;	// filter own
-	*p++ = 0;	// port id
-	*p++ = 0;	// count
-	*p++ = 1;	// pass all
-
-	memmove(p, ctlr->bssid, 6);
-	p += 6;
-	*p++ = 0;
-	*p++ = 0;
-
-	b->wp = p;
-	if((err = qcmd(ctlr, 4, 208, nil, 0, b)) != nil){
-		freeb(b);
-		return err;
-	}
-	return flushq(ctlr, 4);
-}
-
-static char*
-setmacpowermode(Ctlr *ctlr)
-{
-	uchar c[4 + 2+2 + 4+4+4+4 + 1+1 + 2+2 + 1+1+1+1 + 1+1+1+1 + 1+1], *p;
-
-	p = c;
-	put32(p, ctlr->macid);
-	p += 4;
-
-	put16(p, 0);	// flags
-	p += 2;
-	put16(p, 5);	// keep alive seconds
-	p += 2;
-
-	put32(p, 0);	// rx data timeout
-	p += 4;
-	put32(p, 0);	// tx data timeout
-	p += 4;
-	put32(p, 0);	// rx data timeout uapsd
-	p += 4;
-	put32(p, 0);	// tx data timeout uapsd
-	p += 4;
-
-	*p++ = 0;	// lprx rssi threshold
-	*p++ = 0;	// skip dtim periods
-
-	put16(p, 0);	// snooze interval
-	p += 2;
-	put16(p, 0);	// snooze window
-	p += 2;
-
-	*p++ = 0;	// snooze step
-	*p++ = 0;	// qndp tid
-	*p++ = 0;	// uapsd ac flags
-	*p++ = 0;	// uapsd max sp
-
-	*p++ = 0;	// heavy tx thld packets
-	*p++ = 0;	// heavy rx thld packets
-
-	*p++ = 0;	// heavy tx thld percentage
-	*p++ = 0;	// heavy rx thld percentage
-
-	*p++ = 0;	// limited ps threshold
-	*p++ = 0;	// reserved
-
-	return cmd(ctlr, 169, c, p - c);
-}
-
-static char*
-disablebeaconfilter(Ctlr *ctlr)
-{
-	uchar c[11*4];
-
-	memset(c, 0, sizeof(c));
-	return cmd(ctlr, 210, c, 11*4);
-}
-
-static char*
-tttxbackoff(Ctlr *ctlr)
-{
-	uchar c[4];
-	
-	put32(c, 0);
-	return cmd(ctlr, 126, c, sizeof(c));
-}
-
-static char*
-updatedevicepower(Ctlr *ctlr)
-{
-	uchar c[4];
-
-	memset(c, 0, sizeof(c));
-	put16(c, 0<<13 | 1<<0);	// cont active off, pm enable
-
-	return cmd(ctlr, 119, c, 4);
-}
-
-static char*
-postboot7000(Ctlr *ctlr)
-{
-	char *err;
-
-	if(ctlr->calib.done == 0){
-		if(ctlr->family == 7000)
-			if((err = sendbtcoexadv(ctlr)) != nil)
-				return err;
-		if((err = readnvmconfig(ctlr)) != nil)
-			return err;
-	}
-
-	if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask)) != nil)
-		return err;
-
-	if(ctlr->calib.done == 0){
-		if((err = sendphyconfig(ctlr,
-			ctlr->fw->physku,
-			ctlr->fw->init.defcalib.flowmask,
-			ctlr->fw->init.defcalib.eventmask)) != nil)
-			return err;
-
-		/* wait to collect calibration records */
-		if(irqwait(ctlr, Ierr, 2000))
-			return "calibration failed";
-
-		if(ctlr->calib.done == 0){
-			print("iwl: no calibration results\n");
-			ctlr->calib.done = 1;
-		}
-	} else {
-		Block *b;
-		int i;
-
-		for(i = 0; i < nelem(ctlr->calib.cmd); i++){
-			if((b = ctlr->calib.cmd[i]) == nil)
-				continue;
-			b = copyblock(b, BLEN(b));
-			if((qcmd(ctlr, 4, 108, nil, 0, b)) != nil){
-				freeb(b);
-				return err;
-			}
-			if((err = flushq(ctlr, 4)) != nil)
-				return err;
-		}
-
-		if((err = sendphyconfig(ctlr,
-			ctlr->fw->physku,
-			ctlr->fw->main.defcalib.flowmask,
-			ctlr->fw->main.defcalib.eventmask)) != nil)
-			return err;
-
-		if((err = sendbtcoexadv(ctlr)) != nil)
-			return err;
-
-		/* Initialize tx backoffs to the minimum. */
-		if(ctlr->family == 7000)
-			if((err = tttxbackoff(ctlr)) != nil)
-				return err;
-
-		if((err = updatedevicepower(ctlr)) != nil){
-			print("can't update device power: %s\n", err);
-			return err;
-		}
-		if(ctlr->fw->capa[0] & UcodeCapLar)
-			if((err = sendmccupdate(ctlr, "ZZ")) != nil)
-				return err;
-		if((err = disablebeaconfilter(ctlr)) != nil){
-			print("can't disable beacon filter: %s\n", err);
-			return err;
-		}
-	}
-
-	return nil;
-}
-
-static char*
-postboot6000(Ctlr *ctlr)
-{
-	uchar c[Tcmdsize];
-	char *err;
-
-	/* disable wimax coexistance */
-	memset(c, 0, sizeof(c));
-	if((err = cmd(ctlr, 90, c, 4+4*16)) != nil)
-		return err;
-
-	/* 6235 times out if we calibrate the crystal immediately */
-	tsleep(&up->sleep, return0, nil, 10);
-	if(ctlr->type != Type5150){
-		/* calibrate crystal */
-		memset(c, 0, sizeof(c));
-		c[0] = 15;	/* code */
-		c[1] = 0;	/* group */
-		c[2] = 1;	/* ngroup */
-		c[3] = 1;	/* isvalid */
-		c[4] = ctlr->eeprom.crystal;
-		c[5] = ctlr->eeprom.crystal>>16;
-		/* for some reason 8086:4238 needs a second try */
-		if(cmd(ctlr, 176, c, 8) != nil && (err = cmd(ctlr, 176, c, 8)) != nil)
-			return err;
-	}
-
-	if(ctlr->calib.done == 0){
-		/* query calibration (init firmware) */
-		memset(c, 0, sizeof(c));
-		put32(c + 0*(5*4) + 0, 0xffffffff);
-		put32(c + 0*(5*4) + 4, 0xffffffff);
-		put32(c + 0*(5*4) + 8, 0xffffffff);
-		put32(c + 2*(5*4) + 0, 0xffffffff);
-		if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil)
-			return err;
-
-		/* wait to collect calibration records */
-		if(irqwait(ctlr, Ierr, 2000))
-			return "calibration failed";
-
-		if(ctlr->calib.done == 0){
-			print("iwl: no calibration results\n");
-			ctlr->calib.done = 1;
-		}
-	} else {
-		static uchar cmds[] = {8, 9, 11, 17, 16};
-		int q;
-
-		/* send calibration records (runtime firmware) */
-		for(q=0; q<nelem(cmds); q++){
-			Block *b;
-			int i;
-
-			i = cmds[q];
-			if(i == 8 && ctlr->type != Type5150 && ctlr->type != Type2030 &&
-				ctlr->type != Type2000)
-				continue;
-			if(i == 17 && (ctlr->type >= Type6000 || ctlr->type == Type5150) &&
-				ctlr->type != Type2030 && ctlr->type != Type2000)
-				continue;
-
-			if((b = ctlr->calib.cmd[i]) == nil)
-				continue;
-			b = copyblock(b, BLEN(b));
-			if((err = qcmd(ctlr, 4, 176, nil, 0, b)) != nil){
-				freeb(b);
-				return err;
-			}
-			if((err = flushq(ctlr, 4)) != nil)
-				return err;
-		}
-
-		/* temperature sensor offset */
-		switch (ctlr->type){
-		case Type6005:
-			memset(c, 0, sizeof(c));
-			c[0] = 18;
-			c[1] = 0;
-			c[2] = 1;
-			c[3] = 1;
-			put16(c + 4, 2700);
-			if((err = cmd(ctlr, 176, c, 4+2+2)) != nil)
-				return err;
-			break;
-
-		case Type2030:
-		case Type2000:
-			memset(c, 0, sizeof(c));
-			c[0] = 18;
-			c[1] = 0;
-			c[2] = 1;
-			c[3] = 1;
-			if(ctlr->eeprom.rawtemp != 0){
-				put16(c + 4, ctlr->eeprom.temp);
-				put16(c + 6, ctlr->eeprom.rawtemp);
-			} else{
-				put16(c + 4, 2700);
-				put16(c + 6, 2700);
-			}
-			put16(c + 8, ctlr->eeprom.volt);
-			if((err = cmd(ctlr, 176, c, 4+2+2+2+2)) != nil)
-				return err;
-			break;
-		}
-
-		if(ctlr->type == Type6005 || ctlr->type == Type6050){
-			/* runtime DC calibration */
-			memset(c, 0, sizeof(c));
-			put32(c + 0*(5*4) + 0, 0xffffffff);
-			put32(c + 0*(5*4) + 4, 1<<1);
-			if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil)
-				return err;
-		}
-
-		if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask & 7)) != nil)
-			return err;
-
-		if(ctlr->type == Type2030){
-			if((err = sendbtcoexadv(ctlr)) != nil)
-				return err;
-		}
-	}
-	return nil;
-}
-
-static void
-initqueue(Ctlr *ctlr, int qid, int fifo, int chainmode, int window)
-{
-	csr32w(ctlr, HbusTargWptr, (qid << 8) | 0);
-
-	if(ctlr->family >= 7000 || ctlr->type != Type4965){
-		if(ctlr->family >= 7000)
-			prphwrite(ctlr, SchedQueueStatus + qid*4, 1 << 19);
-
-		if(chainmode)
-			prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) | (1<<qid));
-		else
-			prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) & ~(1<<qid));
-
-		prphwrite(ctlr, SchedAggrSel, prphread(ctlr, SchedAggrSel) & ~(1<<qid));
-
-		prphwrite(ctlr, SchedQueueRdptr + qid*4, 0);
-
-		/* Set scheduler window size and frame limit. */
-		memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8, 0);
-		memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8 + 4, window<<16 | window);
-
-		if(ctlr->family >= 7000){
-			prphwrite(ctlr, SchedQueueStatus + qid*4, 0x017f0018 | fifo);
-		} else {
-			prphwrite(ctlr, SchedQueueStatus + qid*4, 0x00ff0018 | fifo);
-		}
-	} else {
-		if(chainmode)
-			prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) | (1<<qid));
-		else
-			prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) & ~(1<<qid));
-
-		prphwrite(ctlr, SchedQueueRdptr4965 + qid*4, 0);
-
-		/* Set scheduler window size and frame limit. */
-		memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8, window);
-		memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8 + 4, window<<16);
-
-		prphwrite(ctlr, SchedQueueStatus4965 + qid*4, 0x0007fc01 | fifo<<1);
-	}
-}
-
-static char*
-postboot(Ctlr *ctlr)
-{
-	uint ctxoff, ctxlen, dramaddr;
-	char *err;
-	int i, f;
-
-	if((err = niclock(ctlr)) != nil)
-		return err;
-
-	if(ctlr->family >= 7000 || ctlr->type != Type4965){
-		dramaddr = SchedDramAddr;
-		ctxoff = SchedCtxOff;
-		ctxlen = (SchedTransTblOff + 2*ctlr->ntxq) - ctxoff;
-	} else {
-		dramaddr = SchedDramAddr4965;
-		ctxoff = SchedCtxOff4965;
-		ctxlen = SchedCtxLen4965;
-	}
-
-	ctlr->sched.base = prphread(ctlr, SchedSramAddr);
-	for(i=0; i < ctxlen; i += 4)
-		memwrite(ctlr, ctlr->sched.base + ctxoff + i, 0);
-
-	prphwrite(ctlr, dramaddr, PCIWADDR(ctlr->sched.s)>>10);
-
-	if(ctlr->family >= 7000) {
-		prphwrite(ctlr, SchedEnCtrl, 0);
-		prphwrite(ctlr, SchedChainExtEn, 0);
-	}
-
-	for(i = 0; i < nelem(ctlr->tx); i++){
-		if(i == 4 && ctlr->family < 7000 && ctlr->type == Type4965)
-			f = 4;
-		else {
-			static char qid2fifo[] = {
-				 3, 2, 1, 0, 7, 5, 6,
-			};
-			f = qid2fifo[i];
-		}
-		initqueue(ctlr, i, f, i != 4 && ctlr->type != Type4965, 64);
-	}
-
-	/* Enable interrupts for all queues. */
-	if(ctlr->family >= 7000){
-		prphwrite(ctlr, SchedEnCtrl, 1 << 4);
-	} else if(ctlr->type != Type4965) {
-		prphwrite(ctlr, SchedIntrMask, (1<<ctlr->ntxq)-1);
-	} else {
-		prphwrite(ctlr, SchedIntrMask4965, (1<<ctlr->ntxq)-1);
-	}
-
-	/* Identify TX FIFO rings (0-7). */
-	if(ctlr->family >= 7000 || ctlr->type != Type4965){
-		prphwrite(ctlr, SchedTxFact, 0xff);
-	} else {
-		prphwrite(ctlr, SchedTxFact4965, 0xff);
-	}
-
-	/* Enable DMA channels */
-	for(i = 0; i < ctlr->ndma; i++)
-		csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna);
-
-	/* Auto Retry Enable */
-	csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2);
-
-	nicunlock(ctlr);
-
-	if((err = enablepaging(ctlr)) != nil){
-		ctlr->calib.done = 0;
-		return err;
-	}
-
-	if(ctlr->family >= 7000)
-		return postboot7000(ctlr);
-	else if(ctlr->type != Type4965)
-		return postboot6000(ctlr);
-
-	return nil;
-}
-
-static char*
-loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size)
-{
-	enum { Maxchunk = 0x20000 };
-	uchar *dma;
-	char *err;
-
-	while(size > Maxchunk){
-		if((err = loadfirmware1(ctlr, dst, data, Maxchunk)) != nil)
-			return err;
-		size -= Maxchunk;
-		data += Maxchunk;
-		dst += Maxchunk;
-	}
-
-	dma = mallocalign(size, 16, 0, 0);
-	if(dma == nil)
-		return "no memory for dma";
-	memmove(dma, data, size);
-	coherence();
-	
-	if((err = niclock(ctlr)) != nil){
-		free(dma);
-		return err;
-	}
-
-	if(ctlr->family >= 7000 && dst >= 0x40000 && dst < 0x57fff)
-		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) | ExtAddr);
-	else
-		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr);
-
-	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);
-
-	err = nil;
-	if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx)
-		err = "dma error / timeout";
-
-	if(niclock(ctlr) == nil){
-		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr);
-		nicunlock(ctlr);
-	}
-
-	free(dma);
-
-	return err;
-}
-
-static char*
-setloadstatus(Ctlr *ctlr, u32int val)
-{
-	char *err;
-
-	if((err = niclock(ctlr)) != nil)
-		return err;
-	csr32w(ctlr, UcodeLoadStatus, val);
-	nicunlock(ctlr);
-	return nil;
-}
-
-static char*
-loadsections(Ctlr *ctlr, FWSect *sect, int nsect)
-{
-	int i, num;
-	char *err;
-
-	if(ctlr->family >= 8000){
-		if((err = niclock(ctlr)) != nil)
-			return err;
-		prphwrite(ctlr, ReleaseCpuReset, CpuResetBit);
-		nicunlock(ctlr);
-	}
-
-	num = 0;
-	for(i = 0; i < nsect; i++){
-		if(sect[i].addr == 0xAAAABBBB)
-			break;
-		if(sect[i].addr == 0xFFFFCCCC)
-			num = 16;
-		else {
-			if(sect[i].data == nil || sect[i].size == 0)
-				return "bad load section";
-			if((err = loadfirmware1(ctlr, sect[i].addr, sect[i].data, sect[i].size)) != nil)
-				return err;
-			num++;
-		}
-		if(ctlr->family >= 8000
-		&& (err = setloadstatus(ctlr, (1ULL << num)-1)) != nil)
-			return err;
-	}
-	return nil;
-}
-
-static char*
-ucodestart(Ctlr *ctlr)
-{
-	memset(&ctlr->fwinfo, 0, sizeof(ctlr->fwinfo));
-	if(ctlr->family >= 8000)
-		return setloadstatus(ctlr, -1);
-	csr32w(ctlr, Reset, 0);
-	return nil;
-}
-
-static char*
-boot(Ctlr *ctlr)
-{
-	int i, n, size;
-	uchar *p, *dma;
-	FWImage *fw;
-	char *err;
-
-	fw = ctlr->fw;
-
-	if(fw->boot.text.size == 0){
-		if(ctlr->calib.done == 0){
-			if((err = loadsections(ctlr, fw->init.sect, fw->init.nsect)) != nil)
-				return err;
-			if((err = ucodestart(ctlr)) != nil)
-				return err;
-
-			tsleep(&up->sleep, return0, 0, 100);
-
-			if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive|| !ctlr->fwinfo.valid){
-				return "init firmware boot failed";
-			}
-			if((err = postboot(ctlr)) != nil)
-				return err;
-			if((err = reset(ctlr)) != nil)
-				return err;
-		}
-
-		if((err = loadsections(ctlr, fw->main.sect, fw->main.nsect)) != nil)
-			return err;
-		if((err= ucodestart(ctlr)) != nil)
-			return err;
-
-		tsleep(&up->sleep, return0, 0, 100);
-
-		if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive || !ctlr->fwinfo.valid)
-			return "main firmware boot failed";
-
-		return postboot(ctlr);
-	}
-
-	if(ctlr->family >= 7000)
-		return "wrong firmware image";
-
-	size = ROUND(fw->init.data.size, 16) + ROUND(fw->init.text.size, 16);
-	dma = mallocalign(size, 16, 0, 0);
-	if(dma == nil)
-		return "no memory for dma";
-
-	if((err = niclock(ctlr)) != nil){
-		free(dma);
-		return err;
-	}
-
-	p = dma;
-	memmove(p, fw->init.data.data, fw->init.data.size);
-	coherence();
-	prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4);
-	prphwrite(ctlr, BsmDramDataSize, fw->init.data.size);
-	p += ROUND(fw->init.data.size, 16);
-	memmove(p, fw->init.text.data, fw->init.text.size);
-	coherence();
-	prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4);
-	prphwrite(ctlr, BsmDramTextSize, fw->init.text.size);
-
-	nicunlock(ctlr);
-	if((err = niclock(ctlr)) != nil){
-		free(dma);
-		return err;
-	}
-
-	p = fw->boot.text.data;
-	n = fw->boot.text.size/4;
-	for(i=0; i<n; i++, p += 4)
-		prphwrite(ctlr, BsmSramBase+i*4, get32(p));
-
-	prphwrite(ctlr, BsmWrMemSrc, 0);
-	prphwrite(ctlr, BsmWrMemDst, 0);
-	prphwrite(ctlr, BsmWrDwCount, n);
-
-	prphwrite(ctlr, BsmWrCtrl, 1<<31);
-
-	for(i=0; i<1000; i++){
-		if((prphread(ctlr, BsmWrCtrl) & (1<<31)) == 0)
-			break;
-		microdelay(10);
-	}
-	if(i == 1000){
-		nicunlock(ctlr);
-		free(dma);
-		return "bootcode timeout";
-	}
-
-	prphwrite(ctlr, BsmWrCtrl, 1<<30);
-	nicunlock(ctlr);
-
-	csr32w(ctlr, Reset, 0);
-	if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){
-		free(dma);
-		return "boot firmware boot failed";
-	}
-	free(dma);
-
-	size = ROUND(fw->main.data.size, 16) + ROUND(fw->main.text.size, 16);
-	dma = mallocalign(size, 16, 0, 0);
-	if(dma == nil)
-		return "no memory for dma";
-	if((err = niclock(ctlr)) != nil){
-		free(dma);
-		return err;
-	}
-	p = dma;
-	memmove(p, fw->main.data.data, fw->main.data.size);
-	coherence();
-	prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4);
-	prphwrite(ctlr, BsmDramDataSize, fw->main.data.size);
-	p += ROUND(fw->main.data.size, 16);
-	memmove(p, fw->main.text.data, fw->main.text.size);
-	coherence();
-	prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4);
-	prphwrite(ctlr, BsmDramTextSize, fw->main.text.size | (1<<31));
-	nicunlock(ctlr);
-
-	if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){
-		free(dma);
-		return "main firmware boot failed";
-	}
-	free(dma);
-	return postboot(ctlr);
-}
-
-static int
-txqready(void *arg)
-{
-	TXQ *q = arg;
-	return q->n < Ntxqmax;
-}
-
-static char*
-qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block)
-{
-	char *err;
-	int hdrlen;
-	Block *bcmd;
-	uchar *d, *c;
-	TXQ *q;
-
-	assert(qid < ctlr->ntxq);
-
-	if((code & 0xFF00) != 0)
-		hdrlen = 8;
-	else
-		hdrlen = 4;
-
-	if(hdrlen+size > Tcmdsize)
-		bcmd = allocb(hdrlen + size);
-	else
-		bcmd = nil;
-
-	ilock(ctlr);
-	q = &ctlr->tx[qid];
-	while(q->n >= Ntxqmax && !ctlr->broken){
-		iunlock(ctlr);
-		qlock(q);
-		if(!waserror()){
-			tsleep(q, txqready, q, 5);
-			poperror();
-		}
-		qunlock(q);
-		ilock(ctlr);
-	}
-	if(ctlr->broken){
-		iunlock(ctlr);
-		return "qcmd: broken";
-	}
-	/* wake up the nic (just needed for 7k) */
-	if(ctlr->family == 7000 && qid == 4 && q->n == 0)
-		if((err = niclock(ctlr)) != nil){
-			iunlock(ctlr);
-			return err;
-		}
-	q->n++;
-	q->lastcmd = code;
-
-	q->b[q->i] = block;
-	if(bcmd != nil){
-		bcmd->next = q->b[q->i];
-		q->b[q->i] = bcmd;
-
-		c = bcmd->rp;
-		bcmd->wp = c + hdrlen + size;
-	} else {
-		c = q->c + q->i * Tcmdsize;
-	}
-
-	/* build command */
-	if(hdrlen == 8){
-		c[0] = code;
-		c[1] = code>>8;	/* group id */
-		c[2] = q->i;
-		c[3] = qid;
-		put16(c+4, size);
-		c[6] = 0;
-		c[7] = code>>16;
-	} else {
-		c[0] = code;
-		c[1] = 0;	/* flags */
-		c[2] = q->i;
-		c[3] = qid;
-	}
-	if(size > 0)
-		memmove(c+hdrlen, data, size);
-	size += hdrlen;
-
-	/* build descriptor */
-	d = q->d + q->i * Tdscsize;
-	*d++ = 0;
-	*d++ = 0;
-	*d++ = 0;
-	*d++ = 1 + (block != nil); /* nsegs */
-	put32(d, PCIWADDR(c));	d += 4;
-	put16(d, size << 4); d += 2;
-	if(block != nil){
-		size = BLEN(block);
-		put32(d, PCIWADDR(block->rp)); d += 4;
-		put16(d, size << 4); d += 2;
-	}
-	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(!waserror()){
-			tsleep(q, txqempty, q, 10);
-			poperror();
-		}
-	}
-	qunlock(q);
-	if(ctlr->broken)
-		return "flushq: broken";
-	ctlr->broken = 1;
-	return "flushq: timeout";
-}
-
-static char*
-cmd(Ctlr *ctlr, uint code, uchar *data, int size)
-{
-	char *err;
-
-	if(0) print("cmd %ud\n", code);
-
-	if((err = qcmd(ctlr, 4, code, data, size, nil)) != nil
-	|| (err = flushq(ctlr, 4)) != nil){
-		print("#l%d: cmd %ud: %s\n", ctlr->edev->ctlrno, code, err);
-		return err;
-	}
-	return nil;
-}
-
-static void
-setled(Ctlr *ctlr, int which, int on, int off)
-{
-	uchar c[8];
-
-	if(ctlr->family >= 7000)
-		return;	// TODO
-
-	csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl);
-	put32(c, 10000);
-	c[4] = which;
-	c[5] = on;
-	c[6] = off;
-	c[7] = 0;
-	cmd(ctlr, 72, c, sizeof(c));
-}
-
-static char*
-rxoff7000(Ether *edev, Ctlr *ctlr)
-{
-	char *err;
-	int i;
-
-	for(i = 0; i < nelem(ctlr->tx); i++)
-		if((err = flushq(ctlr, i)) != nil){
-			print("can't flush queue %d: %s\n", i, err);
-			return err;
-		}
-
-	if((err = settimeevent(ctlr, CmdRemove, 0)) != nil){
-		print("can't remove time event: %s\n", err);
-		return err;
-	}
-
-	if((err = setbindingquotas(ctlr, -1)) != nil){
-		print("can't disable quotas: %s\n", err);
-		return err;
-	}
-	if((err = delstation(ctlr, &ctlr->bss)) != nil){
-		print("can't remove bss station: %s\n", err);
-		return err;
-	}
-	if((err = delstation(ctlr, &ctlr->bcast)) != nil){
-		print("can't remove bcast station: %s\n", err);
-		return err;
-	}
-	if((err = setbindingcontext(ctlr, CmdRemove)) != nil){
-		print("removing bindingcontext: %s\n", err);
-		return err;
-	}
-	if((err = setmaccontext(edev, ctlr, CmdRemove, nil)) != nil){
-		print("removing maccontext: %s\n", err);
-		return err;
-	}
-	if((err = setphycontext(ctlr, CmdRemove)) != nil){
-		print("setphycontext: %s\n", err);
-		return err;
-	}
-	return nil;
-}
-
-static char*
-rxon7000(Ether *edev, Ctlr *ctlr)
-{
-	char *err;
-
-	if((err = setphycontext(ctlr, CmdAdd)) != nil){
-		print("setphycontext: %s\n", err);
-		return err;
-	}
-	if((err = setmaccontext(edev, ctlr, CmdAdd, nil)) != nil){
-		print("setmaccontext: %s\n", err);
-		return err;
-	}
-	if((err = setbindingcontext(ctlr, CmdAdd)) != nil){
-		print("adding bindingcontext: %s\n", err);
-		return err;
-	}
-	if((err = setmcastfilter(ctlr)) != nil){
-		print("can't set mcast filter: %s\n", err);
-		return err;
-	}
-	if((err = setmacpowermode(ctlr)) != nil){
-		print("can't set mac power: %s\n", err);
-		return err;
-	}
-	if((err = setbindingquotas(ctlr, ctlr->aid != 0 ? ctlr->bindid : -1)) != nil){
-		print("can't set binding quotas: %s\n", err);
-		return err;
-	}
-	return nil;
-}
-
-static char*
-rxon6000(Ether *edev, Ctlr *ctlr)
-{
-	uchar c[Tcmdsize], *p;
-	char *err;
-
-	memset(p = c, 0, sizeof(c));
-	memmove(p, edev->ea, 6); p += 8;	/* myaddr */
-	memmove(p, ctlr->bssid, 6); p += 8;	/* bssid */
-	memmove(p, edev->ea, 6); p += 8;	/* wlap */
-	*p++ = 3;				/* mode (STA) */
-	*p++ = 0;				/* air (?) */
-	/* rxchain */
-	put16(p, ((ctlr->rfcfg.rxantmask & 7)<<1) | (2<<10) | (2<<12));
-	p += 2;
-	*p++ = 0xff;				/* ofdm mask (not yet negotiated) */
-	*p++ = 0x0f;				/* cck mask (not yet negotiated) */
-	put16(p, ctlr->aid & 0x3fff);
-	p += 2;					/* aid */
-	put32(p, ctlr->rxflags);
-	p += 4;
-	put32(p, ctlr->rxfilter);
-	p += 4;
-	*p++ = ctlr->channel;
-	p++;					/* reserved */
-	*p++ = 0xff;				/* ht single mask */
-	*p++ = 0xff;				/* ht dual mask */
-	if(ctlr->type != Type4965){
-		*p++ = 0xff;			/* ht triple mask */
-		p++;				/* reserved */
-		put16(p, 0); p += 2;		/* acquisition */
-		p += 2;				/* reserved */
-	}
-	if((err = cmd(ctlr, 16, c, p - c)) != nil){
-		print("rxon6000: %s\n", err);
-		return err;
-	}
-	return nil;
-}
-
-static char*
-rxon(Ether *edev, Wnode *bss)
-{
-	Ctlr *ctlr = edev->ctlr;
-	char *err;
-
-	if(ctlr->family >= 7000)
-		if((err = rxoff7000(edev, ctlr)) != nil)
-			goto Out;
-
-	ctlr->rxfilter = FilterNoDecrypt | FilterMulticast | FilterBeacon;
-	if(ctlr->family >= 7000)
-		ctlr->rxfilter |= FilterNoDecryptMcast;
-	if(ctlr->prom)
-		ctlr->rxfilter |= FilterPromisc;
-
-	ctlr->rxflags =  RFlagTSF | RFlagCTSToSelf | RFlag24Ghz | RFlagAuto;
-	if(bss != nil){
-		ctlr->aid = bss->aid;
-		ctlr->channel = bss->channel;
-		memmove(ctlr->bssid, bss->bssid, sizeof(ctlr->bssid));
-		if(bss->cap & (1<<5))
-			ctlr->rxflags |= RFlagShPreamble;
-		if(bss->cap & (1<<10))
-			ctlr->rxflags |= RFlagShSlot;
-		if(ctlr->aid != 0){
-			ctlr->rxfilter |= FilterBSS;
-			ctlr->rxfilter &= ~FilterBeacon;
-			ctlr->bss.id = -1;
-		} else {
-			ctlr->bcast.id = -1;
-		}
-	} else {
-		ctlr->aid = 0;
-		memmove(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid));
-		ctlr->bcast.id = -1;
-		ctlr->bss.id = -1;
-	}
-
-	if(ctlr->aid != 0)
-		setled(ctlr, 2, 0, 1);		/* on when associated */
-	else if(memcmp(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid)) != 0)
-		setled(ctlr, 2, 10, 10);	/* slow blink when connecting */
-	else
-		setled(ctlr, 2, 5, 5);		/* fast blink when scanning */
-
-	if(ctlr->wifi->debug)
-		print("#l%d: rxon: bssid %E, aid %x, channel %d, rxfilter %ux, rxflags %ux\n",
-			edev->ctlrno, ctlr->bssid, ctlr->aid, ctlr->channel, ctlr->rxfilter, ctlr->rxflags);
-
-	if(ctlr->family >= 7000)
-		err = rxon7000(edev, ctlr);
-	else
-		err = rxon6000(edev, ctlr);
-	if(err != nil)
-		goto Out;
-
-	if(ctlr->bcast.id == -1){
-		if((err = setstation(ctlr,
-			(ctlr->type != Type4965)? 15: 31,
-			StaTypeGeneralPurpose,
-			edev->bcast,
-			&ctlr->bcast)) != nil)
-			goto Out;
-	}
-	if(ctlr->bss.id == -1 && bss != nil && ctlr->aid != 0){
-		if((err = setstation(ctlr,
-			0,
-			StaTypeLink,
-			bss->bssid,
-			&ctlr->bss)) != nil)
-			goto Out;
-
-		if(ctlr->family >= 7000)
-			if((err = setmaccontext(edev, ctlr, CmdModify, bss)) != nil)
-				goto Out;
-	}
-Out:
-	return err;
-}
-
-static void
-transmit(Wifi *wifi, Wnode *wn, Block *b)
-{
-	int flags, rate, ant;
-	uchar c[Tcmdsize], *p;
-	Ether *edev;
-	Station *sta;
-	Ctlr *ctlr;
-	Wifipkt *w;
-	char *err;
-
-	edev = wifi->ether;
-	ctlr = edev->ctlr;
-
-	qlock(ctlr);
-	if(ctlr->attached == 0 || ctlr->broken){
-Broken:
-		qunlock(ctlr);
-		freeb(b);
-		return;
-	}
-
-	if((wn->channel != ctlr->channel)
-	|| (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0))){
-		if(rxon(edev, wn) != nil)
-			goto Broken;
-	}
-
-	/*
-	 * unless authenticated, the firmware will hop
-	 * channels unless we force it onto a channel using
-	 * a timeevent.
-	 */
-	if(ctlr->aid == 0 && ctlr->family >= 7000)
-		if(settimeevent(ctlr, CmdAdd, wn->ival) != nil)
-			goto Broken;
-
-	if(b == nil){
-		/* association note has no data to transmit */
-		qunlock(ctlr);
-		return;
-	}
-	flags = 0;
-	sta = &ctlr->bcast;
-	p = wn->minrate;
-	w = (Wifipkt*)b->rp;
-	if((w->a1[0] & 1) == 0){
-		flags |= TFlagNeedACK;
-
-		if(BLEN(b) > 512-4)
-			flags |= TFlagNeedRTS;
-
-		if((w->fc[0] & 0x0c) == 0x08 &&	ctlr->bss.id != -1){
-			sta = &ctlr->bss;
-			p = wn->actrate;
-		}
-
-		if(flags & (TFlagNeedRTS|TFlagNeedCTS)){
-			if(ctlr->family >= 7000 || ctlr->type != Type4965){
-				flags &= ~(TFlagNeedRTS|TFlagNeedCTS);
-				flags |= TFlagNeedProtection;
-			} else
-				flags |= TFlagFullTxOp;
-		}
-	}
-
-	if(sta->id == -1)
-		goto Broken;
-
-	if(p >= wifi->rates)
-		rate = p - wifi->rates;
-	else
-		rate = 0;
-
-	/* select first available antenna */
-	ant = ctlr->rfcfg.txantmask & 7;
-	ant |= (ant == 0);
-	ant = ((ant - 1) & ant) ^ ant;
-
-	memset(p = c, 0, sizeof(c));
-	put16(p, BLEN(b));
-	p += 2;
-	p += 2;		/* lnext */
-	put32(p, flags);
-	p += 4;
-	put32(p, 0);
-	p += 4;		/* scratch */
-
-	*p++ = ratetab[rate].plcp;
-	*p++ = ratetab[rate].flags | (ant<<6);
-
-	p += 2;		/* xflags */
-	*p++ = sta->id;	/* station id */
-	*p++ = 0;	/* security */
-	*p++ = 0;	/* linkq */
-	p++;		/* reserved */
-	p += 16;	/* key */
-	p += 2;		/* fnext */
-	p += 2;		/* reserved */
-	put32(p, ~0);	/* lifetime */
-	p += 4;
-
-	/* BUG: scratch ptr? not clear what this is for */
-	put32(p, PCIWADDR(ctlr->kwpage));
-	p += 5;
-
-	*p++ = 60;	/* rts ntries */
-	*p++ = 15;	/* data ntries */
-	*p++ = 0;	/* tid */
-	put16(p, 0);	/* timeout */
-	p += 2;
-	p += 2;		/* txop */
-	qunlock(ctlr);
-
-	if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){
-		print("#l%d: transmit %s\n", edev->ctlrno, err);
-		freeb(b);
-	}
-}
-
-static long
-iwlctl(Ether *edev, void *buf, long n)
-{
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(n >= 5 && memcmp(buf, "reset", 5) == 0){
-		ctlr->broken = 1;
-		return n;
-	}
-	if(ctlr->wifi)
-		return wifictl(ctlr->wifi, buf, n);
-	return 0;
-}
-
-static long
-iwlifstat(Ether *edev, void *buf, long n, ulong off)
-{
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(ctlr->wifi)
-		return wifistat(ctlr->wifi, buf, n, off);
-	return 0;
-}
-
-static void
-setoptions(Ether *edev)
-{
-	Ctlr *ctlr;
-	int i;
-
-	ctlr = edev->ctlr;
-	for(i = 0; i < edev->nopt; i++)
-		wificfg(ctlr->wifi, edev->opt[i]);
-}
-
-static void
-iwlpromiscuous(void *arg, int on)
-{
-	Ether *edev;
-	Ctlr *ctlr;
-
-	edev = arg;
-	ctlr = edev->ctlr;
-	qlock(ctlr);
-	ctlr->prom = on;
-	rxon(edev, ctlr->wifi->bss);
-	qunlock(ctlr);
-}
-
-static void
-iwlmulticast(void *, uchar*, int)
-{
-}
-
-static void
-iwlrecover(void *arg)
-{
-	Ether *edev;
-	Ctlr *ctlr;
-
-	edev = arg;
-	ctlr = edev->ctlr;
-	while(waserror())
-		;
-	for(;;){
-		tsleep(&up->sleep, return0, 0, 4000);
-
-		qlock(ctlr);
-		for(;;){
-			if(ctlr->broken == 0)
-				break;
-
-			if(ctlr->power)
-				poweroff(ctlr);
-
-			if((csr32r(ctlr, Gpc) & RfKill) == 0)
-				break;
-
-			if(reset(ctlr) != nil)
-				break;
-			if(boot(ctlr) != nil)
-				break;
-
-			rxon(edev, ctlr->wifi->bss);
-			break;
-		}
-		qunlock(ctlr);
-	}
-}
-
-static void
-iwlattach(Ether *edev)
-{
-	FWImage *fw;
-	Ctlr *ctlr;
-	char *err;
-
-	ctlr = edev->ctlr;
-	eqlock(ctlr);
-	if(waserror()){
-		print("#l%d: %s\n", edev->ctlrno, up->errstr);
-		if(ctlr->power)
-			poweroff(ctlr);
-		qunlock(ctlr);
-		nexterror();
-	}
-	if(ctlr->attached == 0){
-		if((csr32r(ctlr, Gpc) & RfKill) == 0)
-			error("wifi disabled by switch");
-
-		if(ctlr->fw == nil){
-			char *fn;
-
-			fn = ctlr->fwname;
-			if(fn == nil){
-				fn = fwname[ctlr->type];
-				if(ctlr->type == Type6005){
-					switch(ctlr->pdev->did){
-					case 0x0082:	/* Centrino Advanced-N 6205 */
-					case 0x0085:	/* Centrino Advanced-N 6205 */
-						break;
-					default:	/* Centrino Advanced-N 6030, 6235 */
-						fn = "iwn-6030";
-					}
-				}
-			}
-			fw = readfirmware(fn);
-			print("#l%d: firmware: %s, rev %ux, build %ud, size [%d] %ux+%ux + [%d] %ux+%ux + %ux\n",
-				edev->ctlrno, fn,
-				fw->rev, fw->build,
-				fw->main.nsect, fw->main.text.size, fw->main.data.size,
-				fw->init.nsect, fw->init.text.size, fw->init.data.size,
-				fw->boot.text.size);
-			ctlr->fw = fw;
-		}
-
-		if(ctlr->family >= 7000){
-			u32int u = ctlr->fw->physku;
-
-			ctlr->rfcfg.type = u & 3;	u >>= 2;
-			ctlr->rfcfg.step = u & 3;	u >>= 2;
-			ctlr->rfcfg.dash = u & 3;	u >>= 12;
-
-			ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
-			ctlr->rfcfg.rxantmask = u & 15;
-		}
-
-		if((err = reset(ctlr)) != nil)
-			error(err);
-		if((err = boot(ctlr)) != nil)
-			error(err);
-
-		if(ctlr->wifi == nil){
-			qsetlimit(edev->oq, MaxQueue);
-
-			ctlr->wifi = wifiattach(edev, transmit);
-			/* tested with 2230, it has transmit issues using higher bit rates */
-			if(ctlr->family >= 7000 || ctlr->type != Type2030)
-				ctlr->wifi->rates = iwlrates;
-		}
-
-		setoptions(edev);
-
-		ctlr->attached = 1;
-
-		kproc("iwlrecover", iwlrecover, edev);
-	}
-	qunlock(ctlr);
-	poperror();
-}
-
-static void
-updatesystime(Ctlr *ctlr, u32int ts)
-{
-	u32int dt = ts - (u32int)ctlr->systime;
-	ctlr->systime += dt;
-}
-
-static void
-receive(Ctlr *ctlr)
-{
-	Block *b, *bb;
-	uchar *d;
-	RXQ *rx;
-	TXQ *tx;
-	uint hw;
-
-	rx = &ctlr->rx;
-	if(ctlr->broken || rx->s == nil || rx->b == nil)
-		return;
-
-	for(hw = get16(rx->s) % Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){
-		int type, flags, idx, qid, 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++;
-
-		tx = nil;
-		bb = nil;
-		if((qid & 0x80) == 0 && qid < ctlr->ntxq){
-			tx = &ctlr->tx[qid];
-			bb = tx->b[idx];
-			tx->b[idx] = nil;
-		}
-
-		len &= 0x3fff;
-		len -= 4;
-		if(len >= 0) switch(type){
-		case 1:		/* microcontroller ready */
-			setfwinfo(ctlr, d, len);
-			break;
-		case 24:	/* add node done */
-			if(len < 4)
-				break;
-			break;
-		case 28:	/* tx done */
-			if(ctlr->family >= 7000){
-				if(len <= 36 || d[36] == 1 || d[36] == 2)
-					break;
-			} else if(ctlr->type == Type4965){
-				if(len <= 20 || d[20] == 1 || d[20] == 2)
-					break;
-			} else {
-				if(len <= 32 || d[32] == 1 || d[32] == 2)
-					break;
-			}
-			if(ctlr->wifi != nil)
-				wifitxfail(ctlr->wifi, bb);
-			break;
-		case 41:
-			if(len < 2*4)
-				break;
-			if(get32(d) != 0)
-				break;
-			if(ctlr->te.id == -1)
-				ctlr->te.id = get32(d+8);
-			break;
-		case 42:
-			if(len < 6*4)
-				break;
-			if(ctlr->te.id == -1 || get32(d+8) != ctlr->te.id)
-				break;
-			switch(get32(d+16)){
-			case 1:
-				ctlr->te.active = 1;
-				wakeup(&ctlr->te);
-				break;
-			case 2:
-				ctlr->te.active = 0;
-				ctlr->te.id = -1;
-				wakeup(&ctlr->te);
-			}
-			break;
-		case 102:	/* calibration result (Type5000 only) */
-			if(ctlr->family >= 7000)
-				break;
-			if(len < 4)
-				break;
-			idx = d[0];
-		Calib:
-			if(idx < 0 || idx >= nelem(ctlr->calib.cmd))
-				break;
-			if(rbplant(ctlr, rx->i) < 0)
-				break;
-			if(ctlr->calib.cmd[idx] != nil)
-				freeb(ctlr->calib.cmd[idx]);
-			b->rp = d;
-			b->wp = d + len;
-			ctlr->calib.cmd[idx] = b;
-			break;
-		case 4:		/* init complete (>= 7000 family) */
-			if(ctlr->family < 7000)
-				break;
-			/* wet floor */
-		case 103:	/* calibration done (Type5000 only) */
-			ctlr->calib.done = 1;
-			if(ctlr->wait.w == Ierr)
-				wakeup(&ctlr->wait);
-			break;
-		case 107:	/* calibration result (>= 7000 family) */
-			if(ctlr->family < 7000)
-				break;
-			len -= 4;
-			if(len < 0)
-				break;
-			idx = get16(d+2);
-			if(idx < len)
-				len = idx;
-			idx = -1;
-			switch(get16(d)){
-			case 1:
-				idx = &ctlr->calib.cfg - &ctlr->calib.cmd[0];
-				break;
-			case 2:
-				idx = &ctlr->calib.nch - &ctlr->calib.cmd[0];
-				break;
-			case 4:
-				if(len < 2)
-					break;
-				idx = &ctlr->calib.papd[get16(d+4) % nelem(ctlr->calib.papd)] - &ctlr->calib.cmd[0];
-				break;
-			case 5:
-				if(len < 2)
-					break;
-				idx = &ctlr->calib.txp[get16(d+4) % nelem(ctlr->calib.txp)] - &ctlr->calib.cmd[0];
-				break;
-			}
-			len += 4;
-			goto Calib;
-		case 130:	/* start scan */
-		case 132:	/* stop scan */
-			break;
-		case 136:	/* NVM access (>= 7000 family) */
-			if(ctlr->family < 7000)
-				break;
-			len -= 8;
-			if(len < 0)
-				break;
-			if(ctlr->nvm.len < len)
-				len = ctlr->nvm.len;
-			ctlr->nvm.off = get16(d + 0);
-			ctlr->nvm.ret = get16(d + 2);
-			ctlr->nvm.type= get16(d + 4);
-			ctlr->nvm.sts = get16(d + 6);
-			d += 8;
-			if(ctlr->nvm.ret < len)
-				len = ctlr->nvm.ret;
-			if(ctlr->nvm.buf != nil && len > 0)
-				memmove(ctlr->nvm.buf, d, len);
-			ctlr->nvm.buf = nil;
-			ctlr->nvm.len = 0;
-			break;
-		case 156:	/* rx statistics */
-		case 157:	/* beacon statistics */
-		case 161:	/* state changed */
-		case 162:	/* beacon missed */
-		case 177:	/* mduart load notification */
-			break;
-		case 192:	/* rx phy */
-			if(len >= 8)
-				updatesystime(ctlr, get32(d+4));
-			break;
-		case 195:	/* rx done */
-			if(d + 2 > b->lim)
-				break;
-			d += d[1];
-			d += 56;
-			/* wet floor */
-		case 193:	/* mpdu rx done */
-			if(d + 4 > b->lim)
-				break;
-			len = get16(d);
-			if(ctlr->mqrx){
-				if(d + 48 + len > b->lim)
-					break;
-				updatesystime(ctlr, get32(d+36));
-				if((d[12] & 3) != 3)
-					break;
-				d += 48;
-			} else {
-				d += 4;
-				if(d + len + 4 > b->lim)
-					break;
-				if((d[len] & 3) != 3)
-					break;
-			}
-			if(ctlr->wifi == nil)
-				break;
-			if(rbplant(ctlr, rx->i) < 0)
-				break;
-			b->rp = d;
-			b->wp = d + len;
-
-			put64(d - 8, ctlr->systime);
-			b->flag |= Btimestamp;
-
-			wifiiq(ctlr->wifi, b);
-			break;
-		case 197:	/* rx compressed ba */
-			break;
-		}
-		freeblist(bb);
-		if(tx != nil && tx->n > 0){
-			tx->n--;
-			wakeup(tx);
-			/* unlock 7k family nics as the command is done */
-			if(ctlr->family == 7000 && qid == 4 && tx->n == 0)
-				nicunlock(ctlr);
-		}
-	}
-
-	if(ctlr->mqrx){
-		csr32w(ctlr, FhRxQ0Wptr, ((hw+Nrx-1) % Nrx) & ~7);
-	}else
-		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 | Ialive)) || (fhisr & Ifhrx))
-		receive(ctlr);
-	if(isr & Ierr){
-		ctlr->broken = 1;
-		print("#l%d: fatal firmware error\n", edev->ctlrno);
-		dumpctlr(ctlr);
-	}
-	ctlr->wait.m |= isr;
-	if(ctlr->wait.m & ctlr->wait.w)
-		wakeup(&ctlr->wait);
-done:
-	csr32w(ctlr, Imr, ctlr->ie);
-	iunlock(ctlr);
-}
-
-static void
-iwlshutdown(Ether *edev)
-{
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(ctlr->power)
-		poweroff(ctlr);
-	ctlr->broken = 0;
-	pcidisable(ctlr->pdev);
-}
-
-static Ctlr *iwlhead, *iwltail;
-
-static void
-iwlpci(void)
-{
-	Pcidev *pdev;
-	char *fwname;
-	int family;
-	
-	pdev = nil;
-	while(pdev = pcimatch(pdev, Vintel, 0)) {
-		Ctlr *ctlr;
-		void *mem;
-		
-		if(pdev->ccrb != 2 || pdev->ccru != 0x80)
-			continue;
-		if(pdev->mem[0].bar & 1)
-			continue;
-
-		switch(pdev->did){
-		default:
-			continue;
-		case 0x0084:	/* WiFi Link 1000 */
-		case 0x4229:	/* WiFi Link 4965 */
-		case 0x4230:	/* WiFi Link 4965 */
-		case 0x4232:	/* Wifi Link 5100 */
-		case 0x4235:	/* Intel Corporation Ultimate N WiFi Link 5300 */
-		case 0x4236:	/* WiFi Link 5300 AGN */
-		case 0x4237:	/* Wifi Link 5100 AGN */
-		case 0x4239:	/* Centrino Advanced-N 6200 */
-		case 0x423d:	/* Wifi Link 5150 */
-		case 0x423b:	/* PRO/Wireless 5350 AGN */
-		case 0x0082:	/* Centrino Advanced-N 6205 */
-		case 0x0085:	/* Centrino Advanced-N 6205 */
-		case 0x0089:    /* Centrino Advanced-N + WiMAX 6250 */
-		case 0x422b:	/* Centrino Ultimate-N 6300 variant 1 */
-		case 0x4238:	/* Centrino Ultimate-N 6300 variant 2 */
-		case 0x08ae:	/* Centrino Wireless-N 100 */
-		case 0x0083:	/* Centrino Wireless-N 1000 */
-		case 0x008a:	/* Centrino Wireless-N 1030 */
-		case 0x0891:	/* Centrino Wireless-N 2200 */
-		case 0x0887:	/* Centrino Wireless-N 2230 */
-		case 0x0888:	/* Centrino Wireless-N 2230 */
-		case 0x0090:	/* Centrino Advanced-N 6030 */
-		case 0x0091:	/* Centrino Advanced-N 6030 */
-		case 0x088e:	/* Centrino Advanced-N 6235 */
-		case 0x088f:	/* Centrino Advanced-N 6235 */
-			family = 0;
-			fwname = nil;
-			break;
-		case 0x08b1:	/* Wireless AC 7260 */
-		case 0x08b2:	/* Wireless AC 7260 */
-			family = 7000;
-			fwname = "iwm-7260-17";
-			break;
-		case 0x95a:	/* Wireless AC 7265 */
-		case 0x95b:	/* Wireless AC 7265 */
-			family = 7000;
-			fwname = "iwm-7265-17";
-			break;
-		case 0x24f3:	/* Wireless AC 8260 */
-			family = 8000;
-			fwname = "iwm-8000C-34";
-			break;
-		case 0x24fd:	/* Wireless AC 8265 */
-			family = 8000;
-			fwname = "iwm-8265-34";
-			break;
-		case 0x2526:	/* Wireless AC 9260 */
-			family = 9000;
-			fwname = "iwm-9260-34";
-			break;
-		}
-
-		ctlr = malloc(sizeof(Ctlr));
-		if(ctlr == nil) {
-			print("iwl: unable to alloc Ctlr\n");
-			continue;
-		}
-		ctlr->port = pdev->mem[0].bar & ~0xF;
-		mem = vmap(ctlr->port, pdev->mem[0].size);
-		if(mem == nil) {
-			print("iwl: can't map %llux\n", ctlr->port);
-			free(ctlr);
-			continue;
-		}
-		ctlr->nic = mem;
-		ctlr->pdev = pdev;
-		ctlr->fwname = fwname;
-		ctlr->family = family;
-		ctlr->mqrx = family >= 9000;
-
-		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->edev != nil)
-			continue;
-		if(edev->port == 0 || edev->port == ctlr->port){
-			ctlr->edev = edev;
-			break;
-		}
-	}
-
-	if(ctlr == nil)
-		return -1;
-
-	edev->ctlr = ctlr;
-	edev->port = ctlr->port;
-	edev->irq = ctlr->pdev->intl;
-	edev->tbdf = ctlr->pdev->tbdf;
-	edev->arg = edev;
-	edev->attach = iwlattach;
-	edev->ifstat = iwlifstat;
-	edev->ctl = iwlctl;
-	edev->shutdown = iwlshutdown;
-	edev->promiscuous = iwlpromiscuous;
-	edev->multicast = iwlmulticast;
-	edev->mbps = 54;
-
-	pcienable(ctlr->pdev);
-	if(iwlinit(edev) < 0){
-		pcidisable(ctlr->pdev);
-		ctlr->edev = (void*)-1;
-		edev->ctlr = nil;
-		goto again;
-	}
-
-	pcisetbme(ctlr->pdev);
-	intrenable(edev->irq, iwlinterrupt, edev, edev->tbdf, edev->name);
-	
-	return 0;
-}
-
-void
-etheriwllink(void)
-{
-	addethercard("iwl", iwlpnp);
-}
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -134,7 +134,6 @@
 ethervt6102.$O:			../port/ethermii.h
 ethervt6105m.$O:		../port/ethermii.h
 etherm10g.$O:			etherm10g2k.i etherm10g4k.i
-etheriwl.$O:			../port/wifi.h
 etherwpi.$O:			../port/wifi.h
 etherrt2860.$O: 		../port/wifi.h
 l.$O rebootcode.$O apbootstrap.$O:	mem.h
--- a/sys/src/9/pc64/mkfile
+++ b/sys/src/9/pc64/mkfile
@@ -138,7 +138,6 @@
 ethervt6102.$O:			../port/ethermii.h
 ethervt6105m.$O:		../port/ethermii.h
 
-etheriwl.$O:			../port/wifi.h
 etherwpi.$O:			../port/wifi.h
 etherrt2860.$O: 		../port/wifi.h
 
--- /dev/null
+++ b/sys/src/9/port/etheriwl.c
@@ -1,0 +1,4565 @@
+/*
+ * Intel WiFi Link driver.
+ *
+ * Written without any documentation but Damien Bergamini's
+ * iwn(4) and Stefan Sperling's iwm(4) OpenBSD driver sources.
+ * Requires Intel firmware to be present in /lib/firmware/iw[nm]-*
+ * on attach.
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/wifi.h"
+
+enum {
+	MaxQueue	= 24*1024,	/* total buffer is 2*MaxQueue: 48k at 22Mbit ≅ 20ms */
+
+	Ntxlog		= 8,
+	Ntx		= 1<<Ntxlog,
+	Ntxqmax		= MaxQueue/1500,
+
+	Nrxlog		= 8,
+	Nrx		= 1<<Nrxlog,
+
+	Rstatsize	= 16,
+
+	Rbufsize	= 4*1024,
+	Rdscsize	= 8,
+
+	Tdscsize	= 128,
+	Tcmdsize	= 140,
+
+	FWPageshift	= 12,
+	FWPagesize	= 1<<FWPageshift,
+	FWBlockshift	= 3,
+	FWBlockpages	= 1<<FWBlockshift,
+	FWBlocksize	= 1<<(FWBlockshift + FWPageshift),
+};
+
+/* registers */
+enum {
+	Cfg		= 0x000,	/* config register */
+		CfgMacDashShift	= 0,
+		CfgMacDashMask	= 3<<CfgMacDashShift,
+		CfgMacStepShift	= 2,
+		CfgMacStepMask	= 3<<CfgMacStepShift,
+
+		MacSi		= 1<<8,
+		RadioSi		= 1<<9,
+
+		CfgPhyTypeShift	= 10,
+		CfgPhyTypeMask	= 3<<CfgPhyTypeShift,
+		CfgPhyDashShift	= 12,
+		CfgPhyDashMask	= 3<<CfgPhyDashShift,
+		CfgPhyStepShift	= 14,
+		CfgPhyStepMask	= 3<<CfgPhyStepShift,
+
+		EepromLocked	= 1<<21,
+		NicReady	= 1<<22,
+		HapwakeL1A	= 1<<23,
+		PrepareDone	= 1<<25,
+		Prepare		= 1<<27,
+		EnablePme	= 1<<28,
+
+	Isr		= 0x008,	/* interrupt status */
+	Imr		= 0x00c,	/* interrupt mask */
+		Ialive		= 1<<0,
+		Iwakeup		= 1<<1,
+		Iswrx		= 1<<3,
+		Ictreached	= 1<<6,
+		Irftoggled	= 1<<7,
+		Iswerr		= 1<<25,
+		Isched		= 1<<26,
+		Ifhtx		= 1<<27,
+		Irxperiodic	= 1<<28,
+		Ihwerr		= 1<<29,
+		Ifhrx		= 1<<31,
+
+		Ierr		= Iswerr | Ihwerr,
+		Idefmask	= Ierr | Ifhtx | Ifhrx | Ialive | Iwakeup | Iswrx | Ictreached | Irftoggled,
+
+	FhIsr		= 0x010,	/* second interrupt status */
+
+	Reset		= 0x020,
+		
+	Rev		= 0x028,	/* hardware revision */
+
+	EepromIo	= 0x02c,	/* EEPROM i/o register */
+	EepromGp	= 0x030,
+		
+	OtpromGp	= 0x034,
+		DevSelOtp	= 1<<16,
+		RelativeAccess	= 1<<17,
+		EccCorrStts	= 1<<20,
+		EccUncorrStts	= 1<<21,
+
+	Gpc		= 0x024,	/* gp cntrl */
+		MacAccessEna	= 1<<0,
+		MacClockReady	= 1<<0,
+		InitDone	= 1<<2,
+		MacAccessReq	= 1<<3,
+		NicSleep	= 1<<4,
+		RfKill		= 1<<27,
+
+	Gio		= 0x03c,
+		EnaL0S		= 1<<1,
+
+	GpDrv	= 0x050,
+		GpDrvCalV6	= 1<<2,
+		GpDrv1X2	= 1<<3,
+		GpDrvRadioIqInvert	= 1<<7, 
+
+	Led		= 0x094,
+		LedBsmCtrl	= 1<<5,
+		LedOn		= 0x38,
+		LedOff		= 0x78,
+
+	UcodeGp1Clr	= 0x05c,
+		UcodeGp1RfKill		= 1<<1,
+		UcodeGp1CmdBlocked	= 1<<2,
+		UcodeGp1CtempStopRf	= 1<<3,
+
+	ShadowRegCtrl	= 0x0a8,
+
+	MboxSet	= 0x088,
+		MboxSetOsAlive	= 1<<5,
+
+	Giochicken	= 0x100,
+		L1AnoL0Srx	= 1<<23,
+		DisL0Stimer	= 1<<29,
+
+	AnaPll		= 0x20c,
+
+	Dbghpetmem	= 0x240,
+	Dbglinkpwrmgmt	= 0x250,
+
+	MemRaddr	= 0x40c,
+	MemWaddr	= 0x410,
+	MemWdata	= 0x418,
+	MemRdata	= 0x41c,
+
+	PrphWaddr	= 0x444,
+	PrphRaddr	= 0x448,
+	PrphWdata	= 0x44c,
+	PrphRdata	= 0x450,
+
+	HbusTargWptr	= 0x460,
+
+	UcodeLoadStatus	= 0x1af0,
+};
+
+/*
+ * Flow-Handler registers.
+ */
+enum {
+	FhTfbdCtrl0	= 0x1900,	// +q*8
+	FhTfbdCtrl1	= 0x1904,	// +q*8
+
+	FhKwAddr	= 0x197c,
+
+	FhSramAddr	= 0x19a4,	// +q*4
+
+	FhCbbcQueue0	= 0x19d0,	// +q*4
+	FhCbbcQueue16	= 0x1bf0,	// +q*4
+	FhCbbcQueue20	= 0x1b20,	// +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,
+
+	FhRxQ0Wptr	= 0x1c80,	// +q*4 (9000 mqrx)
+
+	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,
+	FhTxErrors	= 0x1eb8,
+};
+
+/*
+ * 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,
+
+	/* 8000 family */
+	ReleaseCpuReset	= 0x300c,
+		CpuResetBit = 0x1000000,
+
+	LmpmChick	= 0xa01ff8,
+		ExtAddr = 1,
+
+	SbCpu1Status	= 0xa01e30,
+	SbCpu2Status	= 0xa01e34,
+	OscClk		= 0xa04068,
+		OscClkCtrl	= 1<<3,
+	UregChick	= 0xa05c00,
+		UregChickMsiEnable	= 1<<24,
+
+	FhUcodeLoadStatus=0xa05c40,
+};
+
+/*
+ * RX ring for mqrx 9000
+*/
+enum {
+	RfhQ0FreeBase	= 0xa08000,	// +q*8
+	RfhQ0FreeWptr	= 0xa08080,	// +q*4
+	RfhQ0FreeRptr	= 0xa080c0,	// +q*4
+
+	RfhQ0UsedBase	= 0xa08100,	// +q*8
+	RfhQ0UsedWptr	= 0xa08180,	// +q*4
+
+	RfhQ0SttsBase	= 0xa08200,	// +q*8
+
+	RfhGenCfg	= 0xa09800,
+		RfhGenServiceDmaSnoop	= 1<<0,
+		RfhGenRfhDmaSnoop	= 1<<1,
+		RfhGenRbChunkSize64	= 0<<4,
+		RfhGenRbChunkSize128	= 1<<4,
+
+	RfhGenStatus	= 0xa09808,
+		RfhGenStatusDmaIdle	= 1<<31,
+
+	RfhRxqActive	= 0xa0980c,
+
+	RfhDmaCfg	= 0xa09820,
+		RfhDma1KSizeShift	= 16,
+		RfhDmaNrbdShift		= 20,
+		RfhDmaMinRbSizeShift	= 24,
+		RfhDmaDropTooLarge	= 1<<26,
+		RfhDmaEnable		= 1<<31,
+};
+
+/*
+ * TX scheduler registers.
+ */
+enum {
+	SchedBase		= 0xa02c00,
+	SchedSramAddr		= SchedBase,
+
+	SchedDramAddr4965	= SchedBase+0x010,
+	SchedTxFact4965		= SchedBase+0x01c,
+	SchedQueueRdptr4965	= SchedBase+0x064,	// +q*4
+	SchedQChainSel4965	= SchedBase+0x0d0,
+	SchedIntrMask4965	= SchedBase+0x0e4,
+	SchedQueueStatus4965	= SchedBase+0x104,	// +q*4
+
+	SchedDramAddr		= SchedBase+0x008,
+	SchedTxFact		= SchedBase+0x010,
+	SchedQueueWrptr		= SchedBase+0x018,	// +q*4
+	SchedQueueRdptr		= SchedBase+0x068,	// +q*4
+	SchedQChainSel		= SchedBase+0x0e8,
+	SchedIntrMask		= SchedBase+0x108,
+
+	SchedQueueStatus	= SchedBase+0x10c,	// +q*4
+
+	SchedGpCtrl		= SchedBase+0x1a8,
+		Enable31Queues	= 1<<0,
+		AutoActiveMode	= 1<<18,
+
+	SchedChainExtEn		= SchedBase+0x244,
+	SchedAggrSel		= SchedBase+0x248,
+	SchedEnCtrl		= SchedBase+0x254,
+
+	SchedQueueRdptr20	= SchedBase+0x2b4,	// +q*4
+	SchedQueueStatus20	= SchedBase+0x334,	// +q*4
+};
+
+enum {
+	SchedCtxOff4965		= 0x380,
+	SchedCtxLen4965		= 416,
+
+	SchedCtxOff		= 0x600,		// +q*8
+
+	SchedSttsOff		= 0x6A0,		// +q*16
+
+	SchedTransTblOff	= 0x7E0,		// +q*2
+};
+
+/*
+ * uCode TLV api
+ */
+enum {
+	/* api[0] */
+	UcodeApiSta	= 1<<30,
+};
+
+/*
+ * uCode capabilities
+ */
+enum {
+	/* capa[0] */
+	UcodeCapLar	= 1<<1,
+
+	/* capa[1] */
+	UcodeCapQuota	= 1<<12,
+	
+	/* capa[2] */
+	UcodeCapLar2	= 1<<9,
+};
+
+enum {
+	FilterPromisc		= 1<<0,
+	FilterCtl		= 1<<1,
+	FilterMulticast		= 1<<2,
+	FilterNoDecrypt		= 1<<3,
+	FilterNoDecryptMcast	= 1<<4,
+	FilterBSS		= 1<<5,
+	FilterBeacon		= 1<<6,
+};
+
+enum {
+	RFlag24Ghz		= 1<<0,
+	RFlagCCK		= 1<<1,
+	RFlagAuto		= 1<<2,
+	RFlagShSlot		= 1<<4,
+	RFlagShPreamble		= 1<<5,
+	RFlagNoDiversity	= 1<<7,
+	RFlagAntennaA		= 1<<8,
+	RFlagAntennaB		= 1<<9,
+	RFlagTSF		= 1<<15,
+	RFlagCTSToSelf		= 1<<30,
+};
+
+enum {
+	TFlagNeedProtection	= 1<<0,
+		TFlagNeedRTS		= 1<<1,
+		TFlagNeedCTS		= 1<<2,
+	TFlagNeedACK		= 1<<3,
+	TFlagLinkq		= 1<<4,
+	TFlagImmBa		= 1<<6,
+	TFlagFullTxOp		= 1<<7,
+	TFlagBtDis		= 1<<12,
+	TFlagAutoSeq		= 1<<13,
+	TFlagMoreFrag		= 1<<14,
+	TFlagInsertTs		= 1<<16,
+	TFlagNeedPadding	= 1<<20,
+};
+
+enum {
+	CmdAdd = 1,
+	CmdModify,
+	CmdRemove,
+};
+
+typedef struct FWInfo FWInfo;
+typedef struct FWImage FWImage;
+typedef struct FWSect FWSect;
+typedef struct FWBlock FWBlock;
+typedef struct FWMem FWMem;
+
+typedef struct TXQ TXQ;
+typedef struct RXQ RXQ;
+
+typedef struct Station Station;
+
+typedef struct Ctlr Ctlr;
+
+struct FWSect
+{
+	uchar	*data;
+	uint	addr;
+	uint	size;
+};
+
+struct FWImage
+{
+	struct {
+		int	nsect;
+		union {
+			struct {
+				FWSect	text;
+				FWSect	data;
+			};
+			FWSect	sect[16];
+		};
+		struct {
+			u32int	flowmask;
+			u32int	eventmask;
+		} defcalib;
+	} init, main;
+
+	struct {
+		FWSect	text;
+	} boot;
+
+	uint	rev;
+	uint	build;
+	char	descr[64+1];
+
+	u32int	flags;
+	u32int	capa[4];
+	u32int	api[4];
+
+	u32int	physku;
+
+	u32int	pagedmemsize;
+
+	uchar	data[];
+};
+
+struct FWInfo
+{
+	int	valid;
+
+	u16int	status;
+	u16int	flags;
+
+	u32int	major;
+	u32int	minor;
+	uchar	type;
+	uchar	subtype;
+
+	u32int	scdptr;
+	u32int	regptr;
+	u32int	logptr;
+	u32int	errptr;
+	u32int	tstamp;
+
+	struct {
+		u32int	major;
+		u32int	minor;
+		u32int	errptr;
+		u32int	logptr;
+	} umac;
+};
+
+struct FWBlock
+{
+	uint	size;
+	uchar	*p;
+};
+
+struct FWMem
+{
+	uchar	*css;
+
+	uint	npage;
+	uint	nblock;
+
+	FWBlock	block[32];
+};
+
+struct TXQ
+{
+	uint	n;
+	uint	i;
+	Block	**b;
+	uchar	*d;
+	uchar	*c;
+
+	uint	lastcmd;
+
+	Rendez;
+	QLock;
+};
+
+struct RXQ
+{
+	uint	i;
+	int	psz;
+	Block	**b;
+	uchar	*s;
+	uchar	*p;
+	uchar	*u;
+};
+
+struct Station
+{
+	int	id;
+};
+
+struct Ctlr {
+	Lock;
+	QLock;
+
+	Ctlr *link;
+	uvlong port;
+	Pcidev *pdev;
+	Ether *edev;
+	Wifi *wifi;
+
+	char *fwname;
+	int mqrx;
+	int family;
+	int type;
+	uint step;
+	uint dash;
+
+	int power;
+	int broken;
+	int attached;
+
+	u32int ie;
+
+	u32int *nic;
+	uchar *kwpage;
+
+	/* assigned sta ids in hardware sta table or -1 if unassigned */
+	Station bcast;
+	Station bss;
+
+	u32int rxflags;
+	u32int rxfilter;
+
+	int phyid;
+	int macid;
+	int bindid;
+
+	/* current receiver settings */
+	uchar bssid[Eaddrlen];
+	int channel;
+	int prom;
+	int aid;
+
+	struct {
+		Rendez;
+		int id;
+		int active;
+	} te;
+
+	uvlong systime;
+
+	RXQ rx;
+	TXQ tx[7];
+
+	int ndma;
+	int ntxq;
+
+	struct {
+		Rendez;
+		u32int	m;
+		u32int	w;
+	} wait;
+
+	struct {
+		uchar	type;
+		uchar	step;
+		uchar	dash;
+		uchar	pnum;
+		uchar	txantmask;
+		uchar	rxantmask;
+	} rfcfg;
+
+	struct {
+		int	otp;
+		uint	off;
+
+		uchar	version;
+		uchar	type;
+		u16int	volt;
+		u16int	temp;
+		u16int	rawtemp;
+
+		char	regdom[4+1];
+
+		u32int	crystal;
+	} eeprom;
+
+	struct {
+		u32int	version;
+
+		void	*buf;
+		int	len;
+
+		int	off;
+		int	ret;
+		int	type;
+		int	sts;
+	} nvm;
+
+	struct {
+		union {
+			Block	*cmd[21];
+			struct {
+				Block *cfg;
+				Block *nch;
+				Block *papd[9];
+				Block *txp[9];
+			};
+		};
+		int	done;
+	} calib;
+
+	struct {
+		u32int	base;
+		uchar	*s;
+	} sched;
+
+	FWInfo fwinfo;
+	FWImage *fw;
+
+	FWMem fwmem;
+};
+
+/* controller types */
+enum {
+	Type4965	= 0,
+	Type5300	= 2,
+	Type5350	= 3,
+	Type5150	= 4,
+	Type5100	= 5,
+	Type1000	= 6,
+	Type6000	= 7,
+	Type6050	= 8,
+	Type6005	= 11,	/* also Centrino Advanced-N 6030, 6235 */
+	Type2030	= 12,
+	Type2000	= 16,
+
+	Type7260	= 20,
+};
+
+static struct ratetab {
+	uchar	rate;
+	uchar	plcp;
+	uchar	flags;
+} ratetab[] = {
+	{   2,  10, RFlagCCK },
+	{   4,  20, RFlagCCK },
+	{  11,  55, RFlagCCK },
+	{  22, 110, RFlagCCK },
+
+	{  12, 0xd, 0 },
+	{  18, 0xf, 0 },
+	{  24, 0x5, 0 },
+	{  36, 0x7, 0 },
+	{  48, 0x9, 0 },
+	{  72, 0xb, 0 },
+	{  96, 0x1, 0 },
+	{ 108, 0x3, 0 },
+	{ 120, 0x3, 0 }
+};
+
+static uchar iwlrates[] = {
+	0x80 | 2,
+	0x80 | 4,
+	0x80 | 11,
+	0x80 | 22,
+
+	0x80 | 12,
+	0x80 | 18,
+	0x80 | 24,
+	0x80 | 36,
+	0x80 | 48,
+	0x80 | 72,
+	0x80 | 96,
+	0x80 | 108,
+	0x80 | 120,
+	0
+};
+
+static char *fwname[32] = {
+	[Type4965] "iwn-4965",
+	[Type5300] "iwn-5000",
+	[Type5350] "iwn-5000",
+	[Type5150] "iwn-5150",
+	[Type5100] "iwn-5000",
+	[Type1000] "iwn-1000",
+	[Type6000] "iwn-6000",
+	[Type6050] "iwn-6050",
+	[Type6005] "iwn-6005", /* see in iwlattach() below */
+	[Type2030] "iwn-2030",
+	[Type2000] "iwn-2000",
+};
+
+static char *qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block);
+static char *flushq(Ctlr *ctlr, uint qid);
+static char *cmd(Ctlr *ctlr, uint code, uchar *data, int size);
+
+#define csr32r(c, r)	(*((c)->nic+((r)/4)))
+#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))
+
+static uint
+get16(uchar *p){
+	return *((u16int*)p);
+}
+static uint
+get32(uchar *p){
+	return *((u32int*)p);
+}
+static void
+put32(uchar *p, uint v){
+	*((u32int*)p) = v;
+}
+static void
+put64(uchar *p, uvlong v)
+{
+	*((u64int*)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);
+	if(ctlr->family >= 8000)
+		microdelay(2);
+	for(i=0; i<1500; i++){
+		if((csr32r(ctlr, Gpc) & (NicSleep | MacAccessEna)) == MacAccessEna)
+			return 0;
+		microdelay(10);
+	}
+	return "niclock: timeout";
+}
+
+static void
+nicunlock(Ctlr *ctlr)
+{
+	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~MacAccessReq);
+}
+
+static u32int
+prphread(Ctlr *ctlr, uint off)
+{
+	off &= 0xfffff;
+	csr32w(ctlr, PrphRaddr, ((sizeof(u32int)-1)<<24) | off);
+	coherence();
+	return csr32r(ctlr, PrphRdata);
+}
+static void
+prphwrite(Ctlr *ctlr, uint off, u32int data)
+{
+	off &= 0xfffff;
+	csr32w(ctlr, PrphWaddr, ((sizeof(u32int)-1)<<24) | off);
+	coherence();
+	csr32w(ctlr, PrphWdata, data);
+}
+
+static void
+prphwrite64(Ctlr *ctlr, uint off, u64int data)
+{
+	prphwrite(ctlr, off, data & 0xFFFFFFFF);
+	prphwrite(ctlr, off+4, data >> 32);
+}
+
+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;
+
+	i = &ctlr->fwinfo;
+	switch(len){
+	case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4:
+	case 2+2 + 1+1+2+1+1 + 1+1 + 1+1 + 2 + 4+4+4+4+4+4 + 4 + 4+4 + 1+1 + 2 + 4+4:
+		i->status = get16(d); d += 2;
+		i->flags = get16(d); d += 2;
+
+		i->minor = *d++;
+		i->major = *d++;
+		d += 2;					// id
+		d++;					// api minor
+		d++;					// api major
+		i->subtype = *d++;
+		i->type = *d++;
+		d++;					// mac
+		d++;					// opt
+		d += 2;					// reserved2
+
+		i->tstamp = get32(d); d += 4;
+		i->errptr = get32(d); d += 4;
+		i->logptr = get32(d); d += 4;
+		i->regptr = get32(d); d += 4;
+		d += 4;					// dbgm_config_ptr
+		d += 4;					// alive counter ptr
+
+		i->scdptr = get32(d); d += 4;
+
+		if(len < 1+1+2+1+1+1+1+1+1+2+4+4+4+4+4 + 4+4+4+1+1+2+4+4)
+			break;
+
+		d += 4;					// fwrd addr
+		d += 4;					// fwrd size
+
+		i->umac.minor = *d++;
+		i->umac.major = *d++;
+		d++;					// id
+		d += 2;
+		i->umac.errptr = get32(d); d += 4;
+		i->umac.logptr = get32(d); d += 4;
+
+		i->valid = (i->status == 0xcafe);
+		break;
+
+	case 2+2 + 4+4 + 1+1 + 1+1 + 4+4+4+4+4+4 + 4 + 4+4 + 4+4+4+4:
+		i->status = get16(d); d += 2;
+		i->flags = get16(d); d += 2;
+
+		i->minor = get32(d);
+		d += 4;
+		i->major = get32(d);
+		d += 4;
+		i->subtype = *d++;
+		i->type = *d++;
+		d++;					// mac
+		d++;					// opt
+
+		i->tstamp = get32(d); d += 4;
+		i->errptr = get32(d); d += 4;
+		i->logptr = get32(d); d += 4;
+		i->regptr = get32(d); d += 4;
+		d += 4;					// dbgm_config_ptr
+		d += 4;					// alive counter ptr
+
+		i->scdptr = get32(d);
+		d += 4;
+
+		d += 4;					// fwrd addr
+		d += 4;					// fwrd size
+
+		i->umac.minor = get32(d); d += 4;
+		i->umac.major = get32(d); d += 4;
+		i->umac.errptr = get32(d); d += 4;
+		i->umac.logptr = get32(d); d += 4;
+
+		i->valid = (i->status == 0xcafe);
+		break;
+	
+	default:
+		if(len < 32)
+			break;
+		i->minor = *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 = 1;
+	}
+	USED(d);
+}
+
+static void
+printfwinfo(Ctlr *ctlr)
+{
+	FWInfo *i = &ctlr->fwinfo;
+
+	print("fwinfo: status=%.4ux flags=%.4ux\n",
+		i->status, i->flags);
+
+	print("fwinfo: ver %ud.%ud type %ud.%ud\n",
+		i->major, i->minor, i->type, i->subtype);
+	print("fwinfo: scdptr=%.8ux\n", i->scdptr);
+	print("fwinfo: regptr=%.8ux\n", i->regptr);
+	print("fwinfo: logptr=%.8ux\n", i->logptr);
+	print("fwinfo: errptr=%.8ux\n", i->errptr);
+
+	print("fwinfo: ts=%.8ux\n", i->tstamp);
+
+	print("fwinfo: umac ver %ud.%ud\n", i->umac.major, i->umac.minor);
+	print("fwinfo: umac errptr %.8ux\n", i->umac.errptr);
+	print("fwinfo: umac logptr %.8ux\n", i->umac.logptr);
+}
+
+static void
+dumpctlr(Ctlr *ctlr)
+{
+	u32int dump[13];
+	int i;
+
+	print("lastcmd: %ud (0x%ux)\n", ctlr->tx[4].lastcmd,  ctlr->tx[4].lastcmd);
+
+	if(!ctlr->fwinfo.valid || 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);
+
+	if(ctlr->family >= 7000){
+		print(	"error:\tid %ux, trm_hw_status %.8ux %.8ux,\n"
+			"\tbranchlink2 %.8ux, interruptlink %.8ux %.8ux,\n"
+			"\terrordata %.8ux %.8ux %.8ux\n",
+			dump[1], dump[2], dump[3],
+			dump[4], dump[5], dump[6],
+			dump[7], dump[8], dump[9]);
+	} else {
+		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;
+			microdelay(10);
+		}
+	}
+	return "eepromlock: timeout";
+}
+static void
+eepromunlock(Ctlr *ctlr)
+{
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) & ~EepromLocked);
+}
+static char*
+eepromread(Ctlr *ctlr, void *data, int count, uint off)
+{
+	uchar *out = data;
+	u32int w, s;
+	int i;
+
+	w = 0;
+	off += ctlr->eeprom.off;
+	for(; count > 0; count -= 2, off++){
+		csr32w(ctlr, EepromIo, off << 2);
+		for(i=0; i<10; i++){
+			w = csr32r(ctlr, EepromIo);
+			if(w & 1)
+				break;
+			microdelay(5);
+		}
+		if(i == 10)
+			return "eepromread: timeout";
+		if(ctlr->eeprom.otp){
+			s = csr32r(ctlr, OtpromGp);
+			if(s & EccUncorrStts)
+				return "eepromread: otprom ecc error";
+			if(s & EccCorrStts)
+				csr32w(ctlr, OtpromGp, s);
+		}
+		*out++ = w >> 16;
+		if(count > 1)
+			*out++ = w >> 24;
+	}
+	return 0;
+}
+
+static char*
+handover(Ctlr *ctlr)
+{
+	int i, j;
+
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
+	for(i=0; i<5; i++){
+		if(csr32r(ctlr, Cfg) & NicReady)
+			goto Ready;
+		microdelay(10);
+	}
+
+	if(ctlr->family >= 7000){
+		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
+		delay(1);
+	}
+
+	csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare);
+	for(i=0; i<750; i++){
+		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | NicReady);
+		for(j=0; j<5; j++){
+			if(csr32r(ctlr, Cfg) & NicReady)
+				goto Ready;
+			microdelay(10);
+		}
+		microdelay(200);
+	}
+
+	return "handover: timeout";
+Ready:
+	if(ctlr->family >= 7000)
+		csr32w(ctlr, MboxSet, csr32r(ctlr, MboxSet) | MboxSetOsAlive);
+	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 0;
+		microdelay(10);
+	}
+	return "clockwait: timeout";
+}
+
+static char*
+poweron(Ctlr *ctlr)
+{
+	int capoff;
+	char *err;
+
+	if(ctlr->family >= 7000){
+		/* Reset entire device */
+		csr32w(ctlr, Reset, (1<<7));
+		delay(5);
+	}
+
+	if(ctlr->family < 8000){
+		/* 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->family < 7000){
+		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(ctlr->mqrx){
+		/* Newer cards default to MSIX? */
+		if((err = niclock(ctlr)) != nil)
+			return err;
+		prphwrite(ctlr, UregChick, UregChickMsiEnable);
+		nicunlock(ctlr);
+	}
+
+	/* Enable the oscillator to count wake up time for L1 exit. (weird W/A) */
+	if(ctlr->type == Type7260){
+		if((err = niclock(ctlr)) != nil)
+			return err;
+
+		prphread(ctlr, OscClk);
+		prphread(ctlr, OscClk);
+		microdelay(20);
+
+		prphwrite(ctlr, OscClk, prphread(ctlr, OscClk) | OscClkCtrl);
+
+		prphread(ctlr, OscClk);
+		prphread(ctlr, OscClk);
+
+		nicunlock(ctlr);
+	}
+
+	if(ctlr->family < 8000){
+		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);
+		microdelay(20);
+
+		/* Disable L1-Active. */
+		prphwrite(ctlr, ApmgPciStt, prphread(ctlr, ApmgPciStt) | (1<<11));
+
+		nicunlock(ctlr);
+	}
+
+	ctlr->power = 1;
+
+	return 0;
+}
+
+static void
+poweroff(Ctlr *ctlr)
+{
+	int i, j;
+
+	csr32w(ctlr, Reset, 1);
+
+	/* Disable interrupts */
+	ctlr->ie = 0;
+	csr32w(ctlr, Imr, 0);
+	csr32w(ctlr, Isr, ~0);
+	csr32w(ctlr, FhIsr, ~0);
+
+	/* Stop scheduler */
+	if(ctlr->family >= 7000 || ctlr->type != Type4965)
+		prphwrite(ctlr, SchedTxFact, 0);
+	else
+		prphwrite(ctlr, SchedTxFact4965, 0);
+
+	/* Stop TX ring */
+	if(niclock(ctlr) == nil){
+		for(i = 0; i < ctlr->ndma; i++){
+			csr32w(ctlr, FhTxConfig + i*32, 0);
+			for(j = 0; j < 200; j++){
+				if(csr32r(ctlr, FhTxStatus) & (0x10000<<i))
+					break;
+				microdelay(20);
+			}
+		}
+		nicunlock(ctlr);
+	}
+
+	/* Stop RX ring */
+	if(niclock(ctlr) == nil){
+		if(ctlr->mqrx){
+			prphwrite(ctlr, RfhDmaCfg, 0);
+			for(j = 0; j < 1000; j++){
+				if(prphread(ctlr, RfhGenStatus) & RfhGenStatusDmaIdle)
+					break;
+				microdelay(10);
+			}
+		} else {
+			csr32w(ctlr, FhRxConfig, 0);
+			for(j = 0; j < 1000; j++){
+				if(csr32r(ctlr, FhRxStatus) & 0x1000000)
+					break;
+				microdelay(10);
+			}
+		}
+		nicunlock(ctlr);
+	}
+
+	if(ctlr->family <= 7000){
+		/* Disable DMA */
+		if(niclock(ctlr) == nil){
+			prphwrite(ctlr, ApmgClkDis, DmaClkRqt);
+			nicunlock(ctlr);
+		}
+		microdelay(5);
+	}
+
+	if(ctlr->family >= 7000){
+		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
+		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | Prepare|EnablePme);
+		delay(1);
+		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) & ~(1<<31));
+		delay(5);
+	}
+
+	/* Stop busmaster DMA activity. */
+	csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<9));
+	for(j = 0; j < 100; j++){
+		if(csr32r(ctlr, Reset) & (1<<8))
+			break;
+		microdelay(10);
+	}
+
+	/* Reset the entire device. */
+	csr32w(ctlr, Reset, csr32r(ctlr, Reset) | (1<<7));
+	delay(5);
+
+	/* Clear "initialization complete" bit. */
+	csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) & ~InitDone);
+
+	ctlr->power = 0;
+}
+
+static char*
+rominit(Ctlr *ctlr)
+{
+	uint prev, last;
+	uchar buf[2];
+	char *err;
+	int i;
+
+	ctlr->eeprom.otp = 0;
+	ctlr->eeprom.off = 0;
+	if(ctlr->type < Type1000 || (csr32r(ctlr, OtpromGp) & DevSelOtp) == 0)
+		return nil;
+
+	/* Wait for clock stabilization before accessing prph. */
+	if((err = clockwait(ctlr)) != nil)
+		return err;
+
+	if((err = niclock(ctlr)) != nil)
+		return err;
+	prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | ResetReq);
+	microdelay(5);
+	prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) & ~ResetReq);
+	nicunlock(ctlr);
+
+	/* Set auto clock gate disable bit for HW with OTP shadow RAM. */
+	if(ctlr->type != Type1000)
+		csr32w(ctlr, Dbglinkpwrmgmt, csr32r(ctlr, Dbglinkpwrmgmt) | (1<<31));
+
+	csr32w(ctlr, EepromGp, csr32r(ctlr, EepromGp) & ~0x00000180);
+
+	/* Clear ECC status. */
+	csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) | (EccCorrStts | EccUncorrStts));
+
+	ctlr->eeprom.otp = 1;
+	if(ctlr->type != Type1000)
+		return nil;
+
+	/* Switch to absolute addressing mode. */
+	csr32w(ctlr, OtpromGp, csr32r(ctlr, OtpromGp) & ~RelativeAccess);
+
+	/*
+	 * Find the block before last block (contains the EEPROM image)
+	 * for HW without OTP shadow RAM.
+	 */
+	prev = last = 0;
+	for(i=0; i<3; i++){
+		if((err = eepromread(ctlr, buf, 2, last)) != nil)
+			return err;
+		if(get16(buf) == 0)
+			break;
+		prev = last;
+		last = get16(buf);
+	}
+	if(i == 0 || i >= 3)
+		return "rominit: missing eeprom image";
+
+	ctlr->eeprom.off = prev+1;
+	return nil;
+}
+
+static int
+iwlinit(Ether *edev)
+{
+	Ctlr *ctlr;
+	char *err;
+	uchar b[4];
+	uint u, caloff, regoff;
+
+	ctlr = edev->ctlr;
+
+	/* Clear device-specific "PCI retry timeout" register (41h). */
+	if(pcicfgr8(ctlr->pdev, 0x41) != 0)
+		pcicfgw8(ctlr->pdev, 0x41, 0);
+
+	/* Clear interrupt disable bit. Hardware bug workaround. */
+	if(ctlr->pdev->pcr & 0x400){
+		ctlr->pdev->pcr &= ~0x400;
+		pcicfgw16(ctlr->pdev, PciPCR, ctlr->pdev->pcr);
+	}
+
+	ctlr->type = csr32r(ctlr, Rev);
+	if(ctlr->family >= 8000){
+		ctlr->type &= 0xFFFF;
+		ctlr->dash = 0;
+		ctlr->step = ctlr->type & 15, ctlr->type >>= 4;
+	} else {
+		ctlr->type &= 0x1FF;
+		ctlr->dash = ctlr->type & 3, ctlr->type >>= 2;
+		ctlr->step = ctlr->type & 3, ctlr->type >>= 2;
+		if(ctlr->fwname == nil && fwname[ctlr->type] == nil){
+			print("iwl: unsupported controller type %d\n", ctlr->type);
+			return -1;
+		}
+	}
+
+	if((err = handover(ctlr)) != nil)
+		goto Err;
+
+	/* >= 7000 family needs firmware loaded to access NVM */
+	if(ctlr->family >= 7000)
+		return 0;
+
+	if((err = poweron(ctlr)) != nil)
+		goto Err;
+
+	if((csr32r(ctlr, EepromGp) & 0x7) == 0){
+		err = "bad rom signature";
+		goto Err;
+	}
+	if((err = eepromlock(ctlr)) != nil)
+		goto Err;
+	if((err = rominit(ctlr)) != nil)
+		goto Err2;
+	if((err = eepromread(ctlr, edev->ea, sizeof(edev->ea), 0x15)) != nil){
+		eepromunlock(ctlr);
+		goto Err;
+	}
+	if((err = eepromread(ctlr, b, 2, 0x048)) != nil){
+	Err2:
+		eepromunlock(ctlr);
+		goto Err;
+	}
+	u = get16(b);
+	ctlr->rfcfg.type = u & 3;	u >>= 2;
+	ctlr->rfcfg.step = u & 3;	u >>= 2;
+	ctlr->rfcfg.dash = u & 3;	u >>= 4;
+	ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
+	ctlr->rfcfg.rxantmask = u & 15;
+	if((err = eepromread(ctlr, b, 2, 0x66)) != nil)
+		goto Err2;
+	regoff = get16(b);
+	if((err = eepromread(ctlr, b, 4, regoff+1)) != nil)
+		goto Err2;
+	strncpy(ctlr->eeprom.regdom, (char*)b, 4);
+	ctlr->eeprom.regdom[4] = 0;
+	if((err = eepromread(ctlr, b, 2, 0x67)) != nil)
+		goto Err2;
+	caloff = get16(b);
+	if((err = eepromread(ctlr, b, 4, caloff)) != nil)
+		goto Err2;
+	ctlr->eeprom.version = b[0];
+	ctlr->eeprom.type = b[1];
+	ctlr->eeprom.volt = get16(b+2);
+
+	ctlr->eeprom.temp = 0;
+	ctlr->eeprom.rawtemp = 0;
+	if(ctlr->type == Type2030 || ctlr->type == Type2000){
+		if((err = eepromread(ctlr, b, 2, caloff + 0x12a)) != nil)
+			goto Err2;
+		ctlr->eeprom.temp = get16(b);
+		if((err = eepromread(ctlr, b, 2, caloff + 0x12b)) != nil)
+			goto Err2;
+		ctlr->eeprom.rawtemp = get16(b);
+	}
+
+	if(ctlr->type != Type4965 && ctlr->type != Type5150){
+		if((err = eepromread(ctlr, b, 4, caloff + 0x128)) != nil)
+			goto Err2;
+		ctlr->eeprom.crystal = get32(b);
+	}
+	eepromunlock(ctlr);
+
+	switch(ctlr->type){
+	case Type4965:
+		ctlr->rfcfg.txantmask = 3;
+		ctlr->rfcfg.rxantmask = 7;
+		break;
+	case Type5100:
+		ctlr->rfcfg.txantmask = 2;
+		ctlr->rfcfg.rxantmask = 3;
+		break;
+	case Type6000:
+		if(ctlr->pdev->did == 0x422c || ctlr->pdev->did == 0x4230){
+			ctlr->rfcfg.txantmask = 6;
+			ctlr->rfcfg.rxantmask = 6;
+		}
+		break;
+	}
+	poweroff(ctlr);
+	return 0;
+Err:
+	print("iwlinit: %s\n", err);
+	poweroff(ctlr);
+	return -1;
+}
+
+static char*
+crackfw(FWImage *i, uchar *data, uint size, int alt)
+{
+	uchar *p, *e;
+	FWSect *s;
+	uint t, l;
+
+	memset(i, 0, sizeof(*i));
+	if(size < 4){
+Tooshort:
+		return "firmware image too short";
+	}
+	p = data;
+	e = p + size;
+	i->rev = get32(p); p += 4;
+	if(i->rev == 0){
+		uvlong altmask;
+
+		if(size < (4+64+4+4+8))
+			goto Tooshort;
+		if(memcmp(p, "IWL\n", 4) != 0)
+			return "bad firmware signature";
+		p += 4;
+		strncpy(i->descr, (char*)p, 64);
+		i->descr[64] = 0;
+		p += 64;
+		i->rev = get32(p); p += 4;
+		i->build = get32(p); p += 4;
+		altmask = get32(p); p += 4;
+		altmask |= (uvlong)get32(p) << 32; p += 4;
+		while(alt > 0 && (altmask & (1ULL<<alt)) == 0)
+			alt--;
+		for(;p < e; p += (l + 3) & ~3){
+			if(p + 8 > e)
+				goto Tooshort;
+
+			t = get32(p), p += 4;
+			l = get32(p), p += 4;
+			if(p + l > e)
+				goto Tooshort;
+
+			if((t >> 16) != 0 && (t >> 16) != alt)
+				continue;
+
+			switch(t & 0xFFFF){
+			case 1:
+				s = &i->main.text;
+				if(i->main.nsect < 1)
+					i->main.nsect = 1;
+				s->addr = 0x00000000;
+				goto Sect;
+			case 2:
+				s = &i->main.data;
+				if(i->main.nsect < 2)
+					i->main.nsect = 2;
+				s->addr = 0x00800000;
+				goto Sect;
+			case 3:
+				s = &i->init.text;
+				if(i->init.nsect < 1)
+					i->init.nsect = 1;
+				s->addr = 0x00000000;
+				goto Sect;
+			case 4:
+				s = &i->init.data;
+				if(i->init.nsect < 2)
+					i->init.nsect = 2;
+				s->addr = 0x00800000;
+				goto Sect;
+			case 5:
+				s = &i->boot.text;
+				s->addr = 0x00000000;
+				goto Sect;
+			case 18:
+				if(l < 4)
+					goto Tooshort;
+				i->flags = get32(p);
+				break;
+			case 19:
+				if(i->main.nsect >= nelem(i->main.sect))
+					return "too many main sections";
+				s = &i->main.sect[i->main.nsect++];
+				goto Chunk;
+			case 20:
+				if(i->init.nsect >= nelem(i->init.sect))
+					return "too many init sections";
+				s = &i->init.sect[i->init.nsect++];
+			Chunk:
+				if(l < 4)
+					goto Tooshort;
+				s->addr = get32(p);
+				p += 4, l -= 4;
+			Sect:
+				s->size = l;
+				s->data = p;
+				break;
+			case 22:
+				if(l < 12)
+					goto Tooshort;
+				switch(get32(p)){
+				case 0:
+					i->main.defcalib.flowmask = get32(p+4);
+					i->main.defcalib.eventmask = get32(p+8);
+					break;
+				case 1:
+					i->init.defcalib.flowmask = get32(p+4);
+					i->init.defcalib.eventmask = get32(p+8);
+					break;
+				}
+				break;
+			case 23:
+				if(l < 4)
+					goto Tooshort;
+				i->physku = get32(p);
+				break;
+			case 29:
+				if(l < 8)
+					goto Tooshort;
+				t = get32(p);
+				if(t >= nelem(i->api))
+					goto Tooshort;
+				i->api[t] = get32(p+4);
+				break;
+			case 30:
+				if(l < 8)
+					goto Tooshort;
+				t = get32(p);
+				if(t >= nelem(i->capa))
+					goto Tooshort;
+				i->capa[t] = get32(p+4);
+				break;
+			case 32:
+				if(l < 4)
+					goto Tooshort;
+				i->pagedmemsize = get32(p) & -FWPagesize;
+				break;
+			}
+		}
+	} 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;
+
+		i->main.nsect = 2;
+		i->init.nsect = 2;
+		i->main.text.addr = 0x00000000;
+		i->main.data.addr = 0x00800000;
+		i->init.text.addr = 0x00000000;
+		i->init.data.addr = 0x00800000;
+	}
+	return 0;
+}
+
+static FWImage*
+readfirmware(char *name)
+{
+	uchar dirbuf[sizeof(Dir)+100], *data;
+	char buf[128], *err;
+	FWImage *fw;
+	int n, r;
+	Chan *c;
+	Dir d;
+
+	if(!iseve())
+		error(Eperm);
+	if(!waserror()){
+		snprint(buf, sizeof buf, "/boot/%s", name);
+		c = namec(buf, Aopen, OREAD, 0);
+		poperror();
+	} else {
+		snprint(buf, sizeof buf, "/lib/firmware/%s", name);
+		c = namec(buf, Aopen, OREAD, 0);
+	}
+	if(waserror()){
+		cclose(c);
+		nexterror();
+	}
+	n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
+	if(n <= 0)
+		error("can't stat firmware");
+	convM2D(dirbuf, n, &d, nil);
+	fw = smalloc(sizeof(*fw) + 16 + d.length);
+	data = (uchar*)(fw+1);
+	if(waserror()){
+		free(fw);
+		nexterror();
+	}
+	r = 0;
+	while(r < d.length){
+		n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
+		if(n <= 0)
+			break;
+		r += n;
+	}
+	if((err = crackfw(fw, data, r, 1)) != nil)
+		error(err);
+	poperror();
+	poperror();
+	cclose(c);
+	return fw;
+}
+
+
+static int
+gotirq(void *arg)
+{
+	Ctlr *ctlr = arg;
+	return (ctlr->wait.m & ctlr->wait.w) != 0;
+}
+
+static u32int
+irqwait(Ctlr *ctlr, u32int mask, int timeout)
+{
+	u32int r;
+
+	ilock(ctlr);
+	r = ctlr->wait.m & mask;
+	if(r == 0){
+		ctlr->wait.w = mask;
+		iunlock(ctlr);
+		if(!waserror()){
+			tsleep(&ctlr->wait, gotirq, ctlr, timeout);
+			poperror();
+		}
+		ilock(ctlr);
+		ctlr->wait.w = 0;
+		r = ctlr->wait.m & mask;
+	}
+	ctlr->wait.m &= ~r;
+	iunlock(ctlr);
+	return r;
+}
+
+static int
+rbplant(Ctlr *ctlr, uint i)
+{
+	Block *b;
+
+	assert(i < Nrx);
+
+	b = iallocb(Rbufsize*2);
+	if(b == nil)
+		return -1;
+	b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, Rbufsize);
+	memset(b->rp, 0, Rbufsize);
+	dmaflush(1, b->rp, Rbufsize);
+	coherence();
+	ctlr->rx.b[i] = b;
+
+	if(ctlr->mqrx)
+		put64(ctlr->rx.p + (i<<3), PCIWADDR(b->rp));
+	else
+		put32(ctlr->rx.p + (i<<2), PCIWADDR(b->rp) >> 8);
+	dmaflush(1, ctlr->rx.p + i*ctlr->rx.psz, ctlr->rx.psz);
+
+	return 0;
+}
+
+static char*
+initmem(Ctlr *ctlr)
+{
+	RXQ *rx;
+	TXQ *tx;
+	int i, q;
+
+	if(ctlr->fw->pagedmemsize > 0){
+		ctlr->fwmem.npage = ctlr->fw->pagedmemsize >> FWPageshift;
+		ctlr->fwmem.nblock = ctlr->fwmem.npage >> FWBlockshift;
+		if(ctlr->fwmem.nblock >= nelem(ctlr->fwmem.block)-1)
+			return "paged memory size too big";
+		for(i = 0; i < ctlr->fwmem.nblock; i++)
+			ctlr->fwmem.block[i].size = FWBlocksize;
+		ctlr->fwmem.block[i].size = (ctlr->fwmem.npage % FWBlockpages) << FWPageshift;
+		if(ctlr->fwmem.block[i].size != 0)
+			ctlr->fwmem.nblock++;
+		for(i = 0; i < ctlr->fwmem.nblock; i++){
+			if(ctlr->fwmem.block[i].p == nil){
+				ctlr->fwmem.block[i].p = mallocalign(ctlr->fwmem.block[i].size, FWPagesize, 0, 0);
+				if(ctlr->fwmem.block[i].p == nil)
+					return "no memory for firmware block";
+			}
+		}
+		if(ctlr->fwmem.css == nil){
+			if((ctlr->fwmem.css = mallocalign(FWPagesize, FWPagesize, 0, 0)) == nil)
+				return "no memory for firmware css page";
+		}
+	}
+
+	rx = &ctlr->rx;
+	if(ctlr->mqrx){
+		if(rx->u == nil)
+			rx->u = mallocalign(4 * Nrx, 4096, 0, 0);
+		if(rx->u == nil)
+			return "no memory for rx rings";
+		memset(rx->u, 0, 4 * Nrx);
+		dmaflush(1, rx->u, 4 * Nrx);
+		rx->psz = 8;
+	} else {
+		rx->u = nil;
+		rx->psz = 4;
+	}
+	if(rx->p == nil)
+		rx->p = mallocalign(rx->psz*Nrx, 4096, 0, 0);
+	if(rx->s == nil)
+		rx->s = mallocalign(Rstatsize, 4096, 0, 0);
+	if(rx->b == nil)
+		rx->b = malloc(sizeof(Block*) * Nrx);
+	if(rx->p == nil || rx->b == nil || rx->s == nil)
+		return "no memory for rx ring";
+	memset(rx->s, 0, Rstatsize);
+	for(i=0; i<Nrx; i++){
+		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;
+
+	ctlr->ndma = 8;
+	ctlr->ntxq = 20;
+	if(ctlr->family >= 7000) {
+		ctlr->ntxq = 31;
+	} else {
+		if(ctlr->type == Type4965) {
+			ctlr->ndma = 7;
+			ctlr->ntxq = 16;
+		}
+	}
+
+	if(ctlr->sched.s == nil)
+		ctlr->sched.s = mallocalign((256+64)*2 * ctlr->ntxq, 4096, 0, 0);
+	if(ctlr->sched.s == nil)
+		return "no memory for sched buffer";
+	memset(ctlr->sched.s, 0, (256+64)*2 * ctlr->ntxq);
+	dmaflush(1, ctlr->sched.s, (256+64)*2 * ctlr->ntxq);
+
+	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, 4096, 0, 0);
+		if(tx->c == nil)
+			tx->c = mallocalign(Tcmdsize * Ntx, 4096, 0, 0);
+		if(tx->b == nil || tx->d == nil || tx->c == nil)
+			return "no memory for tx ring";
+		memset(tx->d, 0, Tdscsize * Ntx);
+		dmaflush(1, tx->d, Tdscsize * Ntx);
+		memset(tx->c, 0, Tcmdsize * Ntx);
+		dmaflush(1, tx->c, Tcmdsize * Ntx);
+		for(i=0; i<Ntx; i++){
+			if(tx->b[i] != nil){
+				freeblist(tx->b[i]);
+				tx->b[i] = nil;
+			}
+		}
+		tx->i = 0;
+		tx->n = 0;
+		tx->lastcmd = 0;
+	}
+
+	if(ctlr->kwpage == nil)
+		ctlr->kwpage = mallocalign(4096, 4096, 0, 0);
+	if(ctlr->kwpage == nil)
+		return "no memory for kwpage";		
+	memset(ctlr->kwpage, 0, 4096);
+	dmaflush(1, ctlr->kwpage, 4096);
+
+	return nil;
+}
+
+static char*
+reset(Ctlr *ctlr)
+{
+	char *err;
+	int q, i;
+
+	if(ctlr->power)
+		poweroff(ctlr);
+	if((err = initmem(ctlr)) != nil)
+		return err;
+	if((err = poweron(ctlr)) != nil)
+		return err;
+
+	if(ctlr->family <= 7000){
+		if((err = niclock(ctlr)) != nil)
+			return err;
+		prphwrite(ctlr, ApmgPs, (prphread(ctlr, ApmgPs) & ~PwrSrcMask) | PwrSrcVMain);
+		nicunlock(ctlr);
+	}
+
+	if(ctlr->family >= 7000){
+		u32int u;
+
+		u = csr32r(ctlr, Cfg);
+
+		u &= ~(RadioSi|MacSi|CfgMacDashMask|CfgMacStepMask|CfgPhyTypeMask|CfgPhyStepMask|CfgPhyDashMask);
+
+		u |= (ctlr->step << CfgMacStepShift) & CfgMacStepMask;
+		u |= (ctlr->dash << CfgMacDashShift) & CfgMacDashMask;
+
+		u |= ctlr->rfcfg.type << CfgPhyTypeShift;
+		u |= ctlr->rfcfg.step << CfgPhyStepShift;
+		u |= ctlr->rfcfg.dash << CfgPhyDashShift;
+
+		csr32w(ctlr, Cfg, u);
+
+	} else {
+		csr32w(ctlr, Cfg, csr32r(ctlr, Cfg) | RadioSi | MacSi);
+	}
+
+	if(ctlr->family < 8000){
+		if((err = niclock(ctlr)) != nil)
+			return err;
+		if(ctlr->family == 7000 || ctlr->type != Type4965)
+			prphwrite(ctlr, ApmgPs, prphread(ctlr, ApmgPs) | EarlyPwroffDis);
+		nicunlock(ctlr);
+	}
+	if(ctlr->family < 7000){
+		if((err = niclock(ctlr)) != nil)
+			return err;
+		if(ctlr->type == Type1000){
+			/*
+			 * Select first Switching Voltage Regulator (1.32V) to
+			 * solve a stability issue related to noisy DC2DC line
+			 * in the silicon of 1000 Series.
+			 */
+			prphwrite(ctlr, ApmgDigitalSvr, 
+				(prphread(ctlr, ApmgDigitalSvr) & ~(0xf<<5)) | (3<<5));
+		}
+		if((ctlr->type == Type6005 || ctlr->type == Type6050) && ctlr->eeprom.version == 6)
+			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvCalV6);
+		if(ctlr->type == Type6005)
+			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrv1X2);
+		if(ctlr->type == Type2030 || ctlr->type == Type2000)
+			csr32w(ctlr, GpDrv, csr32r(ctlr, GpDrv) | GpDrvRadioIqInvert);
+		nicunlock(ctlr);
+	}
+
+	if((err = niclock(ctlr)) != nil)
+		return err;
+
+	dmaflush(1, ctlr->rx.s, Rstatsize);
+
+	if(ctlr->mqrx){
+		/* Stop RX DMA. */
+		prphwrite(ctlr, RfhDmaCfg, 0);
+		/* Disable RX used and free queue operation. */
+		prphwrite(ctlr, RfhRxqActive, 0);
+
+		prphwrite64(ctlr, RfhQ0SttsBase, PCIWADDR(ctlr->rx.s));
+		prphwrite64(ctlr, RfhQ0FreeBase, PCIWADDR(ctlr->rx.p));
+		prphwrite64(ctlr, RfhQ0UsedBase, PCIWADDR(ctlr->rx.u));
+
+		prphwrite(ctlr, RfhQ0FreeWptr, 0);
+		prphwrite(ctlr, RfhQ0FreeRptr, 0);
+		prphwrite(ctlr, RfhQ0UsedWptr, 0);
+
+		/* Enable RX DMA */
+		prphwrite(ctlr, RfhDmaCfg,
+			RfhDmaEnable |
+			RfhDmaDropTooLarge |
+			((Rbufsize/1024) << RfhDma1KSizeShift) |
+			(3 << RfhDmaMinRbSizeShift) |
+			(Nrxlog << RfhDmaNrbdShift));
+
+		/* Enable RX DMA snooping. */
+		prphwrite(ctlr, RfhGenCfg,
+			RfhGenServiceDmaSnoop |
+			RfhGenRfhDmaSnoop |
+			RfhGenRbChunkSize128);
+
+		/* Enable Q0 */
+		prphwrite(ctlr, RfhRxqActive, (1 << 16) | 1);
+		delay(1);
+
+		csr32w(ctlr, FhRxQ0Wptr, (Nrx-1) & ~7);
+		delay(1);
+	} else {
+		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);
+	}
+
+	for(i = 0; i < ctlr->ndma; i++)
+		csr32w(ctlr, FhTxConfig + i*32, 0);
+
+	if(ctlr->family >= 7000 || ctlr->type != Type4965)
+		prphwrite(ctlr, SchedTxFact, 0);
+	else
+		prphwrite(ctlr, SchedTxFact4965, 0);
+
+	if(ctlr->family >= 7000){
+		prphwrite(ctlr, SchedEnCtrl, 0);
+		prphwrite(ctlr, SchedGpCtrl, prphread(ctlr, SchedGpCtrl)
+			| Enable31Queues*(ctlr->ntxq == 31)
+			| AutoActiveMode);
+		for(q = 0; q < ctlr->ntxq; q++)
+			prphwrite(ctlr, (q<20? SchedQueueStatus: SchedQueueStatus20) + q*4, 1 << 19);
+	}
+
+	csr32w(ctlr, FhKwAddr, PCIWADDR(ctlr->kwpage) >> 4);
+	for(q = 0; q < ctlr->ntxq; q++){
+		i = q < nelem(ctlr->tx) ? q : nelem(ctlr->tx)-1;
+		if(q < 16)
+			csr32w(ctlr, FhCbbcQueue0 + q*4, PCIWADDR(ctlr->tx[i].d) >> 8);
+		else if(q < 20)
+			csr32w(ctlr, FhCbbcQueue16 + (q-16)*4, PCIWADDR(ctlr->tx[i].d) >> 8);
+		else
+			csr32w(ctlr, FhCbbcQueue20 + (q-20)*4, PCIWADDR(ctlr->tx[i].d) >> 8);
+	}
+
+	if(ctlr->family >= 7000 || ctlr->type >= Type6000)
+		csr32w(ctlr, ShadowRegCtrl, csr32r(ctlr, ShadowRegCtrl) | 0x800fffff);
+
+	nicunlock(ctlr);
+
+	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
+	csr32w(ctlr, UcodeGp1Clr, UcodeGp1CmdBlocked);
+
+	ctlr->systime = 0;
+
+	ctlr->broken = 0;
+	ctlr->wait.m = 0;
+	ctlr->wait.w = 0;
+
+	ctlr->bcast.id = -1;
+	ctlr->bss.id = -1;
+
+	ctlr->phyid = -1;
+	ctlr->macid = -1;
+	ctlr->bindid = -1;
+	ctlr->te.id = -1;
+	ctlr->te.active = 0;
+	ctlr->aid = 0;
+
+	if(ctlr->family >= 9000)
+		csr32w(ctlr, Gpc, csr32r(ctlr, Gpc) | 0x4000000);
+
+	ctlr->ie = Idefmask;
+	csr32w(ctlr, Imr, ctlr->ie);
+	csr32w(ctlr, Isr, ~0);
+
+	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
+	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
+	csr32w(ctlr, UcodeGp1Clr, UcodeGp1RfKill);
+
+	return nil;
+}
+
+static char*
+sendmccupdate(Ctlr *ctlr, char *mcc)
+{
+	uchar c[2+1+1+4+5*4], *p;
+
+	memset(p = c, 0, sizeof(c));
+	*p++ = mcc[1];
+	*p++ = mcc[0];
+	*p++ = 0;
+	*p++ = 0;	// reserved
+	if(ctlr->fw->capa[2] & UcodeCapLar2){
+		p += 4;
+		p += 5*4;
+	}
+	return cmd(ctlr, 200, c, p - c);
+}
+
+static char*
+sendbtcoexadv(Ctlr *ctlr)
+{
+	static u32int btcoex3wire[12] = {
+		0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa,
+		0xcc00ff28, 0x0000aaaa, 0xcc00aaaa, 0x0000aaaa,
+		0xc0004000, 0x00004000, 0xf0005000, 0xf0005000,
+	};
+
+	uchar c[Tcmdsize], *p;
+	char *err;
+	int i;
+
+	/* set BT config */
+	memset(c, 0, sizeof(c));
+	p = c;
+
+	if(ctlr->family >= 7000){
+		put32(p, 3);
+		p += 4;
+		put32(p, (1<<4));
+		p += 4;
+	} else if(ctlr->type == Type2030){
+		*p++ = 145; /* flags */
+		p++; /* lead time */
+		*p++ = 5; /* max kill */
+		*p++ = 1; /* bt3 t7 timer */
+		put32(p, 0xffff0000); /* kill ack */
+		p += 4;
+		put32(p, 0xffff0000); /* kill cts */
+		p += 4;
+		*p++ = 2; /* sample time */
+		*p++ = 0xc; /* bt3 t2 timer */
+		p += 2; /* bt4 reaction */
+		for (i = 0; i < nelem(btcoex3wire); i++){
+			put32(p, btcoex3wire[i]);
+			p += 4;
+		}
+		p += 2; /* bt4 decision */
+		put16(p, 0xff); /* valid */
+		p += 2;
+		put32(p, 0xf0); /* prio boost */
+		p += 4;
+		p++; /* reserved */
+		p++; /* tx prio boost */
+		p += 2; /* rx prio boost */
+	}
+	if((err = cmd(ctlr, 155, c, p-c)) != nil)
+		return err;
+
+	if(ctlr->family >= 7000)
+		return nil;
+
+	/* set BT priority */
+	memset(c, 0, sizeof(c));
+	p = c;
+
+	*p++ = 0x6; /* init1 */
+	*p++ = 0x7; /* init2 */
+	*p++ = 0x2; /* periodic low1 */
+	*p++ = 0x3; /* periodic low2 */
+	*p++ = 0x4; /* periodic high1 */
+	*p++ = 0x5; /* periodic high2 */
+	*p++ = 0x6; /* dtim */
+	*p++ = 0x8; /* scan52 */
+	*p++ = 0xa; /* scan24 */
+	p += 7; /* reserved */
+	if((err = cmd(ctlr, 204, c, p-c)) != nil)
+		return err;
+
+	/* force BT state machine change */
+	memset(c, 0, sizeof(c));
+	p = c;
+
+	*p++ = 1; /* open */
+	*p++ = 1; /* type */
+	p += 2; /* reserved */
+	if((err = cmd(ctlr, 205, c, p-c)) != nil)
+		return err;
+
+	c[0] = 0; /* open */
+	return cmd(ctlr, 205, c, p-c);
+}
+
+static char*
+sendpagingcmd(Ctlr *ctlr)
+{
+	uchar c[3*4 + 4 + 32*4], *p;
+	int i;
+
+	p = c;
+	put32(p, (3<<8) | (ctlr->fwmem.npage % FWBlockpages));
+	p += 4;
+	put32(p, FWPageshift + FWBlockshift);
+	p += 4;
+	put32(p, ctlr->fwmem.nblock);
+	p += 4;
+
+	dmaflush(1, ctlr->fwmem.css, FWPagesize);
+	put32(p, PCIWADDR(ctlr->fwmem.css) >> FWPageshift);
+	p += 4;
+
+	for(i = 0; i < ctlr->fwmem.nblock; i++){
+		dmaflush(1, ctlr->fwmem.block[i].p, ctlr->fwmem.block[i].size);
+		put32(p, PCIWADDR(ctlr->fwmem.block[i].p) >> FWPageshift);
+		p += 4;
+	}
+
+	for(; i < 32; i++){
+		put32(p, 0);
+		p += 4;
+	}
+
+	return cmd(ctlr, 79 | (1<<8), c, p-c);
+}
+
+static char*
+enablepaging(Ctlr *ctlr)
+{
+	FWSect *sect;
+	int nsect;
+	int i, j, o, n;
+
+	if(ctlr->fwmem.css == nil)
+		return nil;
+
+	if(1){
+		/* clear everything */
+		memset(ctlr->fwmem.css, 0, FWPagesize);
+		for(i = 0; i < ctlr->fwmem.nblock; i++)
+			memset(ctlr->fwmem.block[i].p, 0, ctlr->fwmem.block[i].size);
+	}
+
+	if(ctlr->calib.done == 0){
+		sect = ctlr->fw->init.sect;
+		nsect = ctlr->fw->init.nsect;
+	} else {
+		sect = ctlr->fw->main.sect;
+		nsect = ctlr->fw->main.nsect;
+	}
+
+	/* first CSS segment */
+	for(i = 0; i < nsect; i++) {
+		if(sect[i].addr == 0xAAAABBBB){
+			i++;
+			break;
+		}
+	}
+
+	if(i+1 >= nsect)
+		return "firmware misses CSS+paging sections";
+
+	if(sect[i].size > FWPagesize)
+		return "CSS section too big";
+	if(sect[i+1].size > (ctlr->fwmem.npage << FWPageshift))
+		return "paged section too big";
+
+	memmove(ctlr->fwmem.css, sect[i].data, sect[i].size);
+
+	for(j = 0, o = 0; o < sect[i+1].size; o += n, j++){
+		n = sect[i+1].size - o;
+		if(n > ctlr->fwmem.block[j].size)
+			n = ctlr->fwmem.block[j].size;
+		memmove(ctlr->fwmem.block[j].p, sect[i+1].data + o, n);
+	}
+
+	return sendpagingcmd(ctlr);
+}
+
+static int
+readnvmsect1(Ctlr *ctlr, int type, void *data, int len, int off)
+{
+	uchar c[2+2+2+2], *p;
+	char *err;
+
+	p = c;
+	*p++ = 0; // read op
+	*p++ = 0; // target
+	put16(p, type);
+	p += 2;
+	put16(p, off);
+	p += 2;
+	put16(p, len);
+	p += 2;
+
+	ctlr->nvm.off = -1;
+	ctlr->nvm.ret = -1;
+	ctlr->nvm.type = -1;
+	ctlr->nvm.sts = -1;
+
+	ctlr->nvm.buf = data;
+	ctlr->nvm.len = len;
+
+	if((err = cmd(ctlr, 136, c, p - c)) != nil){
+		ctlr->nvm.buf = nil;
+		ctlr->nvm.len = 0;
+		print("readnvmsect: %s\n", err);
+		return -1;
+	}
+
+	if(ctlr->nvm.ret < len)
+		len = ctlr->nvm.ret;
+
+	if(ctlr->nvm.sts != 0 || ctlr->nvm.off != off || (ctlr->nvm.type & 0xFF) != type)
+		return -1;
+
+	return len;
+}
+
+static int
+readnvmsect(Ctlr *ctlr, int type, void *data, int len, int off)
+{
+	int n, r, o;
+
+	for(o = 0; o < len; o += n){
+		r = len - o;
+		if(r > 256)
+			r = 256;
+		if((n = readnvmsect1(ctlr, type, (char*)data + o, r, o+off)) < 0)
+			return -1;
+		if(n < r){
+			o += n;
+			break;
+		}
+	}
+	return o;
+}
+
+static char*
+readnvmconfig(Ctlr *ctlr)
+{
+	uchar *ea = ctlr->edev->ea;
+	uchar buf[8];
+	uint u;
+	char *err;
+
+	if(readnvmsect(ctlr, 1, buf, 8, 0) != 8)
+		return "can't read nvm version";
+
+	ctlr->nvm.version = get16(buf);
+	if (ctlr->family == 7000) {
+		u = get16(buf + 2);
+
+		ctlr->rfcfg.type = (u >> 4) & 3;
+		ctlr->rfcfg.step = (u >> 2) & 3;
+		ctlr->rfcfg.dash = (u >> 0) & 3;
+		ctlr->rfcfg.pnum = (u >> 6) & 3;
+	} else {
+		if(readnvmsect(ctlr, 12, buf, 8, 0) != 8)
+			return "can't read nvm phy config";
+
+		u = get32(buf);
+
+		ctlr->rfcfg.type = (u >> 12) & 0xFFF;
+		ctlr->rfcfg.step = (u >> 8) & 15;
+		ctlr->rfcfg.dash = (u >> 4) & 15;
+		ctlr->rfcfg.pnum = (u >> 6) & 3;
+
+		ctlr->rfcfg.txantmask = (u >> 24) & 15;
+		ctlr->rfcfg.rxantmask = (u >> 28) & 15;
+	}
+	if(ctlr->family >= 8000){
+		if(readnvmsect(ctlr, 11, ea, Eaddrlen, 0x01<<1) != Eaddrlen){
+			u32int a0, a1;
+
+			if((err = niclock(ctlr)) != nil)
+				return err;
+			a0 = prphread(ctlr, 0xa03080);
+			a1 = prphread(ctlr, 0xa03084);
+			nicunlock(ctlr);
+
+			ea[0] = a0 >> 24;
+			ea[1] = a0 >> 16;
+			ea[2] = a0 >> 8;
+			ea[3] = a0 >> 0;
+			ea[4] = a1 >> 8;
+			ea[5] = a1 >> 0;
+		}
+	} else {
+		/*
+		 * 7260 gets angry if we read 6 bytes from 0x15*2.
+		 * reading 8 bytes from 0x14*2 works fine.
+		 */
+		if(readnvmsect(ctlr, 0, buf, 8, 0x14<<1) != 8)
+			return "can't read ea from nvm";
+
+		/* byte order is 16 bit little endian. */
+		ea[0] = buf[3];
+		ea[1] = buf[2];
+		ea[2] = buf[5];
+		ea[3] = buf[4];
+		ea[4] = buf[7];
+		ea[5] = buf[6];
+	}
+	memmove(ctlr->edev->addr, ea, Eaddrlen);
+
+	return nil;
+}
+
+static char*
+sendtxantconfig(Ctlr *ctlr, uint val)
+{
+	uchar c[4];
+
+	put32(c, val);
+	return cmd(ctlr, 152, c, 4);
+}
+
+static char*
+sendphyconfig(Ctlr *ctlr, u32int physku, u32int flowmask, u32int eventmask)
+{
+	uchar c[3*4];
+
+	put32(c+0, physku);
+	put32(c+4, flowmask);
+	put32(c+8, eventmask);
+	return cmd(ctlr, 106, c, 3*4);
+}
+
+static char*
+delstation(Ctlr *ctlr, Station *sta)
+{
+	uchar c[4], *p;
+	char *err;
+
+	if(sta->id < 0)
+		return nil;
+
+	memset(p = c, 0, sizeof(c));
+	*p = sta->id;
+
+	if((err = cmd(ctlr, 25, c, 4)) != nil)
+		return err;
+
+	sta->id = -1;
+	return nil;
+}
+
+enum {
+	StaTypeLink = 0,
+	StaTypeGeneralPurpose,
+	StaTypeMulticast,
+	StaTypeTdlsLink,
+	StaTypeAux,
+};
+
+static char*
+setstation(Ctlr *ctlr, int id, int type, uchar addr[6], Station *sta)
+{
+	uchar c[Tcmdsize], *p;
+	char *err;
+
+	memset(p = c, 0, sizeof(c));
+
+	*p++ = 0;			/* control (1 = update) */
+	p++;				/* reserved */
+	if(ctlr->family >= 7000){
+		put16(p, 0xffff);
+		p += 2;
+		put32(p, ctlr->macid);
+		p += 4;
+	} else {
+		p += 2;			/* reserved */
+	}
+
+	memmove(p, addr, 6);
+	p += 8;
+
+	*p++ = id;			/* sta id */
+
+	if(ctlr->family >= 7000){
+		*p++ = 1 << 1;		/* modify mask */
+		p += 2;			/* reserved */
+
+		put32(p, 0<<26 | 0<<28);
+		p += 4;			/* station_flags */
+
+		put32(p, 3<<26 | 3<<28);
+		p += 4;			/* station_flags_mask */
+
+		p++;			/* add_immediate_ba_tid */
+		p++;			/* remove_immediate_ba_tid */
+		p += 2;			/* add_immediate_ba_ssn */
+		p += 2;			/* sleep_tx_count */
+		p++;			/* sleep state flags */
+
+		*p++ = ctlr->fw->api[0] & UcodeApiSta ? type : 0;		/* station_type */
+
+		p += 2;			/* assoc id */
+
+		p += 2;			/* beamform flags */
+
+		put32(p, 1<<0);
+		p += 4;			/* tfd_queue_mask */
+
+		if(ctlr->fw->api[0] & UcodeApiSta){
+			p += 2;		/* rx_ba_window */
+			p++;		/* sp_length */
+			p++;		/* uapsd_acs */
+		}
+	} else {
+		p += 3;
+		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 */
+	}
+
+	if((err = cmd(ctlr, 24, c, p - c)) != nil)
+		return err;
+	sta->id = id;
+	return nil;
+}
+
+static char*
+setphycontext(Ctlr *ctlr, int amr)
+{
+	uchar c[Tcmdsize], *p;
+	int phyid;
+	char *err;
+
+	phyid = ctlr->phyid;
+	if(phyid < 0){
+		if(amr == CmdRemove)
+			return nil;
+		amr = CmdAdd;
+		phyid = 0;
+	} else if(amr == CmdAdd)
+		amr = CmdModify;
+
+	memset(p = c, 0, sizeof(c));
+	put32(p, phyid);	// id and color
+	p += 4;
+	put32(p, amr);
+	p += 4;
+	put32(p, 0);		// apply time 0 = immediate
+	p += 4;
+	put32(p, 0);		// tx param color ????
+	p += 4;
+
+	*p++ = (ctlr->rxflags & RFlag24Ghz) != 0;
+	*p++ = ctlr->channel;	// channel number
+	*p++ = 0;		// channel width (20MHz<<val)
+	*p++ = 0;		// pos1 below 
+
+	put32(p, ctlr->rfcfg.txantmask);
+	p += 4;
+	put32(p, ctlr->rfcfg.rxantmask<<1 | (1<<10) | (1<<12));
+	p += 4;
+	put32(p, 0);		// acquisition_data ????
+	p += 4;
+	put32(p, 0);		// dsp_cfg_flags
+	p += 4;
+
+	if((err = cmd(ctlr, 8, c, p - c)) != nil)
+		return err;
+
+	if(amr == CmdRemove)
+		phyid = -1;
+	ctlr->phyid = phyid;
+	return nil;
+}
+
+static u32int
+reciprocal(u32int v)
+{
+	return v != 0 ? 0xFFFFFFFFU / v : 0;
+}
+
+static char*
+setmaccontext(Ether *edev, Ctlr *ctlr, int amr, Wnode *bss)
+{
+	uchar c[4+4 + 4+4 + 8+8 + 4+4+4+4+4+4+4 + 5*8 + 12*4], *p;
+	int macid, i;
+	char *err;
+
+	macid = ctlr->macid;
+	if(macid < 0){
+		if(amr == CmdRemove)
+			return nil;
+		amr = CmdAdd;
+		macid = 0;
+	} else if(amr == CmdAdd)
+		amr = CmdModify;
+
+	memset(p = c, 0, sizeof(c));
+	put32(p, macid);
+	p += 4;
+	put32(p, amr);
+	p += 4;
+
+	put32(p, 5);	// mac type 5 = bss
+	p += 4;
+
+	put32(p, 0);	// tsf id ???
+	p += 4;
+
+	memmove(p, edev->ea, 6);
+	p += 8;
+
+	memmove(p, ctlr->bssid, 6);
+	p += 8;
+
+	put32(p, bss == nil? 0xF : (bss->validrates & 0xF));
+	p += 4;
+	put32(p, bss == nil? 0xFF : (bss->validrates >> 4));
+	p += 4;
+
+	put32(p, 0);	// protection flags
+	p += 4;
+
+	put32(p, ctlr->rxflags & RFlagShPreamble);
+	p += 4;
+	put32(p, ctlr->rxflags & RFlagShSlot);
+	p += 4;
+	put32(p, ctlr->rxfilter);
+	p += 4;
+
+	put32(p, 0);	// qos flags
+	p += 4;
+
+	for(i = 0; i < 4; i++){
+		put16(p, 0x07);		// cw_min
+		p += 2;
+		put16(p, 0x0f);		// cw_max
+		p += 2;
+		*p++ = 2;		// aifsn
+		*p++ = (1<<i);		// fifos_mask
+		put16(p, 102*32);	// edca_txop
+		p += 2;
+	}
+	p += 8;
+
+	if(bss != nil){
+		int dtimoff = bss->ival * (int)bss->dtimcount * 1024;
+
+		/* is assoc */
+		put32(p, bss->aid != 0);
+		p += 4;
+
+		/* dtim time (system time) */
+		put32(p, bss->rs + dtimoff);
+		p += 4;
+
+		/* dtim tsf */
+		put64(p, bss->ts + dtimoff);
+		p += 8;
+
+		/* beacon interval */
+		put32(p, bss->ival);
+		p += 4;
+		put32(p, reciprocal(bss->ival));
+		p += 4;
+
+		/* dtim interval */
+		put32(p, bss->ival * bss->dtimperiod);
+		p += 4;
+		put32(p, reciprocal(bss->ival * bss->dtimperiod));
+		p += 4;
+
+		/* listen interval */
+		put32(p, 10);
+		p += 4;
+
+		/* assoc id */
+		put32(p, bss->aid & 0x3fff);
+		p += 4;
+
+		/* assoc beacon arrive time */
+		put32(p, bss->rs);
+		p += 4;
+	}
+	USED(p);
+
+	if((err = cmd(ctlr, 40, c, sizeof(c))) != nil)
+		return err;
+
+	if(amr == CmdRemove)
+		macid = -1;
+	ctlr->macid = macid;
+
+	return nil;
+}
+
+static char*
+setbindingcontext(Ctlr *ctlr, int amr)
+{
+	uchar c[Tcmdsize], *p;
+	int bindid;
+	char *err;
+	int i;
+
+	bindid = ctlr->bindid;
+	if(bindid < 0){
+		if(amr == CmdRemove)
+			return nil;
+		amr = CmdAdd;
+		bindid = 0;
+	} else if(amr == CmdAdd)
+		amr = CmdModify;
+
+	if(ctlr->phyid < 0)
+		return "setbindingcontext: no phyid";
+	if(ctlr->macid < 0)
+		return "setbindingcontext: no macid";
+
+	p = c;
+	put32(p, bindid);
+	p += 4;
+	put32(p, amr);
+	p += 4;
+
+	i = 0;
+	if(amr != CmdRemove){
+		put32(p, ctlr->macid);
+		p += 4;
+		i++;
+	}
+	for(; i < 3; i++){
+		put32(p, -1);
+		p += 4;
+	}
+	put32(p, ctlr->phyid);
+	p += 4;
+
+	if((err = cmd(ctlr, 43, c, p - c)) != nil)
+		return err;
+
+	if(amr == CmdRemove)
+		bindid = -1;
+	ctlr->bindid = bindid;
+	return nil;
+}
+
+static int
+timeeventdone(void *arg)
+{
+	Ctlr *ctlr = arg;
+	return ctlr->te.id == -1 || ctlr->te.active != 0;
+}
+
+static char*
+settimeevent(Ctlr *ctlr, int amr, int ival)
+{
+	int timeid;
+	uchar c[9*4], *p;
+	char *err;
+
+	switch(amr){
+	case CmdAdd:
+		timeid = ctlr->te.id;
+		if(timeid == -1)
+			timeid = 0;
+		else {
+			if(ctlr->te.active)
+				return nil;
+			amr = CmdModify;
+		}
+		break;
+	default:
+		timeid = ctlr->te.id;
+		if(timeid == -1)
+			return nil;
+		break;
+	}
+
+	memset(p = c, 0, sizeof(c));
+	put32(p, ctlr->macid);
+	p += 4;
+	put32(p, amr);
+	p += 4;
+	put32(p, timeid);
+	p += 4;
+
+	if(amr == CmdRemove)
+		p += 6*4;
+	else{
+		put32(p, 0);			// apply time
+		p += 4;
+		put32(p, ival/2);		// max delay
+		p += 4;
+		put32(p, 0);			// depends on
+		p += 4;
+		put32(p, 1);			// interval
+		p += 4;
+		put32(p, ival? ival*2: 1024);	// duration
+		p += 4;
+		*p++ = 1;			// repeat
+		*p++ = 0;			// max frags
+		put16(p, 1<<0 | 1<<1 | 1<<11);	// policy
+		p += 2;
+	}
+
+	ctlr->te.active = 0;
+	if((err = cmd(ctlr, 41, c, p - c)) != nil)
+		return err;
+
+	if(amr == CmdRemove){
+		ctlr->te.active = 0;
+		ctlr->te.id = -1;
+		return nil;
+	}
+	tsleep(&ctlr->te, timeeventdone, ctlr, 100);
+	return ctlr->te.active? nil: "timeevent did not start";
+}
+
+
+static char*
+setbindingquotas(Ctlr *ctlr, int bindid)
+{
+	uchar c[4*(3*4)], *p;
+	int i;
+
+	if((ctlr->fw->capa[1] & UcodeCapQuota) == 0)
+		return nil;
+
+	i = 0;
+	p = c;
+
+	if(bindid != -1){
+		put32(p, bindid);
+		p += 4;
+		put32(p, 128);
+		p += 4;
+		put32(p, 0);
+		p += 4;
+		i++;
+	}
+	for(; i < 4; i++){
+		put32(p, -1);
+		p += 4;
+		put32(p, 0);
+		p += 4;
+		put32(p, 0);
+		p += 4;
+	}
+
+	return cmd(ctlr, 44, c, p - c);
+}
+
+static char*
+setmcastfilter(Ctlr *ctlr)
+{
+	uchar *p;
+	char *err;
+	Block *b;
+
+	b = allocb(4+6+2);
+	p = b->rp;
+
+	*p++ = 1;	// filter own
+	*p++ = 0;	// port id
+	*p++ = 0;	// count
+	*p++ = 1;	// pass all
+
+	memmove(p, ctlr->bssid, 6);
+	p += 6;
+	*p++ = 0;
+	*p++ = 0;
+
+	b->wp = p;
+	if((err = qcmd(ctlr, 4, 208, nil, 0, b)) != nil){
+		freeb(b);
+		return err;
+	}
+	return flushq(ctlr, 4);
+}
+
+static char*
+setmacpowermode(Ctlr *ctlr)
+{
+	uchar c[4 + 2+2 + 4+4+4+4 + 1+1 + 2+2 + 1+1+1+1 + 1+1+1+1 + 1+1], *p;
+
+	p = c;
+	put32(p, ctlr->macid);
+	p += 4;
+
+	put16(p, 0);	// flags
+	p += 2;
+	put16(p, 5);	// keep alive seconds
+	p += 2;
+
+	put32(p, 0);	// rx data timeout
+	p += 4;
+	put32(p, 0);	// tx data timeout
+	p += 4;
+	put32(p, 0);	// rx data timeout uapsd
+	p += 4;
+	put32(p, 0);	// tx data timeout uapsd
+	p += 4;
+
+	*p++ = 0;	// lprx rssi threshold
+	*p++ = 0;	// skip dtim periods
+
+	put16(p, 0);	// snooze interval
+	p += 2;
+	put16(p, 0);	// snooze window
+	p += 2;
+
+	*p++ = 0;	// snooze step
+	*p++ = 0;	// qndp tid
+	*p++ = 0;	// uapsd ac flags
+	*p++ = 0;	// uapsd max sp
+
+	*p++ = 0;	// heavy tx thld packets
+	*p++ = 0;	// heavy rx thld packets
+
+	*p++ = 0;	// heavy tx thld percentage
+	*p++ = 0;	// heavy rx thld percentage
+
+	*p++ = 0;	// limited ps threshold
+	*p++ = 0;	// reserved
+
+	return cmd(ctlr, 169, c, p - c);
+}
+
+static char*
+disablebeaconfilter(Ctlr *ctlr)
+{
+	uchar c[11*4];
+
+	memset(c, 0, sizeof(c));
+	return cmd(ctlr, 210, c, 11*4);
+}
+
+static char*
+tttxbackoff(Ctlr *ctlr)
+{
+	uchar c[4];
+	
+	put32(c, 0);
+	return cmd(ctlr, 126, c, sizeof(c));
+}
+
+static char*
+updatedevicepower(Ctlr *ctlr)
+{
+	uchar c[4];
+
+	memset(c, 0, sizeof(c));
+	put16(c, 0<<13 | 1<<0);	// cont active off, pm enable
+
+	return cmd(ctlr, 119, c, 4);
+}
+
+static char*
+postboot7000(Ctlr *ctlr)
+{
+	char *err;
+
+	if(ctlr->calib.done == 0){
+		if(ctlr->family == 7000)
+			if((err = sendbtcoexadv(ctlr)) != nil)
+				return err;
+		if((err = readnvmconfig(ctlr)) != nil)
+			return err;
+	}
+
+	if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask)) != nil)
+		return err;
+
+	if(ctlr->calib.done == 0){
+		if((err = sendphyconfig(ctlr,
+			ctlr->fw->physku,
+			ctlr->fw->init.defcalib.flowmask,
+			ctlr->fw->init.defcalib.eventmask)) != nil)
+			return err;
+
+		/* wait to collect calibration records */
+		if(irqwait(ctlr, Ierr, 2000))
+			return "calibration failed";
+
+		if(ctlr->calib.done == 0){
+			print("iwl: no calibration results\n");
+			ctlr->calib.done = 1;
+		}
+	} else {
+		Block *b;
+		int i;
+
+		for(i = 0; i < nelem(ctlr->calib.cmd); i++){
+			if((b = ctlr->calib.cmd[i]) == nil)
+				continue;
+			b = copyblock(b, BLEN(b));
+			if((qcmd(ctlr, 4, 108, nil, 0, b)) != nil){
+				freeb(b);
+				return err;
+			}
+			if((err = flushq(ctlr, 4)) != nil)
+				return err;
+		}
+
+		if((err = sendphyconfig(ctlr,
+			ctlr->fw->physku,
+			ctlr->fw->main.defcalib.flowmask,
+			ctlr->fw->main.defcalib.eventmask)) != nil)
+			return err;
+
+		if((err = sendbtcoexadv(ctlr)) != nil)
+			return err;
+
+		/* Initialize tx backoffs to the minimum. */
+		if(ctlr->family == 7000)
+			if((err = tttxbackoff(ctlr)) != nil)
+				return err;
+
+		if((err = updatedevicepower(ctlr)) != nil){
+			print("can't update device power: %s\n", err);
+			return err;
+		}
+		if(ctlr->fw->capa[0] & UcodeCapLar)
+			if((err = sendmccupdate(ctlr, "ZZ")) != nil)
+				return err;
+		if((err = disablebeaconfilter(ctlr)) != nil){
+			print("can't disable beacon filter: %s\n", err);
+			return err;
+		}
+	}
+
+	return nil;
+}
+
+static char*
+postboot6000(Ctlr *ctlr)
+{
+	uchar c[Tcmdsize];
+	char *err;
+
+	/* disable wimax coexistance */
+	memset(c, 0, sizeof(c));
+	if((err = cmd(ctlr, 90, c, 4+4*16)) != nil)
+		return err;
+
+	/* 6235 times out if we calibrate the crystal immediately */
+	tsleep(&up->sleep, return0, nil, 10);
+	if(ctlr->type != Type5150){
+		/* calibrate crystal */
+		memset(c, 0, sizeof(c));
+		c[0] = 15;	/* code */
+		c[1] = 0;	/* group */
+		c[2] = 1;	/* ngroup */
+		c[3] = 1;	/* isvalid */
+		c[4] = ctlr->eeprom.crystal;
+		c[5] = ctlr->eeprom.crystal>>16;
+		/* for some reason 8086:4238 needs a second try */
+		if(cmd(ctlr, 176, c, 8) != nil && (err = cmd(ctlr, 176, c, 8)) != nil)
+			return err;
+	}
+
+	if(ctlr->calib.done == 0){
+		/* query calibration (init firmware) */
+		memset(c, 0, sizeof(c));
+		put32(c + 0*(5*4) + 0, 0xffffffff);
+		put32(c + 0*(5*4) + 4, 0xffffffff);
+		put32(c + 0*(5*4) + 8, 0xffffffff);
+		put32(c + 2*(5*4) + 0, 0xffffffff);
+		if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil)
+			return err;
+
+		/* wait to collect calibration records */
+		if(irqwait(ctlr, Ierr, 2000))
+			return "calibration failed";
+
+		if(ctlr->calib.done == 0){
+			print("iwl: no calibration results\n");
+			ctlr->calib.done = 1;
+		}
+	} else {
+		static uchar cmds[] = {8, 9, 11, 17, 16};
+		int q;
+
+		/* send calibration records (runtime firmware) */
+		for(q=0; q<nelem(cmds); q++){
+			Block *b;
+			int i;
+
+			i = cmds[q];
+			if(i == 8 && ctlr->type != Type5150 && ctlr->type != Type2030 &&
+				ctlr->type != Type2000)
+				continue;
+			if(i == 17 && (ctlr->type >= Type6000 || ctlr->type == Type5150) &&
+				ctlr->type != Type2030 && ctlr->type != Type2000)
+				continue;
+
+			if((b = ctlr->calib.cmd[i]) == nil)
+				continue;
+			b = copyblock(b, BLEN(b));
+			if((err = qcmd(ctlr, 4, 176, nil, 0, b)) != nil){
+				freeb(b);
+				return err;
+			}
+			if((err = flushq(ctlr, 4)) != nil)
+				return err;
+		}
+
+		/* temperature sensor offset */
+		switch (ctlr->type){
+		case Type6005:
+			memset(c, 0, sizeof(c));
+			c[0] = 18;
+			c[1] = 0;
+			c[2] = 1;
+			c[3] = 1;
+			put16(c + 4, 2700);
+			if((err = cmd(ctlr, 176, c, 4+2+2)) != nil)
+				return err;
+			break;
+
+		case Type2030:
+		case Type2000:
+			memset(c, 0, sizeof(c));
+			c[0] = 18;
+			c[1] = 0;
+			c[2] = 1;
+			c[3] = 1;
+			if(ctlr->eeprom.rawtemp != 0){
+				put16(c + 4, ctlr->eeprom.temp);
+				put16(c + 6, ctlr->eeprom.rawtemp);
+			} else{
+				put16(c + 4, 2700);
+				put16(c + 6, 2700);
+			}
+			put16(c + 8, ctlr->eeprom.volt);
+			if((err = cmd(ctlr, 176, c, 4+2+2+2+2)) != nil)
+				return err;
+			break;
+		}
+
+		if(ctlr->type == Type6005 || ctlr->type == Type6050){
+			/* runtime DC calibration */
+			memset(c, 0, sizeof(c));
+			put32(c + 0*(5*4) + 0, 0xffffffff);
+			put32(c + 0*(5*4) + 4, 1<<1);
+			if((err = cmd(ctlr, 101, c, (((2*(5*4))+4)*2)+4)) != nil)
+				return err;
+		}
+
+		if((err = sendtxantconfig(ctlr, ctlr->rfcfg.txantmask & 7)) != nil)
+			return err;
+
+		if(ctlr->type == Type2030){
+			if((err = sendbtcoexadv(ctlr)) != nil)
+				return err;
+		}
+	}
+	return nil;
+}
+
+static void
+initqueue(Ctlr *ctlr, int qid, int fifo, int chainmode, int window)
+{
+	csr32w(ctlr, HbusTargWptr, (qid << 8) | 0);
+
+	if(ctlr->family >= 7000 || ctlr->type != Type4965){
+		if(ctlr->family >= 7000)
+			prphwrite(ctlr, SchedQueueStatus + qid*4, 1 << 19);
+
+		if(chainmode)
+			prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) | (1<<qid));
+		else
+			prphwrite(ctlr, SchedQChainSel, prphread(ctlr, SchedQChainSel) & ~(1<<qid));
+
+		prphwrite(ctlr, SchedAggrSel, prphread(ctlr, SchedAggrSel) & ~(1<<qid));
+
+		prphwrite(ctlr, SchedQueueRdptr + qid*4, 0);
+
+		/* Set scheduler window size and frame limit. */
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8, 0);
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff + qid*8 + 4, window<<16 | window);
+
+		if(ctlr->family >= 7000){
+			prphwrite(ctlr, SchedQueueStatus + qid*4, 0x017f0018 | fifo);
+		} else {
+			prphwrite(ctlr, SchedQueueStatus + qid*4, 0x00ff0018 | fifo);
+		}
+	} else {
+		if(chainmode)
+			prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) | (1<<qid));
+		else
+			prphwrite(ctlr, SchedQChainSel4965, prphread(ctlr, SchedQChainSel4965) & ~(1<<qid));
+
+		prphwrite(ctlr, SchedQueueRdptr4965 + qid*4, 0);
+
+		/* Set scheduler window size and frame limit. */
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8, window);
+		memwrite(ctlr, ctlr->sched.base + SchedCtxOff4965 + qid*8 + 4, window<<16);
+
+		prphwrite(ctlr, SchedQueueStatus4965 + qid*4, 0x0007fc01 | fifo<<1);
+	}
+}
+
+static char*
+postboot(Ctlr *ctlr)
+{
+	uint ctxoff, ctxlen, dramaddr;
+	char *err;
+	int i, f;
+
+	if((err = niclock(ctlr)) != nil)
+		return err;
+
+	if(ctlr->family >= 7000 || ctlr->type != Type4965){
+		dramaddr = SchedDramAddr;
+		ctxoff = SchedCtxOff;
+		ctxlen = (SchedTransTblOff + 2*ctlr->ntxq) - ctxoff;
+	} else {
+		dramaddr = SchedDramAddr4965;
+		ctxoff = SchedCtxOff4965;
+		ctxlen = SchedCtxLen4965;
+	}
+
+	ctlr->sched.base = prphread(ctlr, SchedSramAddr);
+	for(i=0; i < ctxlen; i += 4)
+		memwrite(ctlr, ctlr->sched.base + ctxoff + i, 0);
+
+	prphwrite(ctlr, dramaddr, PCIWADDR(ctlr->sched.s)>>10);
+
+	if(ctlr->family >= 7000) {
+		prphwrite(ctlr, SchedEnCtrl, 0);
+		prphwrite(ctlr, SchedChainExtEn, 0);
+	}
+
+	for(i = 0; i < nelem(ctlr->tx); i++){
+		if(i == 4 && ctlr->family < 7000 && ctlr->type == Type4965)
+			f = 4;
+		else {
+			static char qid2fifo[] = {
+				 3, 2, 1, 0, 7, 5, 6,
+			};
+			f = qid2fifo[i];
+		}
+		initqueue(ctlr, i, f, i != 4 && ctlr->type != Type4965, 64);
+	}
+
+	/* Enable interrupts for all queues. */
+	if(ctlr->family >= 7000){
+		prphwrite(ctlr, SchedEnCtrl, 1 << 4);
+	} else if(ctlr->type != Type4965) {
+		prphwrite(ctlr, SchedIntrMask, (1<<ctlr->ntxq)-1);
+	} else {
+		prphwrite(ctlr, SchedIntrMask4965, (1<<ctlr->ntxq)-1);
+	}
+
+	/* Identify TX FIFO rings (0-7). */
+	if(ctlr->family >= 7000 || ctlr->type != Type4965){
+		prphwrite(ctlr, SchedTxFact, 0xff);
+	} else {
+		prphwrite(ctlr, SchedTxFact4965, 0xff);
+	}
+
+	/* Enable DMA channels */
+	for(i = 0; i < ctlr->ndma; i++)
+		csr32w(ctlr, FhTxConfig + i*32, FhTxConfigDmaEna | FhTxConfigDmaCreditEna);
+
+	/* Auto Retry Enable */
+	csr32w(ctlr, FhTxChicken, csr32r(ctlr, FhTxChicken) | 2);
+
+	nicunlock(ctlr);
+
+	if((err = enablepaging(ctlr)) != nil){
+		ctlr->calib.done = 0;
+		return err;
+	}
+
+	if(ctlr->family >= 7000)
+		return postboot7000(ctlr);
+	else if(ctlr->type != Type4965)
+		return postboot6000(ctlr);
+
+	return nil;
+}
+
+static char*
+loadfirmware1(Ctlr *ctlr, u32int dst, uchar *data, int size)
+{
+	enum { Maxchunk = 0x20000 };
+	uchar *dma;
+	char *err;
+
+	while(size > Maxchunk){
+		if((err = loadfirmware1(ctlr, dst, data, Maxchunk)) != nil)
+			return err;
+		size -= Maxchunk;
+		data += Maxchunk;
+		dst += Maxchunk;
+	}
+
+	dma = mallocalign(size, 4096, 0, 0);
+	if(dma == nil)
+		return "no memory for dma";
+	memmove(dma, data, size);
+	dmaflush(1, dma, size);
+	coherence();
+	
+	if((err = niclock(ctlr)) != nil){
+		free(dma);
+		return err;
+	}
+
+	if(ctlr->family >= 7000 && dst >= 0x40000 && dst < 0x57fff)
+		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) | ExtAddr);
+	else
+		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr);
+
+	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);
+
+	err = nil;
+	if(irqwait(ctlr, Ifhtx|Ierr, 5000) != Ifhtx)
+		err = "dma error / timeout";
+
+	if(niclock(ctlr) == nil){
+		prphwrite(ctlr, LmpmChick, prphread(ctlr, LmpmChick) & ~ExtAddr);
+		nicunlock(ctlr);
+	}
+
+	free(dma);
+
+	return err;
+}
+
+static char*
+setloadstatus(Ctlr *ctlr, u32int val)
+{
+	char *err;
+
+	if((err = niclock(ctlr)) != nil)
+		return err;
+	csr32w(ctlr, UcodeLoadStatus, val);
+	nicunlock(ctlr);
+	return nil;
+}
+
+static char*
+loadsections(Ctlr *ctlr, FWSect *sect, int nsect)
+{
+	int i, num;
+	char *err;
+
+	if(ctlr->family >= 8000){
+		if((err = niclock(ctlr)) != nil)
+			return err;
+		prphwrite(ctlr, ReleaseCpuReset, CpuResetBit);
+		nicunlock(ctlr);
+	}
+
+	num = 0;
+	for(i = 0; i < nsect; i++){
+		if(sect[i].addr == 0xAAAABBBB)
+			break;
+		if(sect[i].addr == 0xFFFFCCCC)
+			num = 16;
+		else {
+			if(sect[i].data == nil || sect[i].size == 0)
+				return "bad load section";
+			if((err = loadfirmware1(ctlr, sect[i].addr, sect[i].data, sect[i].size)) != nil)
+				return err;
+			num++;
+		}
+		if(ctlr->family >= 8000
+		&& (err = setloadstatus(ctlr, (1ULL << num)-1)) != nil)
+			return err;
+	}
+	return nil;
+}
+
+static char*
+ucodestart(Ctlr *ctlr)
+{
+	memset(&ctlr->fwinfo, 0, sizeof(ctlr->fwinfo));
+	coherence();
+	if(ctlr->family >= 8000)
+		return setloadstatus(ctlr, -1);
+	csr32w(ctlr, Reset, 0);
+	return nil;
+}
+
+static char*
+boot(Ctlr *ctlr)
+{
+	int i, n, size;
+	uchar *p, *dma;
+	FWImage *fw;
+	char *err;
+
+	fw = ctlr->fw;
+
+	if(fw->boot.text.size == 0){
+		if(ctlr->calib.done == 0){
+			if((err = loadsections(ctlr, fw->init.sect, fw->init.nsect)) != nil)
+				return err;
+			if((err = ucodestart(ctlr)) != nil)
+				return err;
+
+			tsleep(&up->sleep, return0, 0, 100);
+
+			if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
+				return "init firmware boot failed";
+			if(!ctlr->fwinfo.valid)
+				return "invalid fw info";
+
+			if((err = postboot(ctlr)) != nil)
+				return err;
+			if((err = reset(ctlr)) != nil)
+				return err;
+		}
+
+		if((err = loadsections(ctlr, fw->main.sect, fw->main.nsect)) != nil)
+			return err;
+		if((err = ucodestart(ctlr)) != nil)
+			return err;
+
+		tsleep(&up->sleep, return0, 0, 100);
+
+		if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive)
+			return "main firmware boot failed";
+		if(!ctlr->fwinfo.valid)
+			return "invalid main fw info";
+
+		return postboot(ctlr);
+	}
+
+	if(ctlr->family >= 7000)
+		return "wrong firmware image";
+
+	size = ROUND(fw->init.data.size, 16) + ROUND(fw->init.text.size, 16);
+	dma = mallocalign(size, 64, 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);
+	dmaflush(1, p, fw->init.data.size);
+	coherence();
+	prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4);
+	prphwrite(ctlr, BsmDramDataSize, fw->init.data.size);
+	p += ROUND(fw->init.data.size, 16);
+	memmove(p, fw->init.text.data, fw->init.text.size);
+	dmaflush(1, p, fw->init.text.size);
+	coherence();
+	prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4);
+	prphwrite(ctlr, BsmDramTextSize, fw->init.text.size);
+
+	nicunlock(ctlr);
+	if((err = niclock(ctlr)) != nil){
+		free(dma);
+		return err;
+	}
+
+	p = fw->boot.text.data;
+	n = fw->boot.text.size/4;
+	for(i=0; i<n; i++, p += 4)
+		prphwrite(ctlr, BsmSramBase+i*4, get32(p));
+
+	prphwrite(ctlr, BsmWrMemSrc, 0);
+	prphwrite(ctlr, BsmWrMemDst, 0);
+	prphwrite(ctlr, BsmWrDwCount, n);
+
+	prphwrite(ctlr, BsmWrCtrl, 1<<31);
+
+	for(i=0; i<1000; i++){
+		if((prphread(ctlr, BsmWrCtrl) & (1<<31)) == 0)
+			break;
+		microdelay(10);
+	}
+	if(i == 1000){
+		nicunlock(ctlr);
+		free(dma);
+		return "bootcode timeout";
+	}
+
+	prphwrite(ctlr, BsmWrCtrl, 1<<30);
+	nicunlock(ctlr);
+
+	csr32w(ctlr, Reset, 0);
+	if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){
+		free(dma);
+		return "boot firmware boot failed";
+	}
+	free(dma);
+
+	size = ROUND(fw->main.data.size, 16) + ROUND(fw->main.text.size, 16);
+	dma = mallocalign(size, 64, 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);
+	dmaflush(1, p, fw->main.data.size);
+	coherence();
+	prphwrite(ctlr, BsmDramDataAddr, PCIWADDR(p) >> 4);
+	prphwrite(ctlr, BsmDramDataSize, fw->main.data.size);
+	p += ROUND(fw->main.data.size, 16);
+	memmove(p, fw->main.text.data, fw->main.text.size);
+	dmaflush(1, p, fw->main.text.size);
+	coherence();
+	prphwrite(ctlr, BsmDramTextAddr, PCIWADDR(p) >> 4);
+	prphwrite(ctlr, BsmDramTextSize, fw->main.text.size | (1<<31));
+	nicunlock(ctlr);
+
+	if(irqwait(ctlr, Ierr|Ialive, 5000) != Ialive){
+		free(dma);
+		return "main firmware boot failed";
+	}
+	free(dma);
+	return postboot(ctlr);
+}
+
+static int
+txqready(void *arg)
+{
+	TXQ *q = arg;
+	return q->n < Ntxqmax;
+}
+
+static char*
+qcmd(Ctlr *ctlr, uint qid, uint code, uchar *data, int size, Block *block)
+{
+	char *err;
+	int hdrlen;
+	Block *bcmd;
+	uchar *d, *c;
+	TXQ *q;
+
+	assert(qid < ctlr->ntxq);
+
+	if((code & 0xFF00) != 0)
+		hdrlen = 8;
+	else
+		hdrlen = 4;
+
+	if(hdrlen+size > Tcmdsize)
+		bcmd = allocb(hdrlen + size);
+	else
+		bcmd = nil;
+
+	ilock(ctlr);
+	q = &ctlr->tx[qid];
+	while(q->n >= Ntxqmax && !ctlr->broken){
+		iunlock(ctlr);
+		qlock(q);
+		if(!waserror()){
+			tsleep(q, txqready, q, 5);
+			poperror();
+		}
+		qunlock(q);
+		ilock(ctlr);
+	}
+	if(ctlr->broken){
+		iunlock(ctlr);
+		return "qcmd: broken";
+	}
+	/* wake up the nic (just needed for 7k) */
+	if(ctlr->family == 7000 && qid == 4 && q->n == 0)
+		if((err = niclock(ctlr)) != nil){
+			iunlock(ctlr);
+			return err;
+		}
+	q->n++;
+	q->lastcmd = code;
+
+	q->b[q->i] = block;
+	if(bcmd != nil){
+		bcmd->next = q->b[q->i];
+		q->b[q->i] = bcmd;
+
+		c = bcmd->rp;
+		bcmd->wp = c + hdrlen + size;
+	} else {
+		c = q->c + q->i * Tcmdsize;
+	}
+
+	/* build command */
+	if(hdrlen == 8){
+		c[0] = code;
+		c[1] = code>>8;	/* group id */
+		c[2] = q->i;
+		c[3] = qid;
+		put16(c+4, size);
+		c[6] = 0;
+		c[7] = code>>16;
+	} else {
+		c[0] = code;
+		c[1] = 0;	/* flags */
+		c[2] = q->i;
+		c[3] = qid;
+	}
+	if(size > 0)
+		memmove(c+hdrlen, data, size);
+	size += hdrlen;
+
+	/* build descriptor */
+	d = q->d + q->i * Tdscsize;
+	d[0] = 0;
+	d[1] = 0;
+	d[2] = 0;
+	d[3] = 1 + (block != nil); /* nsegs */
+	dmaflush(1, c, size);
+	put32(d+4, PCIWADDR(c));
+	put16(d+8, size << 4);
+	if(block != nil){
+		size = BLEN(block);
+		dmaflush(1, block->rp, size);
+		put32(d+10, PCIWADDR(block->rp));
+		put16(d+14, size << 4);
+	}
+	dmaflush(1, d, Tdscsize);
+	coherence();
+
+	q->i = (q->i+1) % Ntx;
+	csr32w(ctlr, HbusTargWptr, (qid<<8) | q->i);
+
+	iunlock(ctlr);
+
+	return nil;
+}
+
+static int
+txqempty(void *arg)
+{
+	TXQ *q = arg;
+	return q->n == 0;
+}
+
+static char*
+flushq(Ctlr *ctlr, uint qid)
+{
+	TXQ *q;
+	int i;
+
+	q = &ctlr->tx[qid];
+	qlock(q);
+	for(i = 0; i < 200 && !ctlr->broken; i++){
+		if(txqempty(q)){
+			qunlock(q);
+			return nil;
+		}
+		if(!waserror()){
+			tsleep(q, txqempty, q, 10);
+			poperror();
+		}
+	}
+	qunlock(q);
+	if(ctlr->broken)
+		return "flushq: broken";
+	ctlr->broken = 1;
+	return "flushq: timeout";
+}
+
+static char*
+cmd(Ctlr *ctlr, uint code, uchar *data, int size)
+{
+	char *err;
+
+	if(0) print("cmd %ud\n", code);
+
+	if((err = qcmd(ctlr, 4, code, data, size, nil)) != nil
+	|| (err = flushq(ctlr, 4)) != nil){
+		print("#l%d: cmd %ud: %s\n", ctlr->edev->ctlrno, code, err);
+		return err;
+	}
+	return nil;
+}
+
+static void
+setled(Ctlr *ctlr, int which, int on, int off)
+{
+	uchar c[8];
+
+	if(ctlr->family >= 7000)
+		return;	// TODO
+
+	csr32w(ctlr, Led, csr32r(ctlr, Led) & ~LedBsmCtrl);
+	put32(c, 10000);
+	c[4] = which;
+	c[5] = on;
+	c[6] = off;
+	c[7] = 0;
+	cmd(ctlr, 72, c, sizeof(c));
+}
+
+static char*
+rxoff7000(Ether *edev, Ctlr *ctlr)
+{
+	char *err;
+	int i;
+
+	for(i = 0; i < nelem(ctlr->tx); i++)
+		if((err = flushq(ctlr, i)) != nil){
+			print("can't flush queue %d: %s\n", i, err);
+			return err;
+		}
+
+	if((err = settimeevent(ctlr, CmdRemove, 0)) != nil){
+		print("can't remove time event: %s\n", err);
+		return err;
+	}
+
+	if((err = setbindingquotas(ctlr, -1)) != nil){
+		print("can't disable quotas: %s\n", err);
+		return err;
+	}
+	if((err = delstation(ctlr, &ctlr->bss)) != nil){
+		print("can't remove bss station: %s\n", err);
+		return err;
+	}
+	if((err = delstation(ctlr, &ctlr->bcast)) != nil){
+		print("can't remove bcast station: %s\n", err);
+		return err;
+	}
+	if((err = setbindingcontext(ctlr, CmdRemove)) != nil){
+		print("removing bindingcontext: %s\n", err);
+		return err;
+	}
+	if((err = setmaccontext(edev, ctlr, CmdRemove, nil)) != nil){
+		print("removing maccontext: %s\n", err);
+		return err;
+	}
+	if((err = setphycontext(ctlr, CmdRemove)) != nil){
+		print("setphycontext: %s\n", err);
+		return err;
+	}
+	return nil;
+}
+
+static char*
+rxon7000(Ether *edev, Ctlr *ctlr)
+{
+	char *err;
+
+	if((err = setphycontext(ctlr, CmdAdd)) != nil){
+		print("setphycontext: %s\n", err);
+		return err;
+	}
+	if((err = setmaccontext(edev, ctlr, CmdAdd, nil)) != nil){
+		print("setmaccontext: %s\n", err);
+		return err;
+	}
+	if((err = setbindingcontext(ctlr, CmdAdd)) != nil){
+		print("adding bindingcontext: %s\n", err);
+		return err;
+	}
+	if((err = setmcastfilter(ctlr)) != nil){
+		print("can't set mcast filter: %s\n", err);
+		return err;
+	}
+	if((err = setmacpowermode(ctlr)) != nil){
+		print("can't set mac power: %s\n", err);
+		return err;
+	}
+	if((err = setbindingquotas(ctlr, ctlr->aid != 0 ? ctlr->bindid : -1)) != nil){
+		print("can't set binding quotas: %s\n", err);
+		return err;
+	}
+	return nil;
+}
+
+static char*
+rxon6000(Ether *edev, Ctlr *ctlr)
+{
+	uchar c[Tcmdsize], *p;
+	char *err;
+
+	memset(p = c, 0, sizeof(c));
+	memmove(p, edev->ea, 6); p += 8;	/* myaddr */
+	memmove(p, ctlr->bssid, 6); p += 8;	/* bssid */
+	memmove(p, edev->ea, 6); p += 8;	/* wlap */
+	*p++ = 3;				/* mode (STA) */
+	*p++ = 0;				/* air (?) */
+	/* rxchain */
+	put16(p, ((ctlr->rfcfg.rxantmask & 7)<<1) | (2<<10) | (2<<12));
+	p += 2;
+	*p++ = 0xff;				/* ofdm mask (not yet negotiated) */
+	*p++ = 0x0f;				/* cck mask (not yet negotiated) */
+	put16(p, ctlr->aid & 0x3fff);
+	p += 2;					/* aid */
+	put32(p, ctlr->rxflags);
+	p += 4;
+	put32(p, ctlr->rxfilter);
+	p += 4;
+	*p++ = ctlr->channel;
+	p++;					/* reserved */
+	*p++ = 0xff;				/* ht single mask */
+	*p++ = 0xff;				/* ht dual mask */
+	if(ctlr->type != Type4965){
+		*p++ = 0xff;			/* ht triple mask */
+		p++;				/* reserved */
+		put16(p, 0); p += 2;		/* acquisition */
+		p += 2;				/* reserved */
+	}
+	if((err = cmd(ctlr, 16, c, p - c)) != nil){
+		print("rxon6000: %s\n", err);
+		return err;
+	}
+	return nil;
+}
+
+static char*
+rxon(Ether *edev, Wnode *bss)
+{
+	Ctlr *ctlr = edev->ctlr;
+	char *err;
+
+	if(ctlr->family >= 7000)
+		if((err = rxoff7000(edev, ctlr)) != nil)
+			goto Out;
+
+	ctlr->rxfilter = FilterNoDecrypt | FilterMulticast | FilterBeacon;
+	if(ctlr->family >= 7000)
+		ctlr->rxfilter |= FilterNoDecryptMcast;
+	if(ctlr->prom)
+		ctlr->rxfilter |= FilterPromisc;
+
+	ctlr->rxflags =  RFlagTSF | RFlagCTSToSelf | RFlag24Ghz | RFlagAuto;
+	if(bss != nil){
+		ctlr->aid = bss->aid;
+		ctlr->channel = bss->channel;
+		memmove(ctlr->bssid, bss->bssid, sizeof(ctlr->bssid));
+		if(bss->cap & (1<<5))
+			ctlr->rxflags |= RFlagShPreamble;
+		if(bss->cap & (1<<10))
+			ctlr->rxflags |= RFlagShSlot;
+		if(ctlr->aid != 0){
+			ctlr->rxfilter |= FilterBSS;
+			ctlr->rxfilter &= ~FilterBeacon;
+			ctlr->bss.id = -1;
+		} else {
+			ctlr->bcast.id = -1;
+		}
+	} else {
+		ctlr->aid = 0;
+		memmove(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid));
+		ctlr->bcast.id = -1;
+		ctlr->bss.id = -1;
+	}
+
+	if(ctlr->aid != 0)
+		setled(ctlr, 2, 0, 1);		/* on when associated */
+	else if(memcmp(ctlr->bssid, edev->bcast, sizeof(ctlr->bssid)) != 0)
+		setled(ctlr, 2, 10, 10);	/* slow blink when connecting */
+	else
+		setled(ctlr, 2, 5, 5);		/* fast blink when scanning */
+
+	if(ctlr->wifi->debug)
+		print("#l%d: rxon: bssid %E, aid %x, channel %d, rxfilter %ux, rxflags %ux\n",
+			edev->ctlrno, ctlr->bssid, ctlr->aid, ctlr->channel, ctlr->rxfilter, ctlr->rxflags);
+
+	if(ctlr->family >= 7000)
+		err = rxon7000(edev, ctlr);
+	else
+		err = rxon6000(edev, ctlr);
+	if(err != nil)
+		goto Out;
+
+	if(ctlr->bcast.id == -1){
+		if((err = setstation(ctlr,
+			(ctlr->type != Type4965)? 15: 31,
+			StaTypeGeneralPurpose,
+			edev->bcast,
+			&ctlr->bcast)) != nil)
+			goto Out;
+	}
+	if(ctlr->bss.id == -1 && bss != nil && ctlr->aid != 0){
+		if((err = setstation(ctlr,
+			0,
+			StaTypeLink,
+			bss->bssid,
+			&ctlr->bss)) != nil)
+			goto Out;
+
+		if(ctlr->family >= 7000)
+			if((err = setmaccontext(edev, ctlr, CmdModify, bss)) != nil)
+				goto Out;
+	}
+Out:
+	return err;
+}
+
+static void
+transmit(Wifi *wifi, Wnode *wn, Block *b)
+{
+	int flags, rate, ant;
+	uchar c[Tcmdsize], *p;
+	Ether *edev;
+	Station *sta;
+	Ctlr *ctlr;
+	Wifipkt *w;
+	char *err;
+
+	edev = wifi->ether;
+	ctlr = edev->ctlr;
+
+	qlock(ctlr);
+	if(ctlr->attached == 0 || ctlr->broken){
+Broken:
+		qunlock(ctlr);
+		freeb(b);
+		return;
+	}
+
+	if((wn->channel != ctlr->channel)
+	|| (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0))){
+		if(rxon(edev, wn) != nil)
+			goto Broken;
+	}
+
+	/*
+	 * unless authenticated, the firmware will hop
+	 * channels unless we force it onto a channel using
+	 * a timeevent.
+	 */
+	if(ctlr->aid == 0 && ctlr->family >= 7000)
+		if(settimeevent(ctlr, CmdAdd, wn->ival) != nil)
+			goto Broken;
+
+	if(b == nil){
+		/* association note has no data to transmit */
+		qunlock(ctlr);
+		return;
+	}
+	flags = 0;
+	sta = &ctlr->bcast;
+	p = wn->minrate;
+	w = (Wifipkt*)b->rp;
+	if((w->a1[0] & 1) == 0){
+		flags |= TFlagNeedACK;
+
+		if(BLEN(b) > 512-4)
+			flags |= TFlagNeedRTS;
+
+		if((w->fc[0] & 0x0c) == 0x08 &&	ctlr->bss.id != -1){
+			sta = &ctlr->bss;
+			p = wn->actrate;
+		}
+
+		if(flags & (TFlagNeedRTS|TFlagNeedCTS)){
+			if(ctlr->family >= 7000 || ctlr->type != Type4965){
+				flags &= ~(TFlagNeedRTS|TFlagNeedCTS);
+				flags |= TFlagNeedProtection;
+			} else
+				flags |= TFlagFullTxOp;
+		}
+	}
+
+	if(sta->id == -1)
+		goto Broken;
+
+	if(p >= wifi->rates)
+		rate = p - wifi->rates;
+	else
+		rate = 0;
+
+	/* select first available antenna */
+	ant = ctlr->rfcfg.txantmask & 7;
+	ant |= (ant == 0);
+	ant = ((ant - 1) & ant) ^ ant;
+
+	memset(p = c, 0, sizeof(c));
+	put16(p, BLEN(b));
+	p += 2;
+	p += 2;		/* lnext */
+	put32(p, flags);
+	p += 4;
+	put32(p, 0);
+	p += 4;		/* scratch */
+
+	*p++ = ratetab[rate].plcp;
+	*p++ = ratetab[rate].flags | (ant<<6);
+
+	p += 2;		/* xflags */
+	*p++ = sta->id;	/* station id */
+	*p++ = 0;	/* security */
+	*p++ = 0;	/* linkq */
+	p++;		/* reserved */
+	p += 16;	/* key */
+	p += 2;		/* fnext */
+	p += 2;		/* reserved */
+	put32(p, ~0);	/* lifetime */
+	p += 4;
+
+	/* BUG: scratch ptr? not clear what this is for */
+	put32(p, PCIWADDR(ctlr->kwpage));
+	p += 5;
+
+	*p++ = 60;	/* rts ntries */
+	*p++ = 15;	/* data ntries */
+	*p++ = 0;	/* tid */
+	put16(p, 0);	/* timeout */
+	p += 2;
+	p += 2;		/* txop */
+	qunlock(ctlr);
+
+	if((err = qcmd(ctlr, 0, 28, c, p - c, b)) != nil){
+		print("#l%d: transmit %s\n", edev->ctlrno, err);
+		freeb(b);
+	}
+}
+
+static long
+iwlctl(Ether *edev, void *buf, long n)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(n >= 5 && memcmp(buf, "reset", 5) == 0){
+		ctlr->broken = 1;
+		return n;
+	}
+	if(ctlr->wifi)
+		return wifictl(ctlr->wifi, buf, n);
+	return 0;
+}
+
+static long
+iwlifstat(Ether *edev, void *buf, long n, ulong off)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr->wifi)
+		return wifistat(ctlr->wifi, buf, n, off);
+	return 0;
+}
+
+static void
+setoptions(Ether *edev)
+{
+	Ctlr *ctlr;
+	int i;
+
+	ctlr = edev->ctlr;
+	for(i = 0; i < edev->nopt; i++)
+		wificfg(ctlr->wifi, edev->opt[i]);
+}
+
+static void
+iwlpromiscuous(void *arg, int on)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	qlock(ctlr);
+	ctlr->prom = on;
+	rxon(edev, ctlr->wifi->bss);
+	qunlock(ctlr);
+}
+
+static void
+iwlmulticast(void *, uchar*, int)
+{
+}
+
+static void
+iwlrecover(void *arg)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	while(waserror())
+		;
+	for(;;){
+		tsleep(&up->sleep, return0, 0, 4000);
+
+		qlock(ctlr);
+		for(;;){
+			if(ctlr->broken == 0)
+				break;
+
+			if(ctlr->power)
+				poweroff(ctlr);
+
+			if((csr32r(ctlr, Gpc) & RfKill) == 0)
+				break;
+
+			if(reset(ctlr) != nil)
+				break;
+			if(boot(ctlr) != nil)
+				break;
+
+			rxon(edev, ctlr->wifi->bss);
+			break;
+		}
+		qunlock(ctlr);
+	}
+}
+
+static void
+iwlattach(Ether *edev)
+{
+	FWImage *fw;
+	Ctlr *ctlr;
+	char *err;
+
+	ctlr = edev->ctlr;
+	eqlock(ctlr);
+	if(waserror()){
+		print("#l%d: %s\n", edev->ctlrno, up->errstr);
+		if(ctlr->power)
+			poweroff(ctlr);
+		qunlock(ctlr);
+		nexterror();
+	}
+	if(ctlr->attached == 0){
+		if((csr32r(ctlr, Gpc) & RfKill) == 0)
+			error("wifi disabled by switch");
+
+		if(ctlr->fw == nil){
+			char *fn;
+
+			fn = ctlr->fwname;
+			if(fn == nil){
+				fn = fwname[ctlr->type];
+				if(ctlr->type == Type6005){
+					switch(ctlr->pdev->did){
+					case 0x0082:	/* Centrino Advanced-N 6205 */
+					case 0x0085:	/* Centrino Advanced-N 6205 */
+						break;
+					default:	/* Centrino Advanced-N 6030, 6235 */
+						fn = "iwn-6030";
+					}
+				}
+			}
+			fw = readfirmware(fn);
+			print("#l%d: firmware: %s, rev %ux, build %ud, size [%d] %ux+%ux + [%d] %ux+%ux + %ux\n",
+				edev->ctlrno, fn,
+				fw->rev, fw->build,
+				fw->main.nsect, fw->main.text.size, fw->main.data.size,
+				fw->init.nsect, fw->init.text.size, fw->init.data.size,
+				fw->boot.text.size);
+			ctlr->fw = fw;
+		}
+
+		if(ctlr->family >= 7000){
+			u32int u = ctlr->fw->physku;
+
+			ctlr->rfcfg.type = u & 3;	u >>= 2;
+			ctlr->rfcfg.step = u & 3;	u >>= 2;
+			ctlr->rfcfg.dash = u & 3;	u >>= 12;
+
+			ctlr->rfcfg.txantmask = u & 15;	u >>= 4;
+			ctlr->rfcfg.rxantmask = u & 15;
+		}
+
+		if((err = reset(ctlr)) != nil)
+			error(err);
+		if((err = boot(ctlr)) != nil)
+			error(err);
+
+		if(ctlr->wifi == nil){
+			qsetlimit(edev->oq, MaxQueue);
+
+			ctlr->wifi = wifiattach(edev, transmit);
+			/* tested with 2230, it has transmit issues using higher bit rates */
+			if(ctlr->family >= 7000 || ctlr->type != Type2030)
+				ctlr->wifi->rates = iwlrates;
+		}
+
+		setoptions(edev);
+
+		ctlr->attached = 1;
+
+		kproc("iwlrecover", iwlrecover, edev);
+	}
+	qunlock(ctlr);
+	poperror();
+}
+
+static void
+updatesystime(Ctlr *ctlr, u32int ts)
+{
+	u32int dt = ts - (u32int)ctlr->systime;
+	ctlr->systime += dt;
+}
+
+static void
+receive(Ctlr *ctlr)
+{
+	Block *b, *bb;
+	uchar *d;
+	RXQ *rx;
+	TXQ *tx;
+	uint hw;
+
+	rx = &ctlr->rx;
+	if(ctlr->broken || rx->s == nil || rx->b == nil)
+		return;
+
+	dmaflush(0, rx->s, Rstatsize);
+	hw = get16(rx->s);
+	for(hw %= Nrx; rx->i != hw; rx->i = (rx->i + 1) % Nrx){
+		int type, flags, idx, qid, len;
+
+		b = rx->b[rx->i];
+		if(b == nil)
+			continue;
+
+		d = b->rp;
+		dmaflush(0, d, Rbufsize);
+		len = get32(d); d += 4;
+		type = *d++;
+		flags = *d++;
+		USED(flags);
+		idx = *d++;
+		qid = *d++;
+
+		tx = nil;
+		bb = nil;
+		if((qid & 0x80) == 0 && qid < ctlr->ntxq){
+			tx = &ctlr->tx[qid];
+			bb = tx->b[idx];
+			tx->b[idx] = nil;
+		}
+
+		len &= 0x3fff;
+		len -= 4;
+		if(len >= 0) switch(type){
+		case 1:		/* microcontroller ready */
+			setfwinfo(ctlr, d, len);
+			break;
+		case 24:	/* add node done */
+			if(len < 4)
+				break;
+			break;
+		case 28:	/* tx done */
+			if(ctlr->family >= 7000){
+				if(len <= 36 || d[36] == 1 || d[36] == 2)
+					break;
+			} else if(ctlr->type == Type4965){
+				if(len <= 20 || d[20] == 1 || d[20] == 2)
+					break;
+			} else {
+				if(len <= 32 || d[32] == 1 || d[32] == 2)
+					break;
+			}
+			if(ctlr->wifi != nil)
+				wifitxfail(ctlr->wifi, bb);
+			break;
+		case 41:
+			if(len < 2*4)
+				break;
+			if(get32(d) != 0)
+				break;
+			if(ctlr->te.id == -1)
+				ctlr->te.id = get32(d+8);
+			break;
+		case 42:
+			if(len < 6*4)
+				break;
+			if(ctlr->te.id == -1 || get32(d+8) != ctlr->te.id)
+				break;
+			switch(get32(d+16)){
+			case 1:
+				ctlr->te.active = 1;
+				wakeup(&ctlr->te);
+				break;
+			case 2:
+				ctlr->te.active = 0;
+				ctlr->te.id = -1;
+				wakeup(&ctlr->te);
+			}
+			break;
+		case 102:	/* calibration result (Type5000 only) */
+			if(ctlr->family >= 7000)
+				break;
+			if(len < 4)
+				break;
+			idx = d[0];
+		Calib:
+			if(idx < 0 || idx >= nelem(ctlr->calib.cmd))
+				break;
+			if(rbplant(ctlr, rx->i) < 0)
+				break;
+			if(ctlr->calib.cmd[idx] != nil)
+				freeb(ctlr->calib.cmd[idx]);
+			b->rp = d;
+			b->wp = d + len;
+			ctlr->calib.cmd[idx] = b;
+			break;
+		case 4:		/* init complete (>= 7000 family) */
+			if(ctlr->family < 7000)
+				break;
+			/* wet floor */
+		case 103:	/* calibration done (Type5000 only) */
+			ctlr->calib.done = 1;
+			if(ctlr->wait.w == Ierr)
+				wakeup(&ctlr->wait);
+			break;
+		case 107:	/* calibration result (>= 7000 family) */
+			if(ctlr->family < 7000)
+				break;
+			len -= 4;
+			if(len < 0)
+				break;
+			idx = get16(d+2);
+			if(idx < len)
+				len = idx;
+			idx = -1;
+			switch(get16(d)){
+			case 1:
+				idx = &ctlr->calib.cfg - &ctlr->calib.cmd[0];
+				break;
+			case 2:
+				idx = &ctlr->calib.nch - &ctlr->calib.cmd[0];
+				break;
+			case 4:
+				if(len < 2)
+					break;
+				idx = &ctlr->calib.papd[get16(d+4) % nelem(ctlr->calib.papd)] - &ctlr->calib.cmd[0];
+				break;
+			case 5:
+				if(len < 2)
+					break;
+				idx = &ctlr->calib.txp[get16(d+4) % nelem(ctlr->calib.txp)] - &ctlr->calib.cmd[0];
+				break;
+			}
+			len += 4;
+			goto Calib;
+		case 130:	/* start scan */
+		case 132:	/* stop scan */
+			break;
+		case 136:	/* NVM access (>= 7000 family) */
+			if(ctlr->family < 7000)
+				break;
+			len -= 8;
+			if(len < 0)
+				break;
+			if(ctlr->nvm.len < len)
+				len = ctlr->nvm.len;
+			ctlr->nvm.off = get16(d + 0);
+			ctlr->nvm.ret = get16(d + 2);
+			ctlr->nvm.type= get16(d + 4);
+			ctlr->nvm.sts = get16(d + 6);
+			d += 8;
+			if(ctlr->nvm.ret < len)
+				len = ctlr->nvm.ret;
+			if(ctlr->nvm.buf != nil && len > 0)
+				memmove(ctlr->nvm.buf, d, len);
+			ctlr->nvm.buf = nil;
+			ctlr->nvm.len = 0;
+			break;
+		case 156:	/* rx statistics */
+		case 157:	/* beacon statistics */
+		case 161:	/* state changed */
+		case 162:	/* beacon missed */
+		case 177:	/* mduart load notification */
+			break;
+		case 192:	/* rx phy */
+			if(len >= 8)
+				updatesystime(ctlr, get32(d+4));
+			break;
+		case 195:	/* rx done */
+			if(d + 2 > b->lim)
+				break;
+			d += d[1];
+			d += 56;
+			/* wet floor */
+		case 193:	/* mpdu rx done */
+			if(d + 4 > b->lim)
+				break;
+			len = get16(d);
+			if(ctlr->mqrx){
+				if(d + 48 + len > b->lim)
+					break;
+				updatesystime(ctlr, get32(d+36));
+				if((d[12] & 3) != 3)
+					break;
+				d += 48;
+			} else {
+				d += 4;
+				if(d + len + 4 > b->lim)
+					break;
+				if((d[len] & 3) != 3)
+					break;
+			}
+			if(ctlr->wifi == nil)
+				break;
+			if(rbplant(ctlr, rx->i) < 0)
+				break;
+			b->rp = d;
+			b->wp = d + len;
+
+			put64(d - 8, ctlr->systime);
+			b->flag |= Btimestamp;
+
+			wifiiq(ctlr->wifi, b);
+			break;
+		case 197:	/* rx compressed ba */
+			break;
+		}
+		freeblist(bb);
+		if(tx != nil && tx->n > 0){
+			tx->n--;
+			wakeup(tx);
+			/* unlock 7k family nics as the command is done */
+			if(ctlr->family == 7000 && qid == 4 && tx->n == 0)
+				nicunlock(ctlr);
+		}
+	}
+
+	if(ctlr->mqrx){
+		csr32w(ctlr, FhRxQ0Wptr, ((hw+Nrx-1) % Nrx) & ~7);
+	}else
+		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 | Ialive)) || (fhisr & Ifhrx))
+		receive(ctlr);
+	if(isr & Ierr){
+		ctlr->broken = 1;
+		print("#l%d: fatal firmware error\n", edev->ctlrno);
+		dumpctlr(ctlr);
+	}
+	ctlr->wait.m |= isr;
+	if(ctlr->wait.m & ctlr->wait.w)
+		wakeup(&ctlr->wait);
+done:
+	csr32w(ctlr, Imr, ctlr->ie);
+	iunlock(ctlr);
+}
+
+static void
+iwlshutdown(Ether *edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr->power)
+		poweroff(ctlr);
+	ctlr->broken = 0;
+	pcidisable(ctlr->pdev);
+}
+
+static Ctlr *iwlhead, *iwltail;
+
+static void
+iwlpci(void)
+{
+	Pcidev *pdev;
+	char *fwname;
+	int family;
+	
+	pdev = nil;
+	while(pdev = pcimatch(pdev, Vintel, 0)) {
+		Ctlr *ctlr;
+		void *mem;
+		
+		if(pdev->ccrb != 2 || pdev->ccru != 0x80)
+			continue;
+		if(pdev->mem[0].bar & 1)
+			continue;
+
+		switch(pdev->did){
+		default:
+			continue;
+		case 0x0084:	/* WiFi Link 1000 */
+		case 0x4229:	/* WiFi Link 4965 */
+		case 0x4230:	/* WiFi Link 4965 */
+		case 0x4232:	/* Wifi Link 5100 */
+		case 0x4235:	/* Intel Corporation Ultimate N WiFi Link 5300 */
+		case 0x4236:	/* WiFi Link 5300 AGN */
+		case 0x4237:	/* Wifi Link 5100 AGN */
+		case 0x4239:	/* Centrino Advanced-N 6200 */
+		case 0x423d:	/* Wifi Link 5150 */
+		case 0x423b:	/* PRO/Wireless 5350 AGN */
+		case 0x0082:	/* Centrino Advanced-N 6205 */
+		case 0x0085:	/* Centrino Advanced-N 6205 */
+		case 0x0089:    /* Centrino Advanced-N + WiMAX 6250 */
+		case 0x422b:	/* Centrino Ultimate-N 6300 variant 1 */
+		case 0x4238:	/* Centrino Ultimate-N 6300 variant 2 */
+		case 0x08ae:	/* Centrino Wireless-N 100 */
+		case 0x0083:	/* Centrino Wireless-N 1000 */
+		case 0x008a:	/* Centrino Wireless-N 1030 */
+		case 0x0891:	/* Centrino Wireless-N 2200 */
+		case 0x0887:	/* Centrino Wireless-N 2230 */
+		case 0x0888:	/* Centrino Wireless-N 2230 */
+		case 0x0090:	/* Centrino Advanced-N 6030 */
+		case 0x0091:	/* Centrino Advanced-N 6030 */
+		case 0x088e:	/* Centrino Advanced-N 6235 */
+		case 0x088f:	/* Centrino Advanced-N 6235 */
+			family = 0;
+			fwname = nil;
+			break;
+		case 0x08b1:	/* Wireless AC 7260 */
+		case 0x08b2:	/* Wireless AC 7260 */
+			family = 7000;
+			fwname = "iwm-7260-17";
+			break;
+		case 0x095a:	/* Wireless AC 7265 */
+		case 0x095b:	/* Wireless AC 7265 */
+			family = 7000;
+			fwname = "iwm-7265-17";
+			break;
+		case 0x24f3:	/* Wireless AC 8260 */
+			family = 8000;
+			fwname = "iwm-8000C-34";
+			break;
+		case 0x24fd:	/* Wireless AC 8265 */
+			family = 8000;
+			fwname = "iwm-8265-34";
+			break;
+		case 0x2526:	/* Wireless AC 9260 */
+			family = 9000;
+			fwname = "iwm-9260-34";
+			break;
+		}
+
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil) {
+			print("iwl: unable to alloc Ctlr\n");
+			continue;
+		}
+		ctlr->port = pdev->mem[0].bar & ~0xF;
+		mem = vmap(ctlr->port, pdev->mem[0].size);
+		if(mem == nil) {
+			print("iwl: can't map %llux\n", ctlr->port);
+			free(ctlr);
+			continue;
+		}
+		ctlr->nic = mem;
+		ctlr->pdev = pdev;
+		ctlr->fwname = fwname;
+		ctlr->family = family;
+		ctlr->mqrx = family >= 9000;
+
+		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->edev != nil)
+			continue;
+		if(edev->port == 0 || edev->port == ctlr->port){
+			ctlr->edev = edev;
+			break;
+		}
+	}
+
+	if(ctlr == nil)
+		return -1;
+
+	edev->ctlr = ctlr;
+	edev->port = ctlr->port;
+	edev->irq = ctlr->pdev->intl;
+	edev->tbdf = ctlr->pdev->tbdf;
+	edev->arg = edev;
+	edev->attach = iwlattach;
+	edev->ifstat = iwlifstat;
+	edev->ctl = iwlctl;
+	edev->shutdown = iwlshutdown;
+	edev->promiscuous = iwlpromiscuous;
+	edev->multicast = iwlmulticast;
+	edev->mbps = 54;
+
+	pcienable(ctlr->pdev);
+	if(iwlinit(edev) < 0){
+		pcidisable(ctlr->pdev);
+		ctlr->edev = (void*)-1;
+		edev->ctlr = nil;
+		goto again;
+	}
+
+	pcisetbme(ctlr->pdev);
+	intrenable(edev->irq, iwlinterrupt, edev, edev->tbdf, edev->name);
+	
+	return 0;
+}
+
+void
+etheriwllink(void)
+{
+	addethercard("iwl", iwlpnp);
+}
--- a/sys/src/9/port/portmkfile
+++ b/sys/src/9/port/portmkfile
@@ -114,5 +114,6 @@
 devether.$O ethersink.$O:	../port/etherif.h ../port/netif.h
 wifi.$O:	../port/etherif.h ../port/netif.h ../port/wifi.h /sys/include/libsec.h
 wifi.$O:	../ip/ip.h ../ip/ipv6.h
+etheriwl.$O:	../port/wifi.h
 ethermii.$O:	../port/ethermii.h
 pci.$O devpnp.$O devpci.$O usbxhcipci.$O:	../port/pci.h