9front - general discussion about 9front
 help / color / mirror / Atom feed
* rpi3 wifi
@ 2020-08-23  5:26 Kurt H Maier
  2020-10-13 12:16 ` [9front] " Eli Cohen
  0 siblings, 1 reply; 8+ messages in thread
From: Kurt H Maier @ 2020-08-23  5:26 UTC (permalink / raw)
  To: 9front

Yo,

I'm trying to get Miller's ether4330 driver working in the bcm64 kernel.
Here is what I have so far: http://sciops.net/downloads/wifi.diff

This compiles and link and boots, but then enumerates the same wifi
device multiple times... and times out when you try to talk to any of
them.  Note you'll need the firmware files in /sys/lib/firmware to try
this; I've been using the ones at http://sciops.net/downloads/pif.tar if
you want to replicate my failure precisely.

Anyone have any ideas?  I am not a programmer.

khm


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-08-23  5:26 rpi3 wifi Kurt H Maier
@ 2020-10-13 12:16 ` Eli Cohen
  2020-10-16  3:42   ` kokamoto
  0 siblings, 1 reply; 8+ messages in thread
From: Eli Cohen @ 2020-10-13 12:16 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 207 bytes --]

I managed to get this working with the 32-bit kernel, though it
doesn't like my access point

it isn't really clean enough to be included...

I'm using the linux firmware, I dunno if that makes a difference

[-- Attachment #2: pi2wifi.diff --]
[-- Type: application/octet-stream, Size: 71372 bytes --]

diff -r 44eae9a7e98b sys/src/9/bcm/emmc.c
--- a/sys/src/9/bcm/emmc.c	Sun Oct 11 14:59:49 2020 +0200
+++ b/sys/src/9/bcm/emmc.c	Tue Oct 13 05:09:49 2020 -0700
@@ -10,7 +10,7 @@
 		22-27 (P1 header)
 		34-39 (wifi - pi3 only)
 	using ALT3 function to activate the required routing
-*/
+ */
 
 #include "u.h"
 #include "../port/lib.h"
@@ -34,12 +34,11 @@
 	GoIdle		= 0,		/* mmc/sdio go idle state */
 	MMCSelect	= 7,		/* mmc/sd card select command */
 	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-
 	Switchfunc	= 6,		/* mmc/sd switch function command */
-	Voltageswitch	= 11,		/* md/sdio switch to 1.8V */
-	IORWdirect	= 52,		/* sdio read/write direct command */
-	IORWextended	= 53,		/* sdio read/write extended command */
-	Appcmd		= 55,		/* mmc/sd application command prefix */
+	Voltageswitch = 11,		/* md/sdio switch to 1.8V */
+	IORWdirect = 52,		/* sdio read/write direct command */
+	IORWextended = 53,		/* sdio read/write extended command */
+	Appcmd = 55,			/* mmc/sd application command prefix */
 };
 
 enum {
@@ -72,7 +71,7 @@
 	Slotisrver		= 0xfc>>2,
 
 	/* Control0 */
-	Hispeed			= 1<<2,	
+	Hispeed			= 1<<2,
 	Dwidth4			= 1<<1,
 	Dwidth1			= 0<<1,
 
@@ -122,7 +121,7 @@
 	Ccrcerr		= 1<<17,
 	Ctoerr		= 1<<16,
 	Err		= 1<<15,
-	Cardintr	= 1<<8,		/* not in Broadcom datasheet */
+	Cardintr	= 1<<8,
 	Cardinsert	= 1<<6,		/* not in Broadcom datasheet */
 	Readrdy		= 1<<5,
 	Writerdy	= 1<<4,
@@ -158,7 +157,7 @@
 [25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken,
 [41] Resp48,
 [52] Resp48 | Ixchken | Crcchken,
-[53] Resp48 | Ixchken | Crcchken | Isdata,
+[53] Resp48	| Ixchken | Crcchken | Isdata,
 [55] Resp48 | Ixchken | Crcchken,
 };
 
@@ -182,7 +181,7 @@
 	u32int *r = (u32int*)EMMCREGS;
 
 	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
-	microdelay(emmc.fastclock ? 2: 20);
+	microdelay(emmc.fastclock? 2 : 20);
 	coherence();
 	r[reg] = val;
 }
@@ -288,6 +287,24 @@
 	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, "mmc");
 }
 
+int
+sdiocardintr(int wait)
+{
+	u32int *r;
+	int i;
+
+	r = (u32int*)EMMCREGS;
+	WR(Interrupt, Cardintr);
+	while(((i = r[Interrupt]) & Cardintr) == 0){
+		if(!wait)
+			return 0;
+		WR(Irpten, r[Irpten] | Cardintr);
+		sleep(&emmc.cardr, cardintready, 0);
+	}
+	WR(Interrupt, Cardintr);
+	return i;
+}
+
 static int
 emmccmd(u32int cmd, u32int arg, u32int *resp)
 {
@@ -319,8 +336,7 @@
 		WR(Control0, r[Control0] & ~(Dwidth4|Hispeed));
 		emmcclk(Initfreq);
 	}
-	if((r[Status] & Datinhibit) &&
-	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
+	if(r[Status] & Cmdinhibit){
 		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstcmd);
@@ -329,8 +345,8 @@
 		while(r[Status] & Cmdinhibit)
 			;
 	}
-	if((c & Isdata || (c & Respmask) == Resp48busy) &&
-	    r[Status] & Datinhibit){
+	if((r[Status] & Datinhibit) &&
+	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
 		print("emmccmd: need to reset Datinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstdata);
@@ -435,7 +451,7 @@
 	return 0;
 }
 
-static void
+void
 emmciosetup(int write, void *buf, int bsize, int bcount)
 {
 	USED(write);
@@ -464,6 +480,8 @@
 			&r[Data], buf, len);
 	if(dmawait(DmaChanEmmc) < 0)
 		error(Eio);
+	if(!write)
+		cachedinvse(buf, len);
 	WR(Irpten, r[Irpten]|Datadone|Err);
 	tsleep(&emmc.r, datadone, 0, 3000);
 	i = r[Interrupt]&~Cardintr;
@@ -508,5 +526,4 @@
 	emmccmd,
 	emmciosetup,
 	emmcio,
-	.highspeed = 1,
 };
diff -r 44eae9a7e98b sys/src/9/bcm/ether4330.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/ether4330.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,2396 @@
+/*
+ * Broadcom bcm4330 wifi (sdio interface)
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/sd.h"
+
+extern int sdiocardintr(int);
+
+#include "../port/etherif.h"
+#define CACHELINESZ 64	/* temp */
+
+enum{
+	SDIODEBUG = 0,
+	SBDEBUG = 0,
+	EVENTDEBUG = 0,
+	VARDEBUG = 0,
+	FWDEBUG  = 0,
+
+	Corescansz = 512,
+	Uploadsz = 2048,
+
+	Wifichan = 0,		/* default channel */
+	Firmwarecmp	= 1,
+
+	ARMcm3		= 0x82A,
+	ARM7tdmi	= 0x825,
+	ARMcr4		= 0x83E,
+
+	Fn0	= 0,
+	Fn1 	= 1,
+	Fn2	= 2,
+	Fbr1	= 0x100,
+	Fbr2	= 0x200,
+
+	/* CCCR */
+	Ioenable	= 0x02,
+	Ioready		= 0x03,
+	Intenable	= 0x04,
+	Intpend		= 0x05,
+	Ioabort		= 0x06,
+	Busifc		= 0x07,
+	Capability	= 0x08,
+	Blksize		= 0x10,
+	Highspeed	= 0x13,
+
+	/* SDIOCommands */
+	GO_IDLE_STATE		= 0,
+	SEND_RELATIVE_ADDR	= 3,
+	IO_SEND_OP_COND		= 5,
+	SELECT_CARD		= 7,
+	VOLTAGE_SWITCH 		= 11,
+	IO_RW_DIRECT 		= 52,
+	IO_RW_EXTENDED 		= 53,
+
+	/* SELECT_CARD args */
+	Rcashift	= 16,
+
+	/* SEND_OP_COND args */
+	Hcs	= 1<<30,	/* host supports SDHC & SDXC */
+	V3_3	= 3<<20,	/* 3.2-3.4 volts */
+	V2_8	= 3<<15,	/* 2.7-2.9 volts */
+	V2_0	= 1<<8,		/* 2.0-2.1 volts */
+	S18R	= 1<<24,	/* switch to 1.8V request */
+
+	/* Sonics Silicon Backplane (access to cores on chip) */
+	Sbwsize	= 0x8000,
+	Sb32bit	= 0x8000,
+	Sbaddr	= 0x1000a,
+		Enumbase	= 	0x18000000,
+	Framectl= 0x1000d,
+		Rfhalt		=	0x01,
+		Wfhalt		=	0x02,
+	Clkcsr	= 0x1000e,
+		ForceALP	=	0x01,	/* active low-power clock */
+		ForceHT		= 	0x02,	/* high throughput clock */
+		ForceILP	=	0x04,	/* idle low-power clock */
+		ReqALP		=	0x08,
+		ReqHT		=	0x10,
+		Nohwreq		=	0x20,
+		ALPavail	=	0x40,
+		HTavail		=	0x80,
+	Pullups	= 0x1000f,
+	Wfrmcnt	= 0x10019,
+	Rfrmcnt	= 0x1001b,
+		
+	/* core control regs */
+	Ioctrl		= 0x408,
+	Resetctrl	= 0x800,
+
+	/* socram regs */
+	Coreinfo	= 0x00,
+	Bankidx		= 0x10,
+	Bankinfo	= 0x40,
+	Bankpda		= 0x44,
+
+	/* armcr4 regs */
+	Cr4Cap		= 0x04,
+	Cr4Bankidx	= 0x40,
+	Cr4Bankinfo	= 0x44,
+	Cr4Cpuhalt	= 0x20,
+
+	/* chipcommon regs */
+	Gpiopullup	= 0x58,
+	Gpiopulldown	= 0x5c,
+	Chipctladdr	= 0x650,
+	Chipctldata	= 0x654,
+
+	/* sdio core regs */
+	Intstatus	= 0x20,
+		Fcstate		= 1<<4,
+		Fcchange	= 1<<5,
+		FrameInt	= 1<<6,
+		MailboxInt	= 1<<7,
+	Intmask		= 0x24,
+	Sbmbox		= 0x40,
+	Sbmboxdata	= 0x48,
+	Hostmboxdata= 0x4c,
+		Fwready		= 0x80,
+
+	/* wifi control commands */
+	GetVar	= 262,
+	SetVar	= 263,
+
+	/* status */
+	Disconnected=	0,
+	Connecting,
+	Connected,
+};
+
+typedef struct Ctlr Ctlr;
+
+enum{
+	Wpa		= 1,
+	Wep		= 2,
+	Wpa2		= 3,
+	WNameLen	= 32,
+	WNKeys		= 4,
+	WKeyLen		= 32,
+	WMinKeyLen	= 5,
+	WMaxKeyLen	= 13,
+};
+
+typedef struct WKey WKey;
+struct WKey
+{
+	ushort	len;
+	char	dat[WKeyLen];
+};
+
+struct Ctlr {
+	Ether*	edev;
+	QLock	cmdlock;
+	QLock	pktlock;
+	QLock	tlock;
+	QLock	alock;
+	Lock	txwinlock;
+	Rendez	cmdr;
+	Rendez	joinr;
+	int	joinstatus;
+	int	cryptotype;
+	int	chanid;
+	char	essid[WNameLen + 1];
+	WKey	keys[WNKeys];
+	Block	*rsp;
+	Block	*scanb;
+	int	scansecs;
+	int	status;
+	int	chipid;
+	int	chiprev;
+	int	armcore;
+	char	*regufile;
+	union {
+		u32int i;
+		uchar c[4];
+	} resetvec;
+	ulong	chipcommon;
+	ulong	armctl;
+	ulong	armregs;
+	ulong	d11ctl;
+	ulong	socramregs;
+	ulong	socramctl;
+	ulong	sdregs;
+	int	sdiorev;
+	int	socramrev;
+	ulong	socramsize;
+	ulong	rambase;
+	short	reqid;
+	uchar	fcmask;
+	uchar	txwindow;
+	uchar	txseq;
+	uchar	rxseq;
+};
+
+enum{
+	CMauth,
+	CMchannel,
+	CMcrypt,
+	CMessid,
+	CMkey1,
+	CMkey2,
+	CMkey3,
+	CMkey4,
+	CMrxkey,
+	CMrxkey0,
+	CMrxkey1,
+	CMrxkey2,
+	CMrxkey3,
+	CMtxkey,
+	CMdebug,
+	CMjoin,
+};
+
+static Cmdtab cmds[] = {
+	{CMauth,	"auth", 2},
+	{CMchannel,	"channel", 2},
+	{CMcrypt,	"crypt", 2},
+	{CMessid,	"essid", 2},
+	{CMkey1,	"key1",	2},
+	{CMkey2,	"key1",	2},
+	{CMkey3,	"key1",	2},
+	{CMkey4,	"key1",	2},
+	{CMrxkey,	"rxkey", 3},
+	{CMrxkey0,	"rxkey0", 3},
+	{CMrxkey1,	"rxkey1", 3},
+	{CMrxkey2,	"rxkey2", 3},
+	{CMrxkey3,	"rxkey3", 3},
+	{CMtxkey,	"txkey", 3},
+	{CMdebug,	"debug", 2},
+	{CMjoin,	"join", 5},
+};
+
+typedef struct Sdpcm Sdpcm;
+typedef struct Cmd Cmd;
+struct Sdpcm {
+	uchar	len[2];
+	uchar	lenck[2];
+	uchar	seq;
+	uchar	chanflg;
+	uchar	nextlen;
+	uchar	doffset;
+	uchar	fcmask;
+	uchar	window;
+	uchar	version;
+	uchar	pad;
+};
+
+struct Cmd {
+	uchar	cmd[4];
+	uchar	len[4];
+	uchar	flags[2];
+	uchar	id[2];
+	uchar	status[4];
+};
+
+static char config40181[] = "bcmdhd.cal.40181";
+static char config40183[] = "bcmdhd.cal.40183.26MHz";
+
+struct {
+	int chipid;
+	int chiprev;
+	char *fwfile;
+	char *cfgfile;
+	char *regufile;
+} firmware[] = {
+	{ 0x4330, 3,	"fw_bcm40183b1.bin", config40183, 0 },
+	{ 0x4330, 4,	"fw_bcm40183b2.bin", config40183, 0 },
+	{ 43362, 0,	"fw_bcm40181a0.bin", config40181, 0 },
+	{ 43362, 1,	"fw_bcm40181a2.bin", config40181, 0 },
+	{ 43430, 1,	"brcmfmac43430-sdio.bin", "brcmfmac43430-sdio.txt", 0 },
+	{ 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" },
+};
+
+static QLock sdiolock;
+static int iodebug;
+
+static void etherbcmintr(void *);
+static void bcmevent(Ctlr*, uchar*, int);
+static void wlscanresult(Ether*, uchar*, int);
+static void wlsetvar(Ctlr*, char*, void*, int);
+
+static uchar*
+put2(uchar *p, short v)
+{
+	p[0] = v;
+	p[1] = v >> 8;
+	return p + 2;
+}
+
+static uchar*
+put4(uchar *p, long v)
+{
+	p[0] = v;
+	p[1] = v >> 8;
+	p[2] = v >> 16;
+	p[3] = v >> 24;
+	return p + 4;
+}
+
+static ushort
+get2(uchar *p)
+{
+	return p[0] | p[1]<<8;
+}
+
+static ulong
+get4(uchar *p)
+{
+	return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+}
+
+static void
+dump(char *s, void *a, int n)
+{
+	int i;
+	uchar *p;
+
+	p = a;
+	print("%s:", s);
+	for(i = 0; i < n; i++)
+		print("%c%2.2x", i&15? ' ' : '\n', *p++);
+	print("\n");
+}
+
+/*
+ * SDIO communication with dongle
+ */
+static ulong
+sdiocmd_locked(int cmd, ulong arg)
+{
+	u32int resp[4];
+
+	sdio.cmd(cmd, arg, resp);
+	return resp[0];
+}
+
+static ulong
+sdiocmd(int cmd, ulong arg)
+{
+	ulong r;
+
+	qlock(&sdiolock);
+	if(waserror()){
+		if(SDIODEBUG) print("sdiocmd error: cmd %d arg %lux\n", cmd, arg);
+		qunlock(&sdiolock);
+		nexterror();
+	}
+	r = sdiocmd_locked(cmd, arg);
+	qunlock(&sdiolock);
+	poperror();
+	return r;
+
+}
+
+static ulong
+trysdiocmd(int cmd, ulong arg)
+{
+	ulong r;
+
+	if(waserror())
+		return 0;
+	r = sdiocmd(cmd, arg);
+	poperror();
+	return r;
+}
+
+static int
+sdiord(int fn, int addr)
+{
+	int r;
+
+	r = sdiocmd(IO_RW_DIRECT, (0<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9));
+	if(r & 0xCF00){
+		print("ether4330: sdiord(%x, %x) fail: %2.2ux %2.2ux\n", fn, addr, (r>>8)&0xFF, r&0xFF);
+		error(Eio);
+	}
+	return r & 0xFF;
+}
+
+static void
+sdiowr(int fn, int addr, int data)
+{
+	int r;
+	int retry;
+
+	r = 0;
+	for(retry = 0; retry < 10; retry++){
+		r = sdiocmd(IO_RW_DIRECT, (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF));
+		if((r & 0xCF00) == 0)
+			return;
+	}
+	print("ether4330: sdiowr(%x, %x, %x) fail: %2.2ux %2.2ux\n", fn, addr, data, (r>>8)&0xFF, r&0xFF);
+	error(Eio);
+}
+
+static void
+sdiorwext(int fn, int write, void *a, int len, int addr, int incr)
+{
+	int bsize, blk, bcount, m;
+
+	bsize = fn == Fn2? 512 : 64;
+	while(len > 0){
+		if(len >= 511*bsize){
+			blk = 1;
+			bcount = 511;
+			m = bcount*bsize;
+		}else if(len > bsize){
+			blk = 1;
+			bcount = len/bsize;
+			m = bcount*bsize;
+		}else{
+			blk = 0;
+			bcount = len;
+			m = bcount;
+		}
+		qlock(&sdiolock);
+		if(waserror()){
+			print("ether4330: sdiorwext fail: %s\n", up->errstr);
+			qunlock(&sdiolock);
+			nexterror();
+		}
+		if(blk)
+			sdio.iosetup(write, a, bsize, bcount);
+		else
+			sdio.iosetup(write, a, bcount, 1);
+		sdiocmd_locked(IO_RW_EXTENDED,
+			write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF));
+		sdio.io(write, a, m);
+		qunlock(&sdiolock);
+		poperror();
+		len -= m;
+		a = (char*)a + m;
+		if(incr)
+			addr += m;
+	}
+}
+
+static void
+sdioset(int fn, int addr, int bits)
+{
+	sdiowr(fn, addr, sdiord(fn, addr) | bits);
+}
+	
+static void
+sdioinit(void)
+{
+	ulong ocr, rca;
+	int i;
+
+	// disconnect emmc from SD card (connect sdhost instead)
+	for(i = 48; i <= 53; i++)
+		gpiosel(i, Alt0);
+	// connect emmc to wifi
+	for(i = 34; i <= 39; i++){
+		gpiosel(i, Alt3);
+		if(i == 34)
+			gpiopulloff(i);
+		else
+			gpiopullup(i);
+	}
+	sdio.init();
+	sdio.enable();
+	sdiocmd(GO_IDLE_STATE, 0);
+	ocr = trysdiocmd(IO_SEND_OP_COND, 0);
+	i = 0;
+	while((ocr & (1<<31)) == 0){
+		if(++i > 5){
+			print("ether4330: no response to sdio access: ocr = %lux\n", ocr);
+			error(Eio);
+		}
+		ocr = trysdiocmd(IO_SEND_OP_COND, V3_3);
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	rca = sdiocmd(SEND_RELATIVE_ADDR, 0) >> Rcashift;
+	sdiocmd(SELECT_CARD, rca << Rcashift);
+	sdioset(Fn0, Highspeed, 2);
+	sdioset(Fn0, Busifc, 2);	// bus width 4
+	sdiowr(Fn0, Fbr1+Blksize, 64);
+	sdiowr(Fn0, Fbr1+Blksize+1, 64>>8);
+	sdiowr(Fn0, Fbr2+Blksize, 512);
+	sdiowr(Fn0, Fbr2+Blksize+1, 512>>8);
+	sdioset(Fn0, Ioenable, 1<<Fn1);
+	sdiowr(Fn0, Intenable, 0);
+	for(i = 0; !(sdiord(Fn0, Ioready) & 1<<Fn1); i++){
+		if(i == 10){
+			print("ether4330: can't enable SDIO function\n");
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+}
+
+static void
+sdioreset(void)
+{
+	sdiowr(Fn0, Ioabort, 1<<3);	/* reset */
+}
+
+static void
+sdioabort(int fn)
+{
+	sdiowr(Fn0, Ioabort, fn);
+}
+
+/*
+ * Chip register and memory access via SDIO
+ */
+
+static void
+cfgw(ulong off, int val)
+{
+	sdiowr(Fn1, off, val);
+}
+
+static int
+cfgr(ulong off)
+{
+	return sdiord(Fn1, off);
+}
+
+static ulong
+cfgreadl(int fn, ulong off)
+{
+	uchar cbuf[2*CACHELINESZ];
+	uchar *p;
+
+	p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ);
+	memset(p, 0, 4);
+	sdiorwext(fn, 0, p, 4, off|Sb32bit, 1);
+	if(SDIODEBUG) print("cfgreadl %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]);
+	return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+}
+
+static void
+cfgwritel(int fn, ulong off, u32int data)
+{
+	uchar cbuf[2*CACHELINESZ];
+	uchar *p;
+	int retry;
+
+	p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ);
+	put4(p, data);
+	if(SDIODEBUG) print("cfgwritel %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]);
+	retry = 0;
+	while(waserror()){
+		print("ether4330: cfgwritel retry %lux %ux\n", off, data);
+		sdioabort(fn);
+		if(++retry == 3)
+			nexterror();
+	}
+	sdiorwext(fn, 1, p, 4, off|Sb32bit, 1);
+	poperror();
+}
+
+static void
+sbwindow(ulong addr)
+{
+	addr &= ~(Sbwsize-1);
+	cfgw(Sbaddr, addr>>8);
+	cfgw(Sbaddr+1, addr>>16);
+	cfgw(Sbaddr+2, addr>>24);
+}
+
+static void
+sbrw(int fn, int write, uchar *buf, int len, ulong off)
+{
+	int n;
+	USED(fn);
+
+	if(waserror()){
+		print("ether4330: sbrw err off %lux len %ud\n", off, len);
+		nexterror();
+	}
+	if(write){
+		if(len >= 4){
+			n = len;
+			n &= ~3;
+			sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1);
+			off += n;
+			buf += n;
+			len -= n;
+		}
+		while(len > 0){
+			sdiowr(Fn1, off|Sb32bit, *buf);
+			off++;
+			buf++;
+			len--;
+		}
+	}else{
+		if(len >= 4){
+			n = len;
+			n &= ~3;
+			sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1);
+			off += n;
+			buf += n;
+			len -= n;
+		}
+		while(len > 0){
+			*buf = sdiord(Fn1, off|Sb32bit);
+			off++;
+			buf++;
+			len--;
+		}
+	}
+	poperror();
+}
+
+static void
+sbmem(int write, uchar *buf, int len, ulong off)
+{
+	ulong n;
+
+	n = ROUNDUP(off, Sbwsize) - off;
+	if(n == 0)
+		n = Sbwsize;
+	while(len > 0){
+		if(n > len)
+			n = len;
+		sbwindow(off);
+		sbrw(Fn1, write, buf, n, off & (Sbwsize-1));
+		off += n;
+		buf += n;
+		len -= n;
+		n = Sbwsize;
+	}
+}
+
+static void
+packetrw(int write, uchar *buf, int len)
+{
+	int n;
+	int retry;
+
+	n = 2048;
+	while(len > 0){
+		if(n > len)
+			n = ROUND(len, 4);
+		retry = 0;
+		while(waserror()){
+			sdioabort(Fn2);
+			if(++retry == 3)
+				nexterror();
+		}
+		sdiorwext(Fn2, write, buf, n, Enumbase, 0);
+		poperror();
+		buf += n;
+		len -= n;
+	}
+}
+
+/*
+ * Configuration and control of chip cores via Silicon Backplane
+ */
+
+static void
+sbdisable(ulong regs, int pre, int ioctl)
+{
+	sbwindow(regs);
+	if((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){
+		cfgwritel(Fn1, regs + Ioctrl, 3|ioctl);
+		cfgreadl(Fn1, regs + Ioctrl);
+		return;
+	}
+	cfgwritel(Fn1, regs + Ioctrl, 3|pre);
+	cfgreadl(Fn1, regs + Ioctrl);
+	cfgwritel(Fn1, regs + Resetctrl, 1);
+	microdelay(10);
+	while((cfgreadl(Fn1, regs + Resetctrl) & 1) == 0)
+		;
+	cfgwritel(Fn1, regs + Ioctrl, 3|ioctl);
+	cfgreadl(Fn1, regs + Ioctrl);
+}
+
+static void
+sbreset(ulong regs, int pre, int ioctl)
+{
+	sbdisable(regs, pre, ioctl);
+	sbwindow(regs);
+	if(SBDEBUG) print("sbreset %#p %#lux %#lux ->", (void*)regs,
+		cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl));
+	while((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){
+		cfgwritel(Fn1, regs + Resetctrl, 0);
+		microdelay(40);
+	}
+	cfgwritel(Fn1, regs + Ioctrl, 1|ioctl);
+	cfgreadl(Fn1, regs + Ioctrl);
+	if(SBDEBUG) print("%#lux %#lux\n",
+		cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl));
+}
+
+static void
+corescan(Ctlr *ctl, ulong r)
+{
+	uchar *buf;
+	int i, coreid, corerev;
+	ulong addr;
+
+	buf = sdmalloc(Corescansz);
+	if(buf == nil)
+		error(Enomem);
+	sbmem(0, buf, Corescansz, r);
+	coreid = 0;
+	corerev = 0;
+	for(i = 0; i < Corescansz; i += 4){
+		switch(buf[i]&0xF){
+		case 0xF:	/* end */
+			sdfree(buf);
+			return;
+		case 0x1:	/* core info */
+			if((buf[i+4]&0xF) != 0x1)
+				break;
+			coreid = (buf[i+1] | buf[i+2]<<8) & 0xFFF;
+			i += 4;
+			corerev = buf[i+3];
+			break;
+		case 0x05:	/* address */
+			addr = buf[i+1]<<8 | buf[i+2]<<16 | buf[i+3]<<24;
+			addr &= ~0xFFF;
+			if(SBDEBUG) print("core %x %s %#p\n", coreid, buf[i]&0xC0? "ctl" : "mem", (void*)addr);
+			switch(coreid){
+			case 0x800:
+				if((buf[i] & 0xC0) == 0)
+					ctl->chipcommon = addr;
+				break;
+			case ARMcm3:
+			case ARM7tdmi:
+			case ARMcr4:
+				ctl->armcore = coreid;
+				if(buf[i] & 0xC0){
+					if(ctl->armctl == 0)
+						ctl->armctl = addr;
+				}else{
+					if(ctl->armregs == 0)
+						ctl->armregs = addr;
+				}
+				break;
+			case 0x80E:
+				if(buf[i] & 0xC0)
+					ctl->socramctl = addr;
+				else if(ctl->socramregs == 0)
+					ctl->socramregs = addr;
+				ctl->socramrev = corerev;
+				break;
+			case 0x829:
+				if((buf[i] & 0xC0) == 0)
+					ctl->sdregs = addr;
+				ctl->sdiorev = corerev;
+				break;
+			case 0x812:
+				if(buf[i] & 0xC0)
+					ctl->d11ctl = addr;
+				break;
+			}
+		}
+	}
+	sdfree(buf);
+}
+
+static void
+ramscan(Ctlr *ctl)
+{
+	ulong r, n, size;
+	int banks, i;
+
+	if(ctl->armcore == ARMcr4){
+		r = ctl->armregs;
+		sbwindow(r);
+		n = cfgreadl(Fn1, r + Cr4Cap);
+		if(SBDEBUG) print("cr4 banks %lux\n", n);
+		banks = ((n>>4) & 0xF) + (n & 0xF);
+		size = 0;
+		for(i = 0; i < banks; i++){
+			cfgwritel(Fn1, r + Cr4Bankidx, i);
+			n = cfgreadl(Fn1, r + Cr4Bankinfo);
+			if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1));
+			size += 8192 * ((n & 0x3F) + 1);
+		}
+		ctl->socramsize = size;
+		ctl->rambase = 0x198000;
+		return;
+	}
+	if(ctl->socramrev <= 7 || ctl->socramrev == 12){
+		print("ether4330: SOCRAM rev %d not supported\n", ctl->socramrev);
+		error(Eio);
+	}
+	sbreset(ctl->socramctl, 0, 0);
+	r = ctl->socramregs;
+	sbwindow(r);
+	n = cfgreadl(Fn1, r + Coreinfo);
+	if(SBDEBUG) print("socramrev %d coreinfo %lux\n", ctl->socramrev, n);
+	banks = (n>>4) & 0xF;
+	size = 0;
+	for(i = 0; i < banks; i++){
+		cfgwritel(Fn1, r + Bankidx, i);
+		n = cfgreadl(Fn1, r + Bankinfo);
+		if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1));
+		size += 8192 * ((n & 0x3F) + 1);
+	}
+	ctl->socramsize = size;
+	ctl->rambase = 0;
+	if(ctl->chipid == 43430){
+		cfgwritel(Fn1, r + Bankidx, 3);
+		cfgwritel(Fn1, r + Bankpda, 0);
+	}
+}
+
+static void
+sbinit(Ctlr *ctl)
+{
+	ulong r;
+	int chipid;
+	char buf[16];
+
+	sbwindow(Enumbase);
+	r = cfgreadl(Fn1, Enumbase);
+	chipid = r & 0xFFFF;
+	sprint(buf, chipid > 43000 ? "%d" : "%#x", chipid);
+	print("ether4330: chip %s rev %ld type %ld\n", buf, (r>>16)&0xF, (r>>28)&0xF);
+	switch(chipid){
+		case 0x4330:
+		case 43362:
+		case 43430:
+		case 0x4345:
+			ctl->chipid = chipid;
+			ctl->chiprev = (r>>16)&0xF;
+			break;
+		default:
+			print("ether4330: chipid %#x (%d) not supported\n", chipid, chipid);
+			error(Eio);
+	}
+	r = cfgreadl(Fn1, Enumbase + 63*4);
+	corescan(ctl, r);
+	if(ctl->armctl == 0 || ctl->d11ctl == 0 ||
+	   (ctl->armcore == ARMcm3 && (ctl->socramctl == 0 || ctl->socramregs == 0)))
+		error("corescan didn't find essential cores\n");
+	if(ctl->armcore == ARMcr4)
+		sbreset(ctl->armctl, Cr4Cpuhalt, Cr4Cpuhalt);
+	else	
+		sbdisable(ctl->armctl, 0, 0);
+	sbreset(ctl->d11ctl, 8|4, 4);
+	ramscan(ctl);
+	if(SBDEBUG) print("ARM %#p D11 %#p SOCRAM %#p,%#p %lud bytes @ %#p\n",
+		(void*)ctl->armctl, (void*)ctl->d11ctl, (void*)ctl->socramctl, (void*)ctl->socramregs, ctl->socramsize, (void*)ctl->rambase);
+	cfgw(Clkcsr, 0);
+	microdelay(10);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	cfgw(Clkcsr, Nohwreq | ReqALP);
+	while((cfgr(Clkcsr) & (HTavail|ALPavail)) == 0)
+		microdelay(10);
+	cfgw(Clkcsr, Nohwreq | ForceALP);
+	microdelay(65);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	cfgw(Pullups, 0);
+	sbwindow(ctl->chipcommon);
+	cfgwritel(Fn1, ctl->chipcommon + Gpiopullup, 0);
+	cfgwritel(Fn1, ctl->chipcommon + Gpiopulldown, 0);
+	if(ctl->chipid != 0x4330 && ctl->chipid != 43362)
+		return;
+	cfgwritel(Fn1, ctl->chipcommon + Chipctladdr, 1);
+	if(cfgreadl(Fn1, ctl->chipcommon + Chipctladdr) != 1)
+		print("ether4330: can't set Chipctladdr\n");
+	else{
+		r = cfgreadl(Fn1, ctl->chipcommon + Chipctldata);
+		if(SBDEBUG) print("chipcommon PMU (%lux) %lux", cfgreadl(Fn1, ctl->chipcommon + Chipctladdr), r);
+		/* set SDIO drive strength >= 6mA */
+		r &= ~0x3800;
+		if(ctl->chipid == 0x4330)
+			r |= 3<<11;
+		else
+			r |= 7<<11;
+		cfgwritel(Fn1, ctl->chipcommon + Chipctldata, r);
+		if(SBDEBUG) print("-> %lux (= %lux)\n", r, cfgreadl(Fn1, ctl->chipcommon + Chipctldata));
+	}
+}
+
+static void
+sbenable(Ctlr *ctl)
+{
+	int i;
+
+	if(SBDEBUG) print("enabling HT clock...");
+	cfgw(Clkcsr, 0);
+	delay(1);
+	cfgw(Clkcsr, ReqHT);
+	for(i = 0; (cfgr(Clkcsr) & HTavail) == 0; i++){
+		if(i == 50){
+			print("ether4330: can't enable HT clock: csr %x\n", cfgr(Clkcsr));
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	cfgw(Clkcsr, cfgr(Clkcsr) | ForceHT);
+	delay(10);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	sbwindow(ctl->sdregs);
+	cfgwritel(Fn1, ctl->sdregs + Sbmboxdata, 4 << 16);	/* protocol version */
+	cfgwritel(Fn1, ctl->sdregs + Intmask, FrameInt | MailboxInt | Fcchange);
+	sdioset(Fn0, Ioenable, 1<<Fn2);
+	for(i = 0; !(sdiord(Fn0, Ioready) & 1<<Fn2); i++){
+		if(i == 10){
+			print("ether4330: can't enable SDIO function 2 - ioready %x\n", sdiord(Fn0, Ioready));
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	sdiowr(Fn0, Intenable, (1<<Fn1) | (1<<Fn2) | 1);
+}
+
+
+/*
+ * Firmware and config file uploading
+ */
+
+/*
+ * Condense config file contents (in buffer buf with length n)
+ * to 'var=value\0' list for firmware:
+ *	- remove comments (starting with '#') and blank lines
+ *	- remove carriage returns
+ *	- convert newlines to nulls
+ *	- mark end with two nulls
+ *	- pad with nulls to multiple of 4 bytes total length
+ */
+static int
+condense(uchar *buf, int n)
+{
+	uchar *p, *ep, *lp, *op;
+	int c, skipping;
+
+	skipping = 0;	/* true if in a comment */
+	ep = buf + n;	/* end of input */
+	op = buf;	/* end of output */
+	lp = buf;	/* start of current output line */
+	for(p = buf; p < ep; p++){
+		switch(c = *p){
+		case '#':
+			skipping = 1;
+			break;
+		case '\0':
+		case '\n':
+			skipping = 0;
+			if(op != lp){
+				*op++ = '\0';
+				lp = op;
+			}
+			break;
+		case '\r':
+			break;
+		default:
+			if(!skipping)
+				*op++ = c;
+			break;
+		}
+	}
+	if(!skipping && op != lp)
+		*op++ = '\0';
+	*op++ = '\0';
+	for(n = op - buf; n & 03; n++)
+		*op++ = '\0';
+	return n;
+}
+
+/*
+ * Try to find firmware file in /boot or in /sys/lib/firmware.
+ * Throw an error if not found.
+ */
+static Chan*
+findfirmware(char *file)
+{
+	char nbuf[64];
+	Chan *c;
+
+	if(!waserror()){
+		snprint(nbuf, sizeof nbuf, "/boot/%s", file);
+		c = namec(nbuf, Aopen, OREAD, 0);
+		poperror();
+	}else if(!waserror()){
+		snprint(nbuf, sizeof nbuf, "/sys/lib/firmware/%s", file);
+		c = namec(nbuf, Aopen, OREAD, 0);
+		poperror();
+	}else{
+		c = nil;
+		snprint(up->genbuf, sizeof up->genbuf, "can't find %s in /boot or /sys/lib/firmware", file);
+		error(up->genbuf);
+	}
+	return c;	
+}
+
+static int
+upload(Ctlr *ctl, char *file, int isconfig)
+{
+	Chan *c;
+	uchar *buf;
+	uchar *cbuf;
+	int off, n;
+
+	buf = cbuf = nil;
+	c = findfirmware(file);
+	if(waserror()){
+		cclose(c);
+		sdfree(buf);
+		sdfree(cbuf);
+		nexterror();
+	}
+	buf = sdmalloc(Uploadsz);
+	if(buf == nil)
+		error(Enomem);
+	if(Firmwarecmp){
+		cbuf = sdmalloc(Uploadsz);
+		if(cbuf == nil)
+			error(Enomem);
+	}
+	off = 0;
+	for(;;){
+		n = devtab[c->type]->read(c, buf, Uploadsz, off);
+		if(n <= 0)
+			break;
+		if(isconfig){
+			n = condense(buf, n);
+			off = ctl->socramsize - n - 4;
+		}else if(off == 0)
+			memmove(ctl->resetvec.c, buf, sizeof(ctl->resetvec.c));
+		while(n&3)
+			buf[n++] = 0;
+		sbmem(1, buf, n, ctl->rambase + off);
+		if(isconfig)
+			break;
+		off += n;
+	}
+	if(Firmwarecmp){
+		if(FWDEBUG) print("compare...");
+		if(!isconfig)
+			off = 0;
+		for(;;){
+			if(!isconfig){
+				n = devtab[c->type]->read(c, buf, Uploadsz, off);
+				if(n <= 0)
+					break;
+			while(n&3)
+				buf[n++] = 0;
+			}
+			sbmem(0, cbuf, n, ctl->rambase + off);
+			if(memcmp(buf, cbuf, n) != 0){
+				print("ether4330: firmware load failed offset %d\n", off);
+				error(Eio);
+			}
+			if(isconfig)
+				break;
+			off += n;
+		}
+	}
+	if(FWDEBUG) print("\n");
+	poperror();
+	cclose(c);
+	sdfree(buf);
+	sdfree(cbuf);
+	return n;
+}
+
+/*
+ * Upload regulatory file (.clm) to firmware.
+ * Packet format is
+ *	[2]flag [2]type [4]len [4]crc [len]data
+ */
+static void
+reguload(Ctlr *ctl, char *file)
+{
+	Chan *c;
+	uchar *buf;
+	int off, n, flag;
+	enum {
+		Reguhdr = 2+2+4+4,
+		Regusz	= 1400,
+		Regutyp	= 2,
+		Flagclm	= 1<<12,
+		Firstpkt= 1<<1,
+		Lastpkt	= 1<<2,
+	};
+
+	buf = nil;
+	c = findfirmware(file);
+	if(waserror()){
+		cclose(c);
+		free(buf);
+		nexterror();
+	}
+	buf = malloc(Reguhdr+Regusz+1);
+	if(buf == nil)
+		error(Enomem);
+	put2(buf+2, Regutyp);
+	put2(buf+8, 0);
+	off = 0;
+	flag = Flagclm | Firstpkt;
+	while((flag&Lastpkt) == 0){
+		n = devtab[c->type]->read(c, buf+Reguhdr, Regusz+1, off);
+		if(n <= 0)
+			break;
+		if(n == Regusz+1)
+			--n;
+		else{
+			while(n&7)
+				buf[Reguhdr+n++] = 0;
+			flag |= Lastpkt;
+		}
+		put2(buf+0, flag);
+		put4(buf+4, n);
+		wlsetvar(ctl, "clmload", buf, Reguhdr + n);
+		off += n;
+		flag &= ~Firstpkt;
+	}
+	poperror();
+	cclose(c);
+	free(buf);
+}
+
+static void
+fwload(Ctlr *ctl)
+{
+	uchar buf[4];
+	uint i, n;
+
+	i = 0;
+	while(firmware[i].chipid != ctl->chipid ||
+		   firmware[i].chiprev != ctl->chiprev){
+		if(++i == nelem(firmware)){
+			print("ether4330: no firmware for chipid %x (%d) chiprev %d\n",
+				ctl->chipid, ctl->chipid, ctl->chiprev);
+			error("no firmware");
+		}
+	}
+	ctl->regufile = firmware[i].regufile;
+	cfgw(Clkcsr, ReqALP);
+	while((cfgr(Clkcsr) & ALPavail) == 0)
+		microdelay(10);
+	memset(buf, 0, 4);
+	sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4);
+	if(FWDEBUG) print("firmware load...");
+	upload(ctl, firmware[i].fwfile, 0);
+	if(FWDEBUG) print("config load...");
+	n = upload(ctl, firmware[i].cfgfile, 1);
+	n /= 4;
+	n = (n & 0xFFFF) | (~n << 16);
+	put4(buf, n);
+	sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4);
+	if(ctl->armcore == ARMcr4){
+		sbwindow(ctl->sdregs);
+		cfgwritel(Fn1, ctl->sdregs + Intstatus, ~0);
+		if(ctl->resetvec.i != 0){
+			if(SBDEBUG) print("%ux\n", ctl->resetvec.i);
+			sbmem(1, ctl->resetvec.c, sizeof(ctl->resetvec.c), 0);
+		}
+		sbreset(ctl->armctl, Cr4Cpuhalt, 0);
+	}else
+		sbreset(ctl->armctl, 0, 0);
+}
+
+/*
+ * Communication of data and control packets
+ */
+
+void
+intwait(Ctlr *ctlr, int wait)
+{
+	ulong ints, mbox;
+	int i;
+
+	if(waserror())
+		return;
+	for(;;){
+		sdiocardintr(wait);
+		sbwindow(ctlr->sdregs);
+		i = sdiord(Fn0, Intpend);
+		if(i == 0){
+			tsleep(&up->sleep, return0, 0, 10);
+			continue;
+		}
+		ints = cfgreadl(Fn1, ctlr->sdregs + Intstatus);
+		cfgwritel(Fn1, ctlr->sdregs + Intstatus, ints);
+		if(0) print("INTS: (%x) %lux -> %lux\n", i, ints, cfgreadl(Fn1, ctlr->sdregs + Intstatus));
+		if(ints & MailboxInt){
+			mbox = cfgreadl(Fn1, ctlr->sdregs + Hostmboxdata);
+			cfgwritel(Fn1, ctlr->sdregs + Sbmbox, 2);	/* ack */
+			if(mbox & 0x8)
+				print("ether4330: firmware ready\n");
+		}
+		if(ints & FrameInt)
+			break;
+	}
+	poperror();
+}
+
+static Block*
+wlreadpkt(Ctlr *ctl)
+{
+	Block *b;
+	Sdpcm *p;
+	int len, lenck;
+
+	b = allocb(2048);
+	p = (Sdpcm*)b->wp;
+	qlock(&ctl->pktlock);
+	for(;;){
+		packetrw(0, b->wp, sizeof(*p));
+		len = p->len[0] | p->len[1]<<8;
+		if(len == 0){
+			freeb(b);
+			b = nil;
+			break;
+		}
+		lenck = p->lenck[0] | p->lenck[1]<<8;
+		if(lenck != (len ^ 0xFFFF) ||
+		   len < sizeof(*p) || len > 2048){
+			print("ether4330: wlreadpkt error len %.4x lenck %.4x\n", len, lenck);
+			cfgw(Framectl, Rfhalt);
+			while(cfgr(Rfrmcnt+1))
+				;
+			while(cfgr(Rfrmcnt))
+				;
+			continue;
+		}
+		if(len > sizeof(*p))
+			packetrw(0, b->wp + sizeof(*p), len - sizeof(*p));
+		b->wp += len;
+		break;
+	}
+	qunlock(&ctl->pktlock);
+	return b;
+}
+
+static void
+txstart(Ether *edev)
+{
+	Ctlr *ctl;
+	Sdpcm *p;
+	Block *b;
+	int len, off;
+
+	ctl = edev->ctlr;
+	if(!canqlock(&ctl->tlock))
+		return;
+	if(waserror()){
+		qunlock(&ctl->tlock);
+		return;
+	}
+	for(;;){
+		lock(&ctl->txwinlock);
+		if(ctl->txseq == ctl->txwindow){
+			//print("f");
+			unlock(&ctl->txwinlock);
+			break;
+		}
+		if(ctl->fcmask & 1<<2){
+			//print("x");
+			unlock(&ctl->txwinlock);
+			break;
+		}
+		unlock(&ctl->txwinlock);
+		b = qget(edev->oq);
+		if(b == nil)
+			break;
+		off = ((uintptr)b->rp & 3) + sizeof(Sdpcm);
+		b = padblock(b, off + 4);
+		len = BLEN(b);
+		p = (Sdpcm*)b->rp;
+		memset(p, 0, off);	/* TODO: refactor dup code */
+		put2(p->len, len);
+		put2(p->lenck, ~len);
+		p->chanflg = 2;
+		p->seq = ctl->txseq;
+		p->doffset = off;
+		put4(b->rp + off, 0x20);	/* BDC header */
+		if(iodebug) dump("send", b->rp, len);
+		qlock(&ctl->pktlock);
+		if(waserror()){
+			if(iodebug) print("halt frame %x %x\n", cfgr(Wfrmcnt+1), cfgr(Wfrmcnt+1));
+			cfgw(Framectl, Wfhalt);
+			while(cfgr(Wfrmcnt+1))
+				;
+			while(cfgr(Wfrmcnt))
+				;
+			qunlock(&ctl->pktlock);
+			nexterror();
+		}
+		packetrw(1, b->rp, len);
+		ctl->txseq++;
+		poperror();
+		qunlock(&ctl->pktlock);
+		freeb(b);
+	}
+	poperror();
+	qunlock(&ctl->tlock);
+}
+
+static void
+rproc(void *a)
+{
+	Ether *edev;
+	Ctlr *ctl;
+	Block *b;
+	Sdpcm *p;
+	Cmd *q;
+	int flowstart;
+	int bdc;
+
+	edev = a;
+	ctl = edev->ctlr;
+	flowstart = 0;
+	for(;;){
+		if(flowstart){
+			//print("F");
+			flowstart = 0;
+			txstart(edev);
+		}
+		b = wlreadpkt(ctl);
+		if(b == nil){
+			intwait(ctl, 1);
+			continue;
+		}
+		p = (Sdpcm*)b->rp;
+		if(p->window != ctl->txwindow || p->fcmask != ctl->fcmask){
+			lock(&ctl->txwinlock);
+			if(p->window != ctl->txwindow){
+				if(ctl->txseq == ctl->txwindow)
+					flowstart = 1;
+				ctl->txwindow = p->window;
+			}
+			if(p->fcmask != ctl->fcmask){
+				if((p->fcmask & 1<<2) == 0)
+					flowstart = 1;
+				ctl->fcmask = p->fcmask;
+			}
+			unlock(&ctl->txwinlock);
+		}
+		switch(p->chanflg & 0xF){
+		case 0:
+			if(iodebug) dump("rsp", b->rp, BLEN(b));
+			if(BLEN(b) < sizeof(Sdpcm) + sizeof(Cmd))
+				break;
+			q = (Cmd*)(b->rp + sizeof(*p));
+			if((q->id[0] | q->id[1]<<8) != ctl->reqid)
+				break;
+			ctl->rsp = b;
+			wakeup(&ctl->cmdr);
+			continue;
+		case 1:
+			if(iodebug) dump("event", b->rp, BLEN(b));
+			if(BLEN(b) > p->doffset + 4){
+				bdc = 4 + (b->rp[p->doffset + 3] << 2);
+				if(BLEN(b) > p->doffset + bdc){
+					b->rp += p->doffset + bdc;	/* skip BDC header */
+					bcmevent(ctl, b->rp, BLEN(b));
+					break;
+				}
+			}
+			if(iodebug && BLEN(b) != p->doffset)
+				print("short event %ld %d\n", BLEN(b), p->doffset);
+			break;
+		case 2:
+			if(iodebug) dump("packet", b->rp, BLEN(b));
+			if(BLEN(b) > p->doffset + 4){
+				bdc = 4 + (b->rp[p->doffset + 3] << 2);
+				if(BLEN(b) >= p->doffset + bdc + ETHERHDRSIZE){
+					b->rp += p->doffset + bdc;	/* skip BDC header */
+					etheriq(edev, b);
+					continue;
+				}
+			}
+			break;
+		default:
+			dump("ether4330: bad packet", b->rp, BLEN(b));
+			break;
+		}
+		freeb(b);
+	}
+}
+
+static void
+linkdown(Ctlr *ctl)
+{
+	Ether *edev;
+	Netfile *f;
+	int i;
+
+	edev = ctl->edev;
+	if(edev == nil || ctl->status != Connected)
+		return;
+	ctl->status = Disconnected;
+	/* send eof to aux/wpa */
+	for(i = 0; i < edev->nfile; i++){
+		f = edev->f[i];
+		if(f == nil || f->in == nil || f->inuse == 0 || f->type != 0x888e)
+			continue;
+		qwrite(f->in, 0, 0);
+	}
+}
+
+/*
+ * Command interface between host and firmware
+ */
+
+static char *eventnames[] = {
+	[0] = "set ssid",
+	[1] = "join",
+	[2] = "start",
+	[3] = "auth",
+	[4] = "auth ind",
+	[5] = "deauth",
+	[6] = "deauth ind",
+	[7] = "assoc",
+	[8] = "assoc ind",
+	[9] = "reassoc",
+	[10] = "reassoc ind",
+	[11] = "disassoc",
+	[12] = "disassoc ind",
+	[13] = "quiet start",
+	[14] = "quiet end",
+	[15] = "beacon rx",
+	[16] = "link",
+	[17] = "mic error",
+	[18] = "ndis link",
+	[19] = "roam",
+	[20] = "txfail",
+	[21] = "pmkid cache",
+	[22] = "retrograde tsf",
+	[23] = "prune",
+	[24] = "autoauth",
+	[25] = "eapol msg",
+	[26] = "scan complete",
+	[27] = "addts ind",
+	[28] = "delts ind",
+	[29] = "bcnsent ind",
+	[30] = "bcnrx msg",
+	[31] = "bcnlost msg",
+	[32] = "roam prep",
+	[33] = "pfn net found",
+	[34] = "pfn net lost",
+	[35] = "reset complete",
+	[36] = "join start",
+	[37] = "roam start",
+	[38] = "assoc start",
+	[39] = "ibss assoc",
+	[40] = "radio",
+	[41] = "psm watchdog",
+	[44] = "probreq msg",
+	[45] = "scan confirm ind",
+	[46] = "psk sup",
+	[47] = "country code changed",
+	[48] = "exceeded medium time",
+	[49] = "icv error",
+	[50] = "unicast decode error",
+	[51] = "multicast decode error",
+	[52] = "trace",
+	[53] = "bta hci event",
+	[54] = "if",
+	[55] = "p2p disc listen complete",
+	[56] = "rssi",
+	[57] = "pfn scan complete",
+	[58] = "extlog msg",
+	[59] = "action frame",
+	[60] = "action frame complete",
+	[61] = "pre assoc ind",
+	[62] = "pre reassoc ind",
+	[63] = "channel adopted",
+	[64] = "ap started",
+	[65] = "dfs ap stop",
+	[66] = "dfs ap resume",
+	[67] = "wai sta event",
+	[68] = "wai msg",
+	[69] = "escan result",
+	[70] = "action frame off chan complete",
+	[71] = "probresp msg",
+	[72] = "p2p probreq msg",
+	[73] = "dcs request",
+	[74] = "fifo credit map",
+	[75] = "action frame rx",
+	[76] = "wake event",
+	[77] = "rm complete",
+	[78] = "htsfsync",
+	[79] = "overlay req",
+	[80] = "csa complete ind",
+	[81] = "excess pm wake event",
+	[82] = "pfn scan none",
+	[83] = "pfn scan allgone",
+	[84] = "gtk plumbed",
+	[85] = "assoc ind ndis",
+	[86] = "reassoc ind ndis",
+	[87] = "assoc req ie",
+	[88] = "assoc resp ie",
+	[89] = "assoc recreated",
+	[90] = "action frame rx ndis",
+	[91] = "auth req",
+	[92] = "tdls peer event",
+	[127] = "bcmc credit support"
+};
+
+static char*
+evstring(uint event)
+{
+	static char buf[12];
+
+	if(event >= nelem(eventnames) || eventnames[event] == 0){
+		/* not reentrant but only called from one kproc */
+		snprint(buf, sizeof buf, "%d", event);
+		return buf;
+	}
+	return eventnames[event];
+}
+
+static void
+bcmevent(Ctlr *ctl, uchar *p, int len)
+{
+	int flags;
+	long event, status, reason;
+
+	if(len < ETHERHDRSIZE + 10 + 46)
+		return;
+	p += ETHERHDRSIZE + 10;			/* skip bcm_ether header */
+	len -= ETHERHDRSIZE + 10;
+	flags = nhgets(p + 2);
+	event = nhgets(p + 6);
+	status = nhgetl(p + 8);
+	reason = nhgetl(p + 12);
+	if(EVENTDEBUG)
+		print("ether4330: [%s] status %ld flags %#x reason %ld\n", 
+			evstring(event), status, flags, reason);
+	switch(event){
+	case 19:	/* E_ROAM */
+		if(status == 0)
+			break;
+	/* fall through */
+	case 0:		/* E_SET_SSID */
+		ctl->joinstatus = 1 + status;
+		wakeup(&ctl->joinr);
+		break;
+	case 16:	/* E_LINK */
+		if(flags&1)	/* link up */
+			break;
+	/* fall through */
+	case 5:		/* E_DEAUTH */
+	case 6:		/* E_DEAUTH_IND */
+	case 12:	/* E_DISASSOC_IND */
+		linkdown(ctl);
+		break;
+	case 26:	/* E_SCAN_COMPLETE */
+		break;
+	case 69:	/* E_ESCAN_RESULT */
+		wlscanresult(ctl->edev, p + 48, len - 48);
+		break;
+	default:
+		if(status){
+			if(!EVENTDEBUG)
+				print("ether4330: [%s] error status %ld flags %#x reason %ld\n",
+					evstring(event), status, flags, reason);
+			dump("event", p, len);
+		}
+	}
+}
+
+static int
+joindone(void *a)
+{
+	return ((Ctlr*)a)->joinstatus;
+}
+
+static int
+waitjoin(Ctlr *ctl)
+{
+	int n;
+
+	sleep(&ctl->joinr, joindone, ctl);
+	n = ctl->joinstatus;
+	ctl->joinstatus = 0;
+	return n - 1;
+}
+
+static int
+cmddone(void *a)
+{
+	return ((Ctlr*)a)->rsp != nil;
+}
+
+static void
+wlcmd(Ctlr *ctl, int write, int op, void *data, int dlen, void *res, int rlen)
+{
+	Block *b;
+	Sdpcm *p;
+	Cmd *q;
+	int len, tlen;
+
+	if(write)
+		tlen = dlen + rlen;
+	else
+		tlen = MAX(dlen, rlen);
+	len = sizeof(Sdpcm) + sizeof(Cmd) + tlen;
+	b = allocb(len);
+	qlock(&ctl->cmdlock);
+	if(waserror()){
+		freeb(b);
+		qunlock(&ctl->cmdlock);
+		nexterror();
+	}
+	memset(b->wp, 0, len);
+	qlock(&ctl->pktlock);
+	p = (Sdpcm*)b->wp;
+	put2(p->len, len);
+	put2(p->lenck, ~len);
+	p->seq = ctl->txseq;
+	p->doffset = sizeof(Sdpcm);
+	b->wp += sizeof(*p);
+	
+	q = (Cmd*)b->wp;
+	put4(q->cmd, op);
+	put4(q->len, tlen);
+	put2(q->flags, write? 2 : 0);
+	put2(q->id, ++ctl->reqid);
+	put4(q->status, 0);
+	b->wp += sizeof(*q);
+
+	if(dlen > 0)
+		memmove(b->wp, data, dlen);
+	if(write)
+		memmove(b->wp + dlen, res, rlen);
+	b->wp += tlen;
+
+	if(iodebug) dump("cmd", b->rp, len);
+	packetrw(1, b->rp, len);
+	ctl->txseq++;
+	qunlock(&ctl->pktlock);
+	freeb(b);
+	b = nil;
+	USED(b);
+	sleep(&ctl->cmdr, cmddone, ctl);
+	b = ctl->rsp;
+	ctl->rsp = nil;
+	assert(b != nil);
+	p = (Sdpcm*)b->rp;
+	q = (Cmd*)(b->rp + p->doffset);
+	if(q->status[0] | q->status[1] | q->status[2] | q->status[3]){
+		print("ether4330: cmd %d error status %ld\n", op, get4(q->status));
+		dump("ether4330: cmd error", b->rp, BLEN(b));
+		error("wlcmd error");
+	}
+	if(!write)
+		memmove(res, q + 1, rlen);
+	freeb(b);
+	qunlock(&ctl->cmdlock);
+	poperror();
+}
+
+static void
+wlcmdint(Ctlr *ctl, int op, int val)
+{
+	uchar buf[4];
+
+	put4(buf, val);
+	wlcmd(ctl, 1, op, buf, 4, nil, 0);
+}
+
+static void
+wlgetvar(Ctlr *ctl, char *name, void *val, int len)
+{
+	wlcmd(ctl, 0, GetVar, name, strlen(name) + 1, val, len);
+}
+
+static void
+wlsetvar(Ctlr *ctl, char *name, void *val, int len)
+{
+	if(VARDEBUG){
+		char buf[32];
+		snprint(buf, sizeof buf, "wlsetvar %s:", name);
+		dump(buf, val, len);
+	}
+	wlcmd(ctl, 1, SetVar, name, strlen(name) + 1, val, len);
+}
+
+static void
+wlsetint(Ctlr *ctl, char *name, int val)
+{
+	uchar buf[4];
+
+	put4(buf, val);
+	wlsetvar(ctl, name, buf, 4);
+}
+
+static void
+wlwepkey(Ctlr *ctl, int i)
+{
+	uchar params[164];
+	uchar *p;
+
+	memset(params, 0, sizeof params);
+	p = params;
+	p = put4(p, i);		/* index */
+	p = put4(p, ctl->keys[i].len);
+	memmove(p, ctl->keys[i].dat, ctl->keys[i].len);
+	p += 32 + 18*4;		/* keydata, pad */
+	if(ctl->keys[i].len == WMinKeyLen)
+		p = put4(p, 1);		/* algo = WEP1 */
+	else
+		p = put4(p, 3);		/* algo = WEP128 */
+	put4(p, 2);		/* flags = Primarykey */
+
+	wlsetvar(ctl, "wsec_key", params, sizeof params);
+}
+
+static void
+memreverse(char *dst, char *src, int len)
+{
+	src += len;
+	while(len-- > 0)
+		*dst++ = *--src;
+}
+
+static void
+wlwpakey(Ctlr *ctl, int id, uvlong iv, uchar *ea)
+{
+	uchar params[164];
+	uchar *p;
+	int pairwise;
+
+	if(id == CMrxkey)
+		return;
+	pairwise = (id == CMrxkey || id == CMtxkey);
+	memset(params, 0, sizeof params);
+	p = params;
+	if(pairwise)
+		p = put4(p, 0);
+	else
+		p = put4(p, id - CMrxkey0);	/* group key id */
+	p = put4(p, ctl->keys[0].len);
+	memmove((char*)p,  ctl->keys[0].dat, ctl->keys[0].len);
+	p += 32 + 18*4;		/* keydata, pad */
+	if(ctl->cryptotype == Wpa)
+		p = put4(p, 2);	/* algo = TKIP */
+	else
+		p = put4(p, 4);	/* algo = AES_CCM */
+	if(pairwise)
+		p = put4(p, 0);
+	else
+		p = put4(p, 2);		/* flags = Primarykey */
+	p += 3*4;
+	p = put4(p, 0); //pairwise);		/* iv initialised */
+	p += 4;
+	p = put4(p, iv>>16);	/* iv high */
+	p = put2(p, iv&0xFFFF);	/* iv low */
+	p += 2 + 2*4;		/* align, pad */
+	if(pairwise)
+		memmove(p, ea, Eaddrlen);
+
+	wlsetvar(ctl, "wsec_key", params, sizeof params);
+}
+
+static void
+wljoin(Ctlr *ctl, char *ssid, int chan)
+{
+	uchar params[72];
+	uchar *p;
+	int n;
+
+	if(chan != 0)
+		chan |= 0x2b00;		/* 20Mhz channel width */
+	p = params;
+	n = strlen(ssid);
+	n = MIN(n, 32);
+	p = put4(p, n);
+	memmove(p, ssid, n);
+	memset(p + n, 0, 32 - n);
+	p += 32;
+	p = put4(p, 0xff);	/* scan type */
+	if(chan != 0){
+		p = put4(p, 2);		/* num probes */
+		p = put4(p, 120);	/* active time */
+		p = put4(p, 390);	/* passive time */
+	}else{
+		p = put4(p, -1);	/* num probes */
+		p = put4(p, -1);	/* active time */
+		p = put4(p, -1);	/* passive time */
+	}
+	p = put4(p, -1);	/* home time */
+	memset(p, 0xFF, Eaddrlen);	/* bssid */
+	p += Eaddrlen;
+	p = put2(p, 0);		/* pad */
+	if(chan != 0){
+		p = put4(p, 1);		/* num chans */
+		p = put2(p, chan);	/* chan spec */
+		p = put2(p, 0);		/* pad */
+		assert(p == params + sizeof(params));
+	}else{
+		p = put4(p, 0);		/* num chans */
+		assert(p == params + sizeof(params) - 4);
+	}
+
+	wlsetvar(ctl, "join", params, chan? sizeof params : sizeof params - 4);
+	ctl->status = Connecting;
+	switch(waitjoin(ctl)){
+		case 0:
+			ctl->status = Connected;
+			break;
+		case 3:
+			ctl->status = Disconnected;
+			error("wifi join: network not found");
+		case 1:
+			ctl->status = Disconnected;
+			error("wifi join: failed");
+		default:
+			ctl->status = Disconnected;
+			error("wifi join: error");
+	}
+}
+
+static void
+wlscanstart(Ctlr *ctl)
+{
+	/* version[4] action[2] sync_id[2] ssidlen[4] ssid[32] bssid[6] bss_type[1]
+		scan_type[1] nprobes[4] active_time[4] passive_time[4] home_time[4]
+		nchans[2] nssids[2] chans[nchans][2] ssids[nssids][32] */
+	/* hack - this is only correct on a little-endian cpu */
+	static uchar params[4+2+2+4+32+6+1+1+4*4+2+2+14*2+32+4] = {
+		1,0,0,0,
+		1,0,
+		0x34,0x12,
+		0,0,0,0,
+		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+		0xff,0xff,0xff,0xff,0xff,0xff,
+		2,
+		0,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		14,0,
+		1,0,
+		0x01,0x2b,0x02,0x2b,0x03,0x2b,0x04,0x2b,0x05,0x2e,0x06,0x2e,0x07,0x2e,
+		0x08,0x2b,0x09,0x2b,0x0a,0x2b,0x0b,0x2b,0x0c,0x2b,0x0d,0x2b,0x0e,0x2b,
+		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	};
+
+	wlcmdint(ctl, 49, 0);	/* PASSIVE_SCAN */
+	wlsetvar(ctl, "escan", params, sizeof params);
+}
+
+static uchar*
+gettlv(uchar *p, uchar *ep, int tag)
+{
+	int len;
+
+	while(p + 1 < ep){
+		len = p[1];
+		if(p + 2 + len > ep)
+			return nil;
+		if(p[0] == tag)
+			return p;
+		p += 2 + len;
+	}
+	return nil;
+}
+
+static void
+addscan(Block *bp, uchar *p, int len)
+{
+	char bssid[24];
+	char *auth, *auth2;
+	uchar *t, *et;
+	int ielen;
+	static uchar wpaie1[4] = { 0x00, 0x50, 0xf2, 0x01 };
+
+	snprint(bssid, sizeof bssid, ";bssid=%E", p + 8);
+	if(strstr((char*)bp->rp, bssid) != nil)
+		return;
+	bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim,
+		"ssid=%.*s%s;signal=%d;noise=%d;chan=%d",
+		p[18], (char*)p+19, bssid,
+		(short)get2(p+78), (signed char)p[80],
+		get2(p+72) & 0xF);
+	auth = auth2 = "";
+	if(get2(p + 16) & 0x10)
+		auth = ";wep";
+	ielen = get4(p + 0x78);
+	if(ielen > 0){
+		t = p + get4(p + 0x74);
+		et = t + ielen;
+		if(et > p + len)
+			return;
+		if(gettlv(t, et, 0x30) != nil){
+			auth = "";
+			auth2 = ";wpa2";
+		}
+		while((t = gettlv(t, et, 0xdd)) != nil){
+			if(t[1] > 4 && memcmp(t+2, wpaie1, 4) == 0){
+				auth = ";wpa";
+				break;
+			}
+			t += 2 + t[1];
+		}
+	}
+	bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim,
+		"%s%s\n", auth, auth2);
+}
+
+
+static void
+wlscanresult(Ether *edev, uchar *p, int len)
+{
+	Ctlr *ctlr;
+	Netfile **ep, *f, **fp;
+	Block *bp;
+	int nbss, i;
+
+	ctlr = edev->ctlr;
+	if(get4(p) > len)
+		return;
+	/* TODO: more syntax checking */
+	bp = ctlr->scanb;
+	if(bp == nil)
+		ctlr->scanb = bp = allocb(8192);
+	nbss = get2(p+10);
+	p += 12;
+	len -= 12;
+	if(0) dump("SCAN", p, len);
+	if(nbss){
+		addscan(bp, p, len);
+		return;
+	}
+	i = edev->scan;
+	ep = &edev->f[Ntypes];
+	for(fp = edev->f; fp < ep && i > 0; fp++){
+		f = *fp;
+		if(f == nil || f->scan == 0)
+			continue;
+		if(i == 1)
+			qpass(f->in, bp);
+		else
+			qpass(f->in, copyblock(bp, BLEN(bp)));
+		i--;
+	}
+	if(i)
+		freeb(bp);
+	ctlr->scanb = nil;
+}
+
+static void
+lproc(void *a)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+	int secs;
+
+	edev = a;
+	ctlr = edev->ctlr;
+	secs = 0;
+	for(;;){
+		tsleep(&up->sleep, return0, 0, 1000);
+		if(ctlr->scansecs){
+			if(secs == 0){
+				if(waserror())
+					ctlr->scansecs = 0;
+				else{
+					wlscanstart(ctlr);
+					poperror();
+				}
+				secs = ctlr->scansecs;
+			}
+			--secs;
+		}else
+			secs = 0;
+	}
+}
+
+static void
+wlinit(Ether *edev, Ctlr *ctlr)
+{
+	uchar ea[Eaddrlen];
+	uchar eventmask[16];
+	char version[128];
+	char *p;
+	static uchar keepalive[12] = {1, 0, 11, 0, 0xd8, 0xd6, 0, 0, 0, 0, 0, 0};
+
+	wlgetvar(ctlr, "cur_etheraddr", ea, Eaddrlen);
+	memmove(edev->ea, ea, Eaddrlen);
+	memmove(edev->addr, ea, Eaddrlen);
+	print("ether4330: addr %E\n", edev->ea);
+	wlsetint(ctlr, "assoc_listen", 10);
+	if(ctlr->chipid == 43430 || ctlr->chipid == 0x4345)
+		wlcmdint(ctlr, 0x56, 0);	/* powersave off */
+	else
+		wlcmdint(ctlr, 0x56, 2);	/* powersave FAST */
+	wlsetint(ctlr, "bus:txglom", 0);
+	wlsetint(ctlr, "bcn_timeout", 10);
+	wlsetint(ctlr, "assoc_retry_max", 3);
+	if(ctlr->chipid == 0x4330){
+		wlsetint(ctlr, "btc_wire", 4);
+		wlsetint(ctlr, "btc_mode", 1);
+		wlsetvar(ctlr, "mkeep_alive", keepalive, 11);
+	}
+	memset(eventmask, 0xFF, sizeof eventmask);
+#define ENABLE(n)	eventmask[n/8] |= 1<<(n%8)
+#define DISABLE(n)	eventmask[n/8] &= ~(1<<(n%8))
+	DISABLE(40);	/* E_RADIO */
+	DISABLE(44);	/* E_PROBREQ_MSG */
+	DISABLE(54);	/* E_IF */
+	DISABLE(71);	/* E_PROBRESP_MSG */
+	DISABLE(20);	/* E_TXFAIL */
+	DISABLE(124);	/* ? */
+	wlsetvar(ctlr, "event_msgs", eventmask, sizeof eventmask);
+	wlcmdint(ctlr, 0xb9, 0x28);	/* SET_SCAN_CHANNEL_TIME */
+	wlcmdint(ctlr, 0xbb, 0x28);	/* SET_SCAN_UNASSOC_TIME */
+	wlcmdint(ctlr, 0x102, 0x82);	/* SET_SCAN_PASSIVE_TIME */
+	wlcmdint(ctlr, 2, 0);		/* UP */
+	memset(version, 0, sizeof version);
+	wlgetvar(ctlr, "ver", version, sizeof version - 1);
+	if((p = strchr(version, '\n')) != nil)
+		*p = '\0';
+	if(0) print("ether4330: %s\n", version);
+	wlsetint(ctlr, "roam_off", 1);
+	wlcmdint(ctlr, 0x14, 1);	/* SET_INFRA 1 */
+	wlcmdint(ctlr, 10, 0);		/* SET_PROMISC */
+	//wlcmdint(ctlr, 0x8e, 0);	/* SET_BAND 0 */
+	//wlsetint(ctlr, "wsec", 1);
+	wlcmdint(ctlr, 2, 1);		/* UP */
+	ctlr->keys[0].len = WMinKeyLen;
+	//wlwepkey(ctlr, 0);
+}
+
+/*
+ * Plan 9 driver interface
+ */
+
+static long
+etherbcmifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	Ctlr *ctlr;
+	char *p;
+	int l;
+	static char *cryptoname[4] = {
+		[0]	"off",
+		[Wep]	"wep",
+		[Wpa]	"wpa",
+		[Wpa2]	"wpa2",
+	};
+	/* these strings are known by aux/wpa */
+	static char* connectstate[] = {
+		[Disconnected]	= "unassociated",
+		[Connecting] = "connecting",
+		[Connected] = "associated",
+	};
+
+	ctlr = edev->ctlr;
+	if(ctlr == nil)
+		return 0;
+	p = malloc(READSTR);
+	l = 0;
+
+	l += snprint(p+l, READSTR-l, "channel: %d\n", ctlr->chanid);
+	l += snprint(p+l, READSTR-l, "essid: %s\n", ctlr->essid);
+	l += snprint(p+l, READSTR-l, "crypt: %s\n", cryptoname[ctlr->cryptotype]);
+	l += snprint(p+l, READSTR-l, "oq: %d\n", qlen(edev->oq));
+	l += snprint(p+l, READSTR-l, "txwin: %d\n", ctlr->txwindow);
+	l += snprint(p+l, READSTR-l, "txseq: %d\n", ctlr->txseq);
+	l += snprint(p+l, READSTR-l, "status: %s\n", connectstate[ctlr->status]);
+	USED(l);
+	n = readstr(offset, a, n, p);
+	free(p);
+	return n;
+}
+
+static void
+etherbcmtransmit(Ether *edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr == nil)
+		return;
+	txstart(edev);
+}
+
+static int
+parsehex(char *buf, int buflen, char *a)
+{
+	int i, k, n;
+
+	k = 0;
+	for(i = 0;k < buflen && *a; i++){
+		if(*a >= '0' && *a <= '9')
+			n = *a++ - '0';
+		else if(*a >= 'a' && *a <= 'f')
+			n = *a++ - 'a' + 10;
+		else if(*a >= 'A' && *a <= 'F')
+			n = *a++ - 'A' + 10;
+		else
+			break;
+
+		if(i & 1){
+			buf[k] |= n;
+			k++;
+		}
+		else
+			buf[k] = n<<4;
+	}
+	if(i & 1)
+		return -1;
+	return k;
+}
+
+static int
+wepparsekey(WKey* key, char* a) 
+{
+	int i, k, len, n;
+	char buf[WMaxKeyLen];
+
+	len = strlen(a);
+	if(len == WMinKeyLen || len == WMaxKeyLen){
+		memset(key->dat, 0, sizeof(key->dat));
+		memmove(key->dat, a, len);
+		key->len = len;
+
+		return 0;
+	}
+	else if(len == WMinKeyLen*2 || len == WMaxKeyLen*2){
+		k = 0;
+		for(i = 0; i < len; i++){
+			if(*a >= '0' && *a <= '9')
+				n = *a++ - '0';
+			else if(*a >= 'a' && *a <= 'f')
+				n = *a++ - 'a' + 10;
+			else if(*a >= 'A' && *a <= 'F')
+				n = *a++ - 'A' + 10;
+			else
+				return -1;
+	
+			if(i & 1){
+				buf[k] |= n;
+				k++;
+			}
+			else
+				buf[k] = n<<4;
+		}
+
+		memset(key->dat, 0, sizeof(key->dat));
+		memmove(key->dat, buf, k);
+		key->len = k;
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static int
+wpaparsekey(WKey *key, uvlong *ivp, char *a)
+{
+	int len;
+	char *e;
+
+	if(cistrncmp(a, "tkip:", 5) == 0 || cistrncmp(a, "ccmp:", 5) == 0)
+		a += 5;
+	else
+		return 1;
+	len = parsehex(key->dat, sizeof(key->dat), a);
+	if(len <= 0)
+		return 1;
+	key->len = len;
+	a += 2*len;
+	if(*a++ != '@')
+		return 1;
+	*ivp = strtoull(a, &e, 16);
+	if(e == a)
+		return -1;
+	return 0;
+}
+
+static void
+setauth(Ctlr *ctlr, Cmdbuf *cb, char *a)
+{
+	uchar wpaie[32];
+	int i;
+
+	i = parsehex((char*)wpaie, sizeof wpaie, a);
+	if(i < 2 || i != wpaie[1] + 2)
+		cmderror(cb, "bad wpa ie syntax");
+	if(wpaie[0] == 0xdd)
+		ctlr->cryptotype = Wpa;
+	else if(wpaie[0] == 0x30)
+		ctlr->cryptotype = Wpa2;
+	else
+		cmderror(cb, "bad wpa ie");
+	wlsetvar(ctlr, "wpaie", wpaie, i);
+	if(ctlr->cryptotype == Wpa){
+		wlsetint(ctlr, "wpa_auth", 4|2);	/* auth_psk | auth_unspecified */
+		wlsetint(ctlr, "auth", 0);
+		wlsetint(ctlr, "wsec", 2);		/* tkip */
+		wlsetint(ctlr, "wpa_auth", 4);		/* auth_psk */
+	}else{
+		wlsetint(ctlr, "wpa_auth", 0x80|0x40);	/* auth_psk | auth_unspecified */
+		wlsetint(ctlr, "auth", 0);
+		wlsetint(ctlr, "wsec", 4);		/* aes */
+		wlsetint(ctlr, "wpa_auth", 0x80);	/* auth_psk */
+	}
+}
+
+static int
+setcrypt(Ctlr *ctlr, Cmdbuf*, char *a)
+{
+	if(cistrcmp(a, "wep") == 0 || cistrcmp(a, "on") == 0)
+		ctlr->cryptotype = Wep;
+	else if(cistrcmp(a, "off") == 0 || cistrcmp(a, "none") == 0)
+		ctlr->cryptotype = 0;
+	else
+		return 0;
+	wlsetint(ctlr, "auth", ctlr->cryptotype);
+	return 1;
+}
+
+static long
+etherbcmctl(Ether* edev, void* buf, long n)
+{
+	Ctlr *ctlr;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+	uchar ea[Eaddrlen];
+	uvlong iv;
+	int i;
+
+	if((ctlr = edev->ctlr) == nil)
+		error(Enonexist);
+	USED(ctlr);
+
+	cb = parsecmd(buf, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, cmds, nelem(cmds));
+	switch(ct->index){
+	case CMauth:
+		setauth(ctlr, cb, cb->f[1]);
+		if(ctlr->essid[0])
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		break;
+	case CMchannel:
+		if((i = atoi(cb->f[1])) < 0 || i > 16)
+			cmderror(cb, "bad channel number");
+		//wlcmdint(ctlr, 30, i);	/* SET_CHANNEL */
+		ctlr->chanid = i;
+		break;
+	case CMcrypt:
+		if(setcrypt(ctlr, cb, cb->f[1])){
+			if(ctlr->essid[0])
+				wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		}else
+			cmderror(cb, "bad crypt type");
+		break;
+	case CMessid:
+		if(cistrcmp(cb->f[1], "default") == 0)
+			memset(ctlr->essid, 0, sizeof(ctlr->essid));
+		else{
+			strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid) - 1);
+			ctlr->essid[sizeof(ctlr->essid) - 1] = '\0';
+		}
+		if(!waserror()){
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+			poperror();
+		}
+		break;
+	case CMjoin:	/* join essid channel wep|on|off|wpakey */
+		if(strcmp(cb->f[1], "") != 0){	/* empty string for no change */
+			if(cistrcmp(cb->f[1], "default") != 0){
+				strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid)-1);
+				ctlr->essid[sizeof(ctlr->essid)-1] = 0;
+			}else
+				memset(ctlr->essid, 0, sizeof(ctlr->essid));
+		}else if(ctlr->essid[0] == 0)
+			cmderror(cb, "essid not set");
+		if((i = atoi(cb->f[2])) >= 0 && i <= 16)
+			ctlr->chanid = i;
+		else
+			cmderror(cb, "bad channel number");
+		if(!setcrypt(ctlr, cb, cb->f[3]))
+			setauth(ctlr, cb, cb->f[3]);
+		if(ctlr->essid[0])
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		break;
+	case CMkey1:
+	case CMkey2:
+	case CMkey3:
+	case CMkey4:
+		i = ct->index - CMkey1;
+		if(wepparsekey(&ctlr->keys[i], cb->f[1]))
+			cmderror(cb, "bad WEP key syntax");
+		wlsetint(ctlr, "wsec", 1);	/* wep enabled */
+		wlwepkey(ctlr, i);
+		break;
+	case CMrxkey:
+	case CMrxkey0:
+	case CMrxkey1:
+	case CMrxkey2:
+	case CMrxkey3:
+	case CMtxkey:
+		if(parseether(ea, cb->f[1]) < 0)
+			cmderror(cb, "bad ether addr");
+		if(wpaparsekey(&ctlr->keys[0], &iv, cb->f[2]))
+			cmderror(cb, "bad wpa key");
+		wlwpakey(ctlr, ct->index, iv, ea);
+		break;
+	case CMdebug:
+		iodebug = atoi(cb->f[1]);
+		break;
+	}
+	poperror();
+	free(cb);
+	return n;
+}
+
+static void
+etherbcmscan(void *a, uint secs)
+{
+	Ether* edev;
+	Ctlr* ctlr;
+
+	edev = a;
+	ctlr = edev->ctlr;
+	ctlr->scansecs = secs;
+}
+
+static void
+etherbcmattach(Ether* edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(waserror()){
+		print("ether4330: attach failed: %s\n", up->errstr);
+		qunlock(&ctlr->alock);
+		nexterror();
+	}
+	if(ctlr->edev == nil){
+		if(ctlr->chipid == 0){
+			sdioinit();
+			sbinit(ctlr);
+		}
+		fwload(ctlr);
+		sbenable(ctlr);
+		kproc("wifireader", rproc, edev);
+		kproc("wifitimer", lproc, edev);
+		if(ctlr->regufile)
+			reguload(ctlr, ctlr->regufile);
+		wlinit(edev, ctlr);
+		ctlr->edev = edev;
+	}
+	qunlock(&ctlr->alock);
+	poperror();
+}
+
+static void
+etherbcmshutdown(Ether*)
+{
+	sdioreset();
+}
+
+
+static int
+etherbcmpnp(Ether* edev)
+{
+	Ctlr *ctlr;
+
+	static int done = 0;
+	if(done)
+		return -1;
+	done = 1;
+
+	ctlr = malloc(sizeof(Ctlr));
+	ctlr->chanid = Wifichan;
+	edev->ctlr = ctlr;
+	edev->attach = etherbcmattach;
+	edev->transmit = etherbcmtransmit;
+	edev->ifstat = etherbcmifstat;
+	edev->ctl = etherbcmctl;
+	edev->scanbs = etherbcmscan;
+	edev->shutdown = etherbcmshutdown;
+	edev->arg = edev;
+
+	return 0;
+}
+
+void
+ether4330link(void)
+{
+	addethercard("4330", etherbcmpnp);
+}
diff -r 44eae9a7e98b sys/src/9/bcm/mkfile
--- a/sys/src/9/bcm/mkfile	Sun Oct 11 14:59:49 2020 +0200
+++ b/sys/src/9/bcm/mkfile	Tue Oct 13 05:09:49 2020 -0700
@@ -1,5 +1,5 @@
 CONF=pi2
-CONFLIST=pi pi2
+CONFLIST=pi pi2 pi2wifi
 CRAPLIST=pif picpuf
 EXTRACOPIES=
 
diff -r 44eae9a7e98b sys/src/9/bcm/pi2wifi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/pi2wifi	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,60 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	dup
+	arch
+	ssl
+	tls
+	cap
+	fs
+	ether	netif
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
+	draw	screen swcursor
+	mouse	mouse
+	uart	gpio
+	gpio	gpio
+	sd
+	usb
+
+link
+	loopbackmedium
+	ethermedium
+	archbcm2
+	usbdwc
+	sdhost
+	ether4330	emmc
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	ipmux
+
+misc
+	armv7
+	uartmini
+	sdmmc	emmc
+	dma
+	vcore
+	vfp3	coproc
+	irq
+
+port
+	int cpuserver = 0;
+
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
+	boot
+	/sys/lib/firmware/brcmfmac43430-sdio.bin
+	/sys/lib/firmware/brcmfmac43430-sdio.txt
diff -r 44eae9a7e98b sys/src/9/bcm/sdhost.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/sdhost.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,393 @@
+/*
+ * bcm2835 sdhost controller
+ *
+ * Copyright © 2016 Richard Miller <r.miller@acm.org>
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/sd.h"
+
+extern SDio *sdcardlink;
+
+#define SDHOSTREGS	(VIRTIO+0x202000)
+
+#define FIELD(v, o, w)	(((v) & ((1<<(w))-1))<<(o))
+#define FCLR(d, o, w)	((d) & ~(((1<<(w))-1)<<(o)))
+#define FINS(d, o, w, v) (FCLR((d), (o), (w))|FIELD((v), (o), (w)))
+
+enum {
+	Extfreq		= 250*Mhz,	/* guess external clock frequency if vcore doesn't say */
+	Initfreq	= 400000,	/* initialisation frequency for MMC */
+	SDfreq		= 25*Mhz,	/* standard SD frequency */
+	SDfreqhs	= 50*Mhz,	/* SD high speed frequency */
+	FifoDepth	= 4,		/* "Limit fifo usage due to silicon bug" (linux driver) */
+
+	GoIdle		= 0,		/* mmc/sdio go idle state */
+	MMCSelect	= 7,		/* mmc/sd card select command */
+	Setbuswidth	= 6,		/* mmc/sd set bus width command */
+	Switchfunc	= 6,		/* mmc/sd switch function command */
+	Stoptransmission = 12,	/* mmc/sd stop transmission command */
+	Appcmd = 55,			/* mmc/sd application command prefix */
+};
+
+enum {
+	/* Controller registers */
+	Cmd		= 0x00>>2,
+	Arg		= 0x04>>2,
+	Timeout		= 0x08>>2,
+	Clkdiv		= 0x0c>>2,
+
+	Resp0		= 0x10>>2,
+	Resp1		= 0x14>>2,
+	Resp2		= 0x18>>2,
+	Resp3		= 0x1c>>2,
+
+	Status		= 0x20>>2,
+	Poweron		= 0x30>>2,
+	Dbgmode		= 0x34>>2,
+	Hconfig		= 0x38>>2,
+	Blksize		= 0x3c>>2,
+	Data		= 0x40>>2,
+	Blkcount	= 0x50>>2,
+
+	/* Cmd */
+	Start		= 1<<15,
+	Failed		= 1<<14,
+	Respmask	= 7<<9,
+	Resp48busy	= 4<<9,
+	Respnone	= 2<<9,
+	Resp136		= 1<<9,
+	Resp48		= 0<<9,
+	Host2card	= 1<<7,
+	Card2host	= 1<<6,
+
+	/* Status */
+	Busyint		= 1<<10,
+	Blkint		= 1<<9,
+	Sdioint		= 1<<8,
+	Rewtimeout	= 1<<7,
+	Cmdtimeout	= 1<<6,
+	Crcerror	= 3<<4,
+	Fifoerror	= 1<<3,
+	Dataflag	= 1<<0,
+	Intstatus	= (Busyint|Blkint|Sdioint|Dataflag),
+	Errstatus	= (Rewtimeout|Cmdtimeout|Crcerror|Fifoerror),
+
+	/* Hconfig */
+	BusyintEn	= 1<<10,
+	BlkintEn	= 1<<8,
+	SdiointEn	= 1<<5,
+	DataintEn	= 1<<4,
+	Slowcard	= 1<<3,
+	Extbus4		= 1<<2,
+	Intbuswide	= 1<<1,
+	Cmdrelease	= 1<<0,
+};
+
+static int cmdinfo[64] = {
+[0]  Start | Respnone,
+[2]  Start | Resp136,
+[3]  Start | Resp48,
+[5]  Start | Resp48,
+[6]  Start | Resp48,
+[63]  Start | Resp48 | Card2host,
+[7]  Start | Resp48busy,
+[8]  Start | Resp48,
+[9]  Start | Resp136,
+[11] Start | Resp48,
+[12] Start | Resp48busy,
+[13] Start | Resp48,
+[16] Start | Resp48,
+[17] Start | Resp48 | Card2host,
+[18] Start | Resp48 | Card2host,
+[24] Start | Resp48 | Host2card,
+[25] Start | Resp48 | Host2card,
+[41] Start | Resp48,
+[52] Start | Resp48,
+[53] Start | Resp48,
+[55] Start | Resp48,
+};
+
+typedef struct Ctlr Ctlr;
+
+struct Ctlr {
+	Rendez	r;
+	int	bcount;
+	int	done;
+	ulong	extclk;
+	int	appcmd;
+};
+
+static Ctlr sdhost;
+
+static void sdhostinterrupt(Ureg*, void*);
+
+static void
+WR(int reg, u32int val)
+{
+	u32int *r = (u32int*)SDHOSTREGS;
+
+	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
+	r[reg] = val;
+}
+
+static int
+datadone(void*)
+{
+	return sdhost.done;
+}
+
+static void
+sdhostclock(uint freq)
+{
+	uint div;
+
+	div = sdhost.extclk / freq;
+	if(sdhost.extclk / freq > freq)
+		div++;
+	if(div < 2)
+		div = 2;
+	WR(Clkdiv, div - 2);
+}
+
+static int
+sdhostinit(void)
+{
+	u32int *r;
+	ulong clk;
+	int i;
+
+	/* disconnect emmc and connect sdhost to SD card gpio pins */
+	for(i = 48; i <= 53; i++)
+		gpiosel(i, Alt0);
+	clk = getclkrate(ClkCore);
+	if(clk == 0){
+		clk = Extfreq;
+		print("sdhost: assuming external clock %lud Mhz\n", clk/1000000);
+	}
+	sdhost.extclk = clk;
+	sdhostclock(Initfreq);
+	r = (u32int*)SDHOSTREGS;
+	WR(Poweron, 0);
+	WR(Timeout, 0xF00000);
+	WR(Dbgmode, FINS(r[Dbgmode], 9, 10, (FifoDepth | FifoDepth<<5)));
+	return 0;
+}
+
+static int
+sdhostinquiry(char *inquiry, int inqlen)
+{
+	return snprint(inquiry, inqlen, "BCM SDHost Controller");
+}
+
+static void
+sdhostenable(void)
+{
+	u32int *r;
+
+	r = (u32int*)SDHOSTREGS;
+	USED(r);
+	WR(Poweron, 1);
+	delay(10);
+	WR(Hconfig, Intbuswide | Slowcard | BusyintEn);
+	WR(Clkdiv, 0x7FF);
+	intrenable(IRQsdhost, sdhostinterrupt, nil, 0, "sdhost");
+}
+
+static int
+sdhostcmd(u32int cmd, u32int arg, u32int *resp)
+{
+	u32int *r;
+	u32int c;
+	int i;
+	ulong now;
+
+	r = (u32int*)SDHOSTREGS;
+	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
+	c = cmd | cmdinfo[cmd];
+	/*
+	 * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix
+	 */
+	if(cmd == Switchfunc && !sdhost.appcmd)
+		c |= Card2host;
+	if(cmd != Stoptransmission && (i = (r[Dbgmode] & 0xF)) > 2){
+		print("sdhost: previous command stuck: Dbg=%d Cmd=%ux\n", i, r[Cmd]);
+		error(Eio);
+	}
+	/*
+	 * GoIdle indicates new card insertion: reset bus width & speed
+	 */
+	if(cmd == GoIdle){
+		WR(Hconfig, r[Hconfig] & ~Extbus4);
+		sdhostclock(Initfreq);
+	}
+
+	if(r[Status] & (Errstatus|Dataflag))
+		WR(Status, Errstatus|Dataflag);
+	sdhost.done = 0;
+	WR(Arg, arg);
+	WR(Cmd, c);
+	now = m->ticks;
+	while(((i=r[Cmd])&(Start|Failed)) == Start)
+		if(m->ticks-now > HZ)
+			break;
+	if((i&(Start|Failed)) != 0){
+		if(r[Status] != Cmdtimeout)
+			print("sdhost: cmd %ux arg %ux error stat %ux\n", i, arg, r[Status]);
+		i = r[Status];
+		WR(Status, i);
+		error(Eio);
+	}
+	switch(c & Respmask){
+	case Resp136:
+		resp[0] = r[Resp0];
+		resp[1] = r[Resp1];
+		resp[2] = r[Resp2];
+		resp[3] = r[Resp3];
+		break;
+	case Resp48:
+	case Resp48busy:
+		resp[0] = r[Resp0];
+		break;
+	case Respnone:
+		resp[0] = 0;
+		break;
+	}
+	if((c & Respmask) == Resp48busy){
+		tsleep(&sdhost.r, datadone, 0, 3000);
+	}
+	switch(cmd) {
+	case MMCSelect:
+		/*
+		 * Once card is selected, use faster clock
+		 */
+		delay(1);
+		sdhostclock(SDfreq);
+		delay(1);
+		break;
+	case Setbuswidth:
+		if(sdhost.appcmd){
+			/*
+			 * If card bus width changes, change host bus width
+			 */
+			switch(arg){
+			case 0:
+				WR(Hconfig, r[Hconfig] & ~Extbus4);
+				break;
+			case 2:
+				WR(Hconfig, r[Hconfig] | Extbus4);
+				break;
+			}
+		}else{
+			/*
+			 * If card switched into high speed mode, increase clock speed
+			 */
+			if((arg&0x8000000F) == 0x80000001){
+				delay(1);
+				sdhostclock(SDfreqhs);
+				delay(1);
+			}
+		}
+		break;
+	}
+	sdhost.appcmd = (cmd == Appcmd);
+	return 0;
+}
+
+void
+sdhostiosetup(int write, void *buf, int bsize, int bcount)
+{
+	USED(write);
+	USED(buf);
+
+	sdhost.bcount = bcount;
+	WR(Blksize, bsize);
+	WR(Blkcount, bcount);
+}
+
+static void
+sdhostio(int write, uchar *buf, int len)
+{
+	u32int *r;
+	int piolen;
+	u32int w;
+
+	r = (u32int*)SDHOSTREGS;
+	assert((len&3) == 0);
+	assert((((uintptr)buf)&3) == 0);
+	okay(1);
+	if(waserror()){
+		okay(0);
+		nexterror();
+	}
+	/*
+	 * According to comments in the linux driver, the hardware "doesn't
+	 * manage the FIFO DREQs properly for multi-block transfers" on input,
+	 * so the dma must be stopped early and the last 3 words fetched with pio
+	 */
+	piolen = 0;
+	if(!write && sdhost.bcount > 1){
+		piolen = (FifoDepth-1) * sizeof(u32int);
+		len -= piolen;
+	}
+	if(write)
+		dmastart(DmaChanSdhost, DmaDevSdhost, DmaM2D,
+			buf, &r[Data], len);
+	else
+		dmastart(DmaChanSdhost, DmaDevSdhost, DmaD2M,
+			&r[Data], buf, len);
+	if(dmawait(DmaChanSdhost) < 0)
+		error(Eio);
+	if(!write){
+		cachedinvse(buf, len);
+		buf += len;
+		for(; piolen > 0; piolen -= sizeof(u32int)){
+			if((r[Dbgmode] & 0x1F00) == 0){
+				print("sdhost: FIFO empty after short dma read\n");
+				error(Eio);
+			}
+			w = r[Data];
+			*((u32int*)buf) = w;
+			buf += sizeof(u32int);
+		}
+	}
+	poperror();
+	okay(0);
+}
+
+static void
+sdhostinterrupt(Ureg*, void*)
+{	
+	u32int *r;
+	int i;
+
+	r = (u32int*)SDHOSTREGS;
+	i = r[Status];
+	WR(Status, i);
+	if(i & Busyint){
+		sdhost.done = 1;
+		wakeup(&sdhost.r);
+	}
+}
+
+SDio sdiohost = {
+	"sdhost",
+	sdhostinit,
+	sdhostenable,
+	sdhostinquiry,
+	sdhostcmd,
+	sdhostiosetup,
+	sdhostio,
+};
+
+void
+sdhostlink(void)
+{
+	sdcardlink = &sdiohost;
+}
diff -r 44eae9a7e98b sys/src/9/bcm/sdmmc.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/sdmmc.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,355 @@
+/*
+ * mmc / sd memory card
+ *
+ * Copyright © 2012 Richard Miller <r.miller@acm.org>
+ *
+ * Assumes only one card on the bus
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+#include "../port/sd.h"
+
+#define CSD(end, start)	rbits(csd, start, (end)-(start)+1)
+
+typedef struct Ctlr Ctlr;
+
+enum {
+	Inittimeout	= 15,
+	Multiblock	= 1,
+
+	/* Commands */
+	GO_IDLE_STATE	= 0,
+	ALL_SEND_CID	= 2,
+	SEND_RELATIVE_ADDR= 3,
+	SWITCH_FUNC	= 6,
+	SELECT_CARD	= 7,
+	SD_SEND_IF_COND	= 8,
+	SEND_CSD	= 9,
+	STOP_TRANSMISSION= 12,
+	SEND_STATUS	= 13,
+	SET_BLOCKLEN	= 16,
+	READ_SINGLE_BLOCK= 17,
+	READ_MULTIPLE_BLOCK= 18,
+	WRITE_BLOCK	= 24,
+	WRITE_MULTIPLE_BLOCK= 25,
+	APP_CMD		= 55,	/* prefix for following app-specific commands */
+	SET_BUS_WIDTH	= 6,
+	SD_SEND_OP_COND	= 41,
+
+	/* Command arguments */
+	/* SD_SEND_IF_COND */
+	Voltage		= 1<<8,
+	Checkpattern	= 0x42,
+
+	/* SELECT_CARD */
+	Rcashift	= 16,
+
+	/* SD_SEND_OP_COND */
+	Hcs	= 1<<30,	/* host supports SDHC & SDXC */
+	Ccs	= 1<<30,	/* card is SDHC or SDXC */
+	V3_3	= 3<<20,	/* 3.2-3.4 volts */
+
+	/* SET_BUS_WIDTH */
+	Width1	= 0<<0,
+	Width4	= 2<<0,
+
+	/* SWITCH_FUNC */
+	Dfltspeed	= 0<<0,
+	Hispeed		= 1<<0,
+	Checkfunc	= 0x00FFFFF0,
+	Setfunc		= 0x80FFFFF0,
+	Funcbytes	= 64,
+
+	/* OCR (operating conditions register) */
+	Powerup	= 1<<31,
+};
+
+struct Ctlr {
+	SDev	*dev;
+	SDio	*io;
+	/* SD card registers */
+	u16int	rca;
+	u32int	ocr;
+	u32int	cid[4];
+	u32int	csd[4];
+};
+
+SDio *sdcardlink;
+
+extern SDifc sdmmcifc;
+
+static uint
+rbits(u32int *p, uint start, uint len)
+{
+	uint w, off, v;
+
+	w   = start / 32;
+	off = start % 32;
+	if(off == 0)
+		v = p[w];
+	else
+		v = p[w] >> off | p[w+1] << (32-off);
+	if(len < 32)
+		return v & ((1<<len) - 1);
+	else
+		return v;
+}
+
+static void
+identify(SDunit *unit, u32int *csd)
+{
+	uint csize, mult;
+
+	unit->secsize = 1 << CSD(83, 80);
+	switch(CSD(127, 126)){
+	case 0:				/* CSD version 1 */
+		csize = CSD(73, 62);
+		mult = CSD(49, 47);
+		unit->sectors = (csize+1) * (1<<(mult+2));
+		break;
+	case 1:				/* CSD version 2 */
+		csize = CSD(69, 48);
+		unit->sectors = (csize+1) * 512LL*KiB / unit->secsize;
+		break;
+	}
+	if(unit->secsize == 1024){
+		unit->sectors <<= 1;
+		unit->secsize = 512;
+	}
+}
+
+static SDev*
+mmcpnp(void)
+{
+	SDev *sdev;
+	Ctlr *ctl;
+
+	if(sdcardlink == nil)
+		sdcardlink = &sdio;
+	if(sdcardlink->init() < 0)
+		return nil;
+	sdev = malloc(sizeof(SDev));
+	if(sdev == nil)
+		return nil;
+	ctl = malloc(sizeof(Ctlr));
+	if(ctl == nil){
+		free(sdev);
+		return nil;
+	}
+	sdev->idno = 'M';
+	sdev->ifc = &sdmmcifc;
+	sdev->nunit = 1;
+	sdev->ctlr = ctl;
+	ctl->dev = sdev;
+	ctl->io = sdcardlink;
+	return sdev;
+}
+
+static int
+mmcverify(SDunit *unit)
+{
+	int n;
+	Ctlr *ctl;
+
+	ctl = unit->dev->ctlr;
+	n = ctl->io->inquiry((char*)&unit->inquiry[8], sizeof(unit->inquiry)-8);
+	if(n < 0)
+		return 0;
+	unit->inquiry[0] = 0;
+	unit->inquiry[1] = 0x80;
+	unit->inquiry[4] = sizeof(unit->inquiry)-4;
+	return 1;
+}
+
+static int
+mmcenable(SDev* dev)
+{
+	Ctlr *ctl;
+
+	ctl = dev->ctlr;
+	ctl->io->enable();
+	return 1;
+}
+
+static void
+mmcswitchfunc(SDio *io, int arg)
+{
+	uchar *buf;
+	int n;
+	u32int r[4];
+
+	n = Funcbytes;
+	buf = sdmalloc(n);
+	if(waserror()){
+		print("mmcswitchfunc error\n");
+		sdfree(buf);
+		nexterror();
+	}
+	io->iosetup(0, buf, n, 1);
+	io->cmd(SWITCH_FUNC, arg, r);
+	io->io(0, buf, n);
+	sdfree(buf);
+	poperror();
+}
+
+static int
+mmconline(SDunit *unit)
+{
+	int hcs, i;
+	u32int r[4];
+	Ctlr *ctl;
+	SDio *io;
+
+	ctl = unit->dev->ctlr;
+	io = ctl->io;
+	assert(unit->subno == 0);
+
+	if(waserror()){
+		unit->sectors = 0;
+		return 0;
+	}
+	if(unit->sectors != 0){
+		io->cmd(SEND_STATUS, ctl->rca<<Rcashift, r);
+		poperror();
+		return 1;
+	}
+	if(waserror()){
+		unit->sectors = 0;
+		poperror();
+		return 2;
+	}
+	io->cmd(GO_IDLE_STATE, 0, r);
+	hcs = 0;
+	if(!waserror()){
+		io->cmd(SD_SEND_IF_COND, Voltage|Checkpattern, r);
+		if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
+			hcs = Hcs;
+		poperror();
+	}
+	for(i = 0; i < Inittimeout; i++){
+		delay(100);
+		io->cmd(APP_CMD, 0, r);
+		io->cmd(SD_SEND_OP_COND, hcs|V3_3, r);
+		if(r[0] & Powerup)
+			break;
+	}
+	if(i == Inittimeout){
+		print("sdmmc: card won't power up\n");
+		error(Eio);
+	}
+	poperror();
+	ctl->ocr = r[0];
+	io->cmd(ALL_SEND_CID, 0, r);
+	memmove(ctl->cid, r, sizeof ctl->cid);
+	io->cmd(SEND_RELATIVE_ADDR, 0, r);
+	ctl->rca = r[0]>>16;
+	io->cmd(SEND_CSD, ctl->rca<<Rcashift, r);
+	memmove(ctl->csd, r, sizeof ctl->csd);
+	identify(unit, ctl->csd);
+	io->cmd(SELECT_CARD, ctl->rca<<Rcashift, r);
+	io->cmd(SET_BLOCKLEN, unit->secsize, r);
+	io->cmd(APP_CMD, ctl->rca<<Rcashift, r);
+	io->cmd(SET_BUS_WIDTH, Width4, r);
+	if(!waserror()){
+		mmcswitchfunc(io, Hispeed|Setfunc);
+		poperror();
+	}
+	poperror();
+	return 1;
+}
+
+static int
+mmcrctl(SDunit *unit, char *p, int l)
+{
+	Ctlr *ctl;
+	int i, n;
+
+	ctl = unit->dev->ctlr;
+	assert(unit->subno == 0);
+	if(unit->sectors == 0){
+		mmconline(unit);
+		if(unit->sectors == 0)
+			return 0;
+	}
+	n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctl->rca, ctl->ocr);
+	for(i = nelem(ctl->cid)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctl->cid[i]);
+	n += snprint(p+n, l-n, " csd ");
+	for(i = nelem(ctl->csd)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctl->csd[i]);
+	n += snprint(p+n, l-n, "\ngeometry %llud %ld %lld 255 63\n",
+		unit->sectors, unit->secsize, unit->sectors / (255*63));
+	return n;
+}
+
+static long
+mmcbio(SDunit *unit, int lun, int write, void *data, long nb, uvlong bno)
+{
+	int len, tries;
+	ulong b;
+	u32int r[4];
+	uchar *buf;
+	Ctlr *ctl;
+	SDio *io;
+
+	USED(lun);
+	ctl = unit->dev->ctlr;
+	io = ctl->io;
+	assert(unit->subno == 0);
+	if(unit->sectors == 0)
+		error("media change");
+	buf = data;
+	len = unit->secsize;
+	if(Multiblock){
+		b = bno;
+		tries = 0;
+		while(waserror())
+			if(++tries == 3)
+				nexterror();
+		io->iosetup(write, buf, len, nb);
+		if(waserror()){
+			io->cmd(STOP_TRANSMISSION, 0, r);
+			nexterror();
+		}
+		io->cmd(write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK,
+			ctl->ocr & Ccs? b: b * len, r);
+		io->io(write, buf, nb * len);
+		poperror();
+		io->cmd(STOP_TRANSMISSION, 0, r);
+		poperror();
+		b += nb;
+	}else{
+		for(b = bno; b < bno + nb; b++){
+			io->iosetup(write, buf, len, 1);
+			io->cmd(write? WRITE_BLOCK : READ_SINGLE_BLOCK,
+				ctl->ocr & Ccs? b: b * len, r);
+			io->io(write, buf, len);
+			buf += len;
+		}
+	}
+	return (b - bno) * len;
+}
+
+static int
+mmcrio(SDreq*)
+{
+	return -1;
+}
+
+SDifc sdmmcifc = {
+	.name	= "mmc",
+	.pnp	= mmcpnp,
+	.enable	= mmcenable,
+	.verify	= mmcverify,
+	.online	= mmconline,
+	.rctl	= mmcrctl,
+	.bio	= mmcbio,
+	.rio	= mmcrio,
+};

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-13 12:16 ` [9front] " Eli Cohen
@ 2020-10-16  3:42   ` kokamoto
  2020-10-16 21:31     ` Eli Cohen
  0 siblings, 1 reply; 8+ messages in thread
From: kokamoto @ 2020-10-16  3:42 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 1522 bytes --]

I got success on my rpi3B terminal, and the following is
my report:

Plan 9 Console
127 holes free
0x0051a000 0x19243000 416452608
416452608 bytes free

Plan 9 from Bell Labs
board rev: 0xa02082 firmware rev: 1574098197
cpu0: 1200MHz ARM Coretex-A53 r0p4
fp: 32 registers, simd
fp: arm arch CFPv3+ with null subarch; rev 4
"l0: 4330: 10Mbps port 0x0 irq -1 ea 000000000000
498M memory: 403M kernel data, 595M user, 3574M swap
cpu1: 1200MHz ARM Coretex-A53 r0p4
cpu2: 1200MHz ARM Coretex-A53 r0p4
cpu3: 1200MHz ARM Coretex-A53 r0p4
ether4330: chip 43430 rev 1 type 1
ether4330: firmware ready
ether4330: addr xxxxxxxxxxxx

/dev/sdM0:	BCM SDHost Controller
/dev/sdM0/data
/dev/sdM0/dos	dos
/dev/sdM0/fs	hjfs
/dev/sdM0/nvram
/dev/sdM0/plan9
bootargs is (tcp, tls, il, local!device) local!/dev/sdM0/fs <====

<now we can login on a rio rc window>
term% aux/wpa -s aterm-dcc371-g -p2 /net/ether0  <====
<now you arer asked as type password>
passwd: xxxxxxxxxxxxx
term% ip/ipconfig -g 192.168.11.1 ether /net/ether0 add 192.168.11.19 255.255.255.0 192.168.11.5 192.168.11.5  <====
term% abaco http://google.co.jp

OK! I got goole.

Problem: I cannot boot this machine from network fs.
My cmdlin.txt is:
console=0 ether0=type=4330 essid=aterm-dcc371-g wpapsk=xxxxxxxxxxxxx wificrypt=wpa2 fs=192.168.11.5 auth=192.168.11.5 bootargs=tls nora6=
However, thos essid etc are not accepted by the booting kernel.

Kenji

PS: 
by the way, I remember someone reported this for arm64, I forgot to archive it, 
please let me know the place

[-- Attachment #2: Type: message/rfc822, Size: 100012 bytes --]

[-- Attachment #2.1.1: Type: text/plain, Size: 207 bytes --]

I managed to get this working with the 32-bit kernel, though it
doesn't like my access point

it isn't really clean enough to be included...

I'm using the linux firmware, I dunno if that makes a difference

[-- Attachment #2.1.2: pi2wifi.diff --]
[-- Type: application/octet-stream, Size: 71372 bytes --]

diff -r 44eae9a7e98b sys/src/9/bcm/emmc.c
--- a/sys/src/9/bcm/emmc.c	Sun Oct 11 14:59:49 2020 +0200
+++ b/sys/src/9/bcm/emmc.c	Tue Oct 13 05:09:49 2020 -0700
@@ -10,7 +10,7 @@
 		22-27 (P1 header)
 		34-39 (wifi - pi3 only)
 	using ALT3 function to activate the required routing
-*/
+ */
 
 #include "u.h"
 #include "../port/lib.h"
@@ -34,12 +34,11 @@
 	GoIdle		= 0,		/* mmc/sdio go idle state */
 	MMCSelect	= 7,		/* mmc/sd card select command */
 	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-
 	Switchfunc	= 6,		/* mmc/sd switch function command */
-	Voltageswitch	= 11,		/* md/sdio switch to 1.8V */
-	IORWdirect	= 52,		/* sdio read/write direct command */
-	IORWextended	= 53,		/* sdio read/write extended command */
-	Appcmd		= 55,		/* mmc/sd application command prefix */
+	Voltageswitch = 11,		/* md/sdio switch to 1.8V */
+	IORWdirect = 52,		/* sdio read/write direct command */
+	IORWextended = 53,		/* sdio read/write extended command */
+	Appcmd = 55,			/* mmc/sd application command prefix */
 };
 
 enum {
@@ -72,7 +71,7 @@
 	Slotisrver		= 0xfc>>2,
 
 	/* Control0 */
-	Hispeed			= 1<<2,	
+	Hispeed			= 1<<2,
 	Dwidth4			= 1<<1,
 	Dwidth1			= 0<<1,
 
@@ -122,7 +121,7 @@
 	Ccrcerr		= 1<<17,
 	Ctoerr		= 1<<16,
 	Err		= 1<<15,
-	Cardintr	= 1<<8,		/* not in Broadcom datasheet */
+	Cardintr	= 1<<8,
 	Cardinsert	= 1<<6,		/* not in Broadcom datasheet */
 	Readrdy		= 1<<5,
 	Writerdy	= 1<<4,
@@ -158,7 +157,7 @@
 [25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken,
 [41] Resp48,
 [52] Resp48 | Ixchken | Crcchken,
-[53] Resp48 | Ixchken | Crcchken | Isdata,
+[53] Resp48	| Ixchken | Crcchken | Isdata,
 [55] Resp48 | Ixchken | Crcchken,
 };
 
@@ -182,7 +181,7 @@
 	u32int *r = (u32int*)EMMCREGS;
 
 	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
-	microdelay(emmc.fastclock ? 2: 20);
+	microdelay(emmc.fastclock? 2 : 20);
 	coherence();
 	r[reg] = val;
 }
@@ -288,6 +287,24 @@
 	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, "mmc");
 }
 
+int
+sdiocardintr(int wait)
+{
+	u32int *r;
+	int i;
+
+	r = (u32int*)EMMCREGS;
+	WR(Interrupt, Cardintr);
+	while(((i = r[Interrupt]) & Cardintr) == 0){
+		if(!wait)
+			return 0;
+		WR(Irpten, r[Irpten] | Cardintr);
+		sleep(&emmc.cardr, cardintready, 0);
+	}
+	WR(Interrupt, Cardintr);
+	return i;
+}
+
 static int
 emmccmd(u32int cmd, u32int arg, u32int *resp)
 {
@@ -319,8 +336,7 @@
 		WR(Control0, r[Control0] & ~(Dwidth4|Hispeed));
 		emmcclk(Initfreq);
 	}
-	if((r[Status] & Datinhibit) &&
-	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
+	if(r[Status] & Cmdinhibit){
 		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstcmd);
@@ -329,8 +345,8 @@
 		while(r[Status] & Cmdinhibit)
 			;
 	}
-	if((c & Isdata || (c & Respmask) == Resp48busy) &&
-	    r[Status] & Datinhibit){
+	if((r[Status] & Datinhibit) &&
+	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
 		print("emmccmd: need to reset Datinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstdata);
@@ -435,7 +451,7 @@
 	return 0;
 }
 
-static void
+void
 emmciosetup(int write, void *buf, int bsize, int bcount)
 {
 	USED(write);
@@ -464,6 +480,8 @@
 			&r[Data], buf, len);
 	if(dmawait(DmaChanEmmc) < 0)
 		error(Eio);
+	if(!write)
+		cachedinvse(buf, len);
 	WR(Irpten, r[Irpten]|Datadone|Err);
 	tsleep(&emmc.r, datadone, 0, 3000);
 	i = r[Interrupt]&~Cardintr;
@@ -508,5 +526,4 @@
 	emmccmd,
 	emmciosetup,
 	emmcio,
-	.highspeed = 1,
 };
diff -r 44eae9a7e98b sys/src/9/bcm/ether4330.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/ether4330.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,2396 @@
+/*
+ * Broadcom bcm4330 wifi (sdio interface)
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/sd.h"
+
+extern int sdiocardintr(int);
+
+#include "../port/etherif.h"
+#define CACHELINESZ 64	/* temp */
+
+enum{
+	SDIODEBUG = 0,
+	SBDEBUG = 0,
+	EVENTDEBUG = 0,
+	VARDEBUG = 0,
+	FWDEBUG  = 0,
+
+	Corescansz = 512,
+	Uploadsz = 2048,
+
+	Wifichan = 0,		/* default channel */
+	Firmwarecmp	= 1,
+
+	ARMcm3		= 0x82A,
+	ARM7tdmi	= 0x825,
+	ARMcr4		= 0x83E,
+
+	Fn0	= 0,
+	Fn1 	= 1,
+	Fn2	= 2,
+	Fbr1	= 0x100,
+	Fbr2	= 0x200,
+
+	/* CCCR */
+	Ioenable	= 0x02,
+	Ioready		= 0x03,
+	Intenable	= 0x04,
+	Intpend		= 0x05,
+	Ioabort		= 0x06,
+	Busifc		= 0x07,
+	Capability	= 0x08,
+	Blksize		= 0x10,
+	Highspeed	= 0x13,
+
+	/* SDIOCommands */
+	GO_IDLE_STATE		= 0,
+	SEND_RELATIVE_ADDR	= 3,
+	IO_SEND_OP_COND		= 5,
+	SELECT_CARD		= 7,
+	VOLTAGE_SWITCH 		= 11,
+	IO_RW_DIRECT 		= 52,
+	IO_RW_EXTENDED 		= 53,
+
+	/* SELECT_CARD args */
+	Rcashift	= 16,
+
+	/* SEND_OP_COND args */
+	Hcs	= 1<<30,	/* host supports SDHC & SDXC */
+	V3_3	= 3<<20,	/* 3.2-3.4 volts */
+	V2_8	= 3<<15,	/* 2.7-2.9 volts */
+	V2_0	= 1<<8,		/* 2.0-2.1 volts */
+	S18R	= 1<<24,	/* switch to 1.8V request */
+
+	/* Sonics Silicon Backplane (access to cores on chip) */
+	Sbwsize	= 0x8000,
+	Sb32bit	= 0x8000,
+	Sbaddr	= 0x1000a,
+		Enumbase	= 	0x18000000,
+	Framectl= 0x1000d,
+		Rfhalt		=	0x01,
+		Wfhalt		=	0x02,
+	Clkcsr	= 0x1000e,
+		ForceALP	=	0x01,	/* active low-power clock */
+		ForceHT		= 	0x02,	/* high throughput clock */
+		ForceILP	=	0x04,	/* idle low-power clock */
+		ReqALP		=	0x08,
+		ReqHT		=	0x10,
+		Nohwreq		=	0x20,
+		ALPavail	=	0x40,
+		HTavail		=	0x80,
+	Pullups	= 0x1000f,
+	Wfrmcnt	= 0x10019,
+	Rfrmcnt	= 0x1001b,
+		
+	/* core control regs */
+	Ioctrl		= 0x408,
+	Resetctrl	= 0x800,
+
+	/* socram regs */
+	Coreinfo	= 0x00,
+	Bankidx		= 0x10,
+	Bankinfo	= 0x40,
+	Bankpda		= 0x44,
+
+	/* armcr4 regs */
+	Cr4Cap		= 0x04,
+	Cr4Bankidx	= 0x40,
+	Cr4Bankinfo	= 0x44,
+	Cr4Cpuhalt	= 0x20,
+
+	/* chipcommon regs */
+	Gpiopullup	= 0x58,
+	Gpiopulldown	= 0x5c,
+	Chipctladdr	= 0x650,
+	Chipctldata	= 0x654,
+
+	/* sdio core regs */
+	Intstatus	= 0x20,
+		Fcstate		= 1<<4,
+		Fcchange	= 1<<5,
+		FrameInt	= 1<<6,
+		MailboxInt	= 1<<7,
+	Intmask		= 0x24,
+	Sbmbox		= 0x40,
+	Sbmboxdata	= 0x48,
+	Hostmboxdata= 0x4c,
+		Fwready		= 0x80,
+
+	/* wifi control commands */
+	GetVar	= 262,
+	SetVar	= 263,
+
+	/* status */
+	Disconnected=	0,
+	Connecting,
+	Connected,
+};
+
+typedef struct Ctlr Ctlr;
+
+enum{
+	Wpa		= 1,
+	Wep		= 2,
+	Wpa2		= 3,
+	WNameLen	= 32,
+	WNKeys		= 4,
+	WKeyLen		= 32,
+	WMinKeyLen	= 5,
+	WMaxKeyLen	= 13,
+};
+
+typedef struct WKey WKey;
+struct WKey
+{
+	ushort	len;
+	char	dat[WKeyLen];
+};
+
+struct Ctlr {
+	Ether*	edev;
+	QLock	cmdlock;
+	QLock	pktlock;
+	QLock	tlock;
+	QLock	alock;
+	Lock	txwinlock;
+	Rendez	cmdr;
+	Rendez	joinr;
+	int	joinstatus;
+	int	cryptotype;
+	int	chanid;
+	char	essid[WNameLen + 1];
+	WKey	keys[WNKeys];
+	Block	*rsp;
+	Block	*scanb;
+	int	scansecs;
+	int	status;
+	int	chipid;
+	int	chiprev;
+	int	armcore;
+	char	*regufile;
+	union {
+		u32int i;
+		uchar c[4];
+	} resetvec;
+	ulong	chipcommon;
+	ulong	armctl;
+	ulong	armregs;
+	ulong	d11ctl;
+	ulong	socramregs;
+	ulong	socramctl;
+	ulong	sdregs;
+	int	sdiorev;
+	int	socramrev;
+	ulong	socramsize;
+	ulong	rambase;
+	short	reqid;
+	uchar	fcmask;
+	uchar	txwindow;
+	uchar	txseq;
+	uchar	rxseq;
+};
+
+enum{
+	CMauth,
+	CMchannel,
+	CMcrypt,
+	CMessid,
+	CMkey1,
+	CMkey2,
+	CMkey3,
+	CMkey4,
+	CMrxkey,
+	CMrxkey0,
+	CMrxkey1,
+	CMrxkey2,
+	CMrxkey3,
+	CMtxkey,
+	CMdebug,
+	CMjoin,
+};
+
+static Cmdtab cmds[] = {
+	{CMauth,	"auth", 2},
+	{CMchannel,	"channel", 2},
+	{CMcrypt,	"crypt", 2},
+	{CMessid,	"essid", 2},
+	{CMkey1,	"key1",	2},
+	{CMkey2,	"key1",	2},
+	{CMkey3,	"key1",	2},
+	{CMkey4,	"key1",	2},
+	{CMrxkey,	"rxkey", 3},
+	{CMrxkey0,	"rxkey0", 3},
+	{CMrxkey1,	"rxkey1", 3},
+	{CMrxkey2,	"rxkey2", 3},
+	{CMrxkey3,	"rxkey3", 3},
+	{CMtxkey,	"txkey", 3},
+	{CMdebug,	"debug", 2},
+	{CMjoin,	"join", 5},
+};
+
+typedef struct Sdpcm Sdpcm;
+typedef struct Cmd Cmd;
+struct Sdpcm {
+	uchar	len[2];
+	uchar	lenck[2];
+	uchar	seq;
+	uchar	chanflg;
+	uchar	nextlen;
+	uchar	doffset;
+	uchar	fcmask;
+	uchar	window;
+	uchar	version;
+	uchar	pad;
+};
+
+struct Cmd {
+	uchar	cmd[4];
+	uchar	len[4];
+	uchar	flags[2];
+	uchar	id[2];
+	uchar	status[4];
+};
+
+static char config40181[] = "bcmdhd.cal.40181";
+static char config40183[] = "bcmdhd.cal.40183.26MHz";
+
+struct {
+	int chipid;
+	int chiprev;
+	char *fwfile;
+	char *cfgfile;
+	char *regufile;
+} firmware[] = {
+	{ 0x4330, 3,	"fw_bcm40183b1.bin", config40183, 0 },
+	{ 0x4330, 4,	"fw_bcm40183b2.bin", config40183, 0 },
+	{ 43362, 0,	"fw_bcm40181a0.bin", config40181, 0 },
+	{ 43362, 1,	"fw_bcm40181a2.bin", config40181, 0 },
+	{ 43430, 1,	"brcmfmac43430-sdio.bin", "brcmfmac43430-sdio.txt", 0 },
+	{ 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" },
+};
+
+static QLock sdiolock;
+static int iodebug;
+
+static void etherbcmintr(void *);
+static void bcmevent(Ctlr*, uchar*, int);
+static void wlscanresult(Ether*, uchar*, int);
+static void wlsetvar(Ctlr*, char*, void*, int);
+
+static uchar*
+put2(uchar *p, short v)
+{
+	p[0] = v;
+	p[1] = v >> 8;
+	return p + 2;
+}
+
+static uchar*
+put4(uchar *p, long v)
+{
+	p[0] = v;
+	p[1] = v >> 8;
+	p[2] = v >> 16;
+	p[3] = v >> 24;
+	return p + 4;
+}
+
+static ushort
+get2(uchar *p)
+{
+	return p[0] | p[1]<<8;
+}
+
+static ulong
+get4(uchar *p)
+{
+	return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+}
+
+static void
+dump(char *s, void *a, int n)
+{
+	int i;
+	uchar *p;
+
+	p = a;
+	print("%s:", s);
+	for(i = 0; i < n; i++)
+		print("%c%2.2x", i&15? ' ' : '\n', *p++);
+	print("\n");
+}
+
+/*
+ * SDIO communication with dongle
+ */
+static ulong
+sdiocmd_locked(int cmd, ulong arg)
+{
+	u32int resp[4];
+
+	sdio.cmd(cmd, arg, resp);
+	return resp[0];
+}
+
+static ulong
+sdiocmd(int cmd, ulong arg)
+{
+	ulong r;
+
+	qlock(&sdiolock);
+	if(waserror()){
+		if(SDIODEBUG) print("sdiocmd error: cmd %d arg %lux\n", cmd, arg);
+		qunlock(&sdiolock);
+		nexterror();
+	}
+	r = sdiocmd_locked(cmd, arg);
+	qunlock(&sdiolock);
+	poperror();
+	return r;
+
+}
+
+static ulong
+trysdiocmd(int cmd, ulong arg)
+{
+	ulong r;
+
+	if(waserror())
+		return 0;
+	r = sdiocmd(cmd, arg);
+	poperror();
+	return r;
+}
+
+static int
+sdiord(int fn, int addr)
+{
+	int r;
+
+	r = sdiocmd(IO_RW_DIRECT, (0<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9));
+	if(r & 0xCF00){
+		print("ether4330: sdiord(%x, %x) fail: %2.2ux %2.2ux\n", fn, addr, (r>>8)&0xFF, r&0xFF);
+		error(Eio);
+	}
+	return r & 0xFF;
+}
+
+static void
+sdiowr(int fn, int addr, int data)
+{
+	int r;
+	int retry;
+
+	r = 0;
+	for(retry = 0; retry < 10; retry++){
+		r = sdiocmd(IO_RW_DIRECT, (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF));
+		if((r & 0xCF00) == 0)
+			return;
+	}
+	print("ether4330: sdiowr(%x, %x, %x) fail: %2.2ux %2.2ux\n", fn, addr, data, (r>>8)&0xFF, r&0xFF);
+	error(Eio);
+}
+
+static void
+sdiorwext(int fn, int write, void *a, int len, int addr, int incr)
+{
+	int bsize, blk, bcount, m;
+
+	bsize = fn == Fn2? 512 : 64;
+	while(len > 0){
+		if(len >= 511*bsize){
+			blk = 1;
+			bcount = 511;
+			m = bcount*bsize;
+		}else if(len > bsize){
+			blk = 1;
+			bcount = len/bsize;
+			m = bcount*bsize;
+		}else{
+			blk = 0;
+			bcount = len;
+			m = bcount;
+		}
+		qlock(&sdiolock);
+		if(waserror()){
+			print("ether4330: sdiorwext fail: %s\n", up->errstr);
+			qunlock(&sdiolock);
+			nexterror();
+		}
+		if(blk)
+			sdio.iosetup(write, a, bsize, bcount);
+		else
+			sdio.iosetup(write, a, bcount, 1);
+		sdiocmd_locked(IO_RW_EXTENDED,
+			write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF));
+		sdio.io(write, a, m);
+		qunlock(&sdiolock);
+		poperror();
+		len -= m;
+		a = (char*)a + m;
+		if(incr)
+			addr += m;
+	}
+}
+
+static void
+sdioset(int fn, int addr, int bits)
+{
+	sdiowr(fn, addr, sdiord(fn, addr) | bits);
+}
+	
+static void
+sdioinit(void)
+{
+	ulong ocr, rca;
+	int i;
+
+	// disconnect emmc from SD card (connect sdhost instead)
+	for(i = 48; i <= 53; i++)
+		gpiosel(i, Alt0);
+	// connect emmc to wifi
+	for(i = 34; i <= 39; i++){
+		gpiosel(i, Alt3);
+		if(i == 34)
+			gpiopulloff(i);
+		else
+			gpiopullup(i);
+	}
+	sdio.init();
+	sdio.enable();
+	sdiocmd(GO_IDLE_STATE, 0);
+	ocr = trysdiocmd(IO_SEND_OP_COND, 0);
+	i = 0;
+	while((ocr & (1<<31)) == 0){
+		if(++i > 5){
+			print("ether4330: no response to sdio access: ocr = %lux\n", ocr);
+			error(Eio);
+		}
+		ocr = trysdiocmd(IO_SEND_OP_COND, V3_3);
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	rca = sdiocmd(SEND_RELATIVE_ADDR, 0) >> Rcashift;
+	sdiocmd(SELECT_CARD, rca << Rcashift);
+	sdioset(Fn0, Highspeed, 2);
+	sdioset(Fn0, Busifc, 2);	// bus width 4
+	sdiowr(Fn0, Fbr1+Blksize, 64);
+	sdiowr(Fn0, Fbr1+Blksize+1, 64>>8);
+	sdiowr(Fn0, Fbr2+Blksize, 512);
+	sdiowr(Fn0, Fbr2+Blksize+1, 512>>8);
+	sdioset(Fn0, Ioenable, 1<<Fn1);
+	sdiowr(Fn0, Intenable, 0);
+	for(i = 0; !(sdiord(Fn0, Ioready) & 1<<Fn1); i++){
+		if(i == 10){
+			print("ether4330: can't enable SDIO function\n");
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+}
+
+static void
+sdioreset(void)
+{
+	sdiowr(Fn0, Ioabort, 1<<3);	/* reset */
+}
+
+static void
+sdioabort(int fn)
+{
+	sdiowr(Fn0, Ioabort, fn);
+}
+
+/*
+ * Chip register and memory access via SDIO
+ */
+
+static void
+cfgw(ulong off, int val)
+{
+	sdiowr(Fn1, off, val);
+}
+
+static int
+cfgr(ulong off)
+{
+	return sdiord(Fn1, off);
+}
+
+static ulong
+cfgreadl(int fn, ulong off)
+{
+	uchar cbuf[2*CACHELINESZ];
+	uchar *p;
+
+	p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ);
+	memset(p, 0, 4);
+	sdiorwext(fn, 0, p, 4, off|Sb32bit, 1);
+	if(SDIODEBUG) print("cfgreadl %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]);
+	return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+}
+
+static void
+cfgwritel(int fn, ulong off, u32int data)
+{
+	uchar cbuf[2*CACHELINESZ];
+	uchar *p;
+	int retry;
+
+	p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ);
+	put4(p, data);
+	if(SDIODEBUG) print("cfgwritel %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]);
+	retry = 0;
+	while(waserror()){
+		print("ether4330: cfgwritel retry %lux %ux\n", off, data);
+		sdioabort(fn);
+		if(++retry == 3)
+			nexterror();
+	}
+	sdiorwext(fn, 1, p, 4, off|Sb32bit, 1);
+	poperror();
+}
+
+static void
+sbwindow(ulong addr)
+{
+	addr &= ~(Sbwsize-1);
+	cfgw(Sbaddr, addr>>8);
+	cfgw(Sbaddr+1, addr>>16);
+	cfgw(Sbaddr+2, addr>>24);
+}
+
+static void
+sbrw(int fn, int write, uchar *buf, int len, ulong off)
+{
+	int n;
+	USED(fn);
+
+	if(waserror()){
+		print("ether4330: sbrw err off %lux len %ud\n", off, len);
+		nexterror();
+	}
+	if(write){
+		if(len >= 4){
+			n = len;
+			n &= ~3;
+			sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1);
+			off += n;
+			buf += n;
+			len -= n;
+		}
+		while(len > 0){
+			sdiowr(Fn1, off|Sb32bit, *buf);
+			off++;
+			buf++;
+			len--;
+		}
+	}else{
+		if(len >= 4){
+			n = len;
+			n &= ~3;
+			sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1);
+			off += n;
+			buf += n;
+			len -= n;
+		}
+		while(len > 0){
+			*buf = sdiord(Fn1, off|Sb32bit);
+			off++;
+			buf++;
+			len--;
+		}
+	}
+	poperror();
+}
+
+static void
+sbmem(int write, uchar *buf, int len, ulong off)
+{
+	ulong n;
+
+	n = ROUNDUP(off, Sbwsize) - off;
+	if(n == 0)
+		n = Sbwsize;
+	while(len > 0){
+		if(n > len)
+			n = len;
+		sbwindow(off);
+		sbrw(Fn1, write, buf, n, off & (Sbwsize-1));
+		off += n;
+		buf += n;
+		len -= n;
+		n = Sbwsize;
+	}
+}
+
+static void
+packetrw(int write, uchar *buf, int len)
+{
+	int n;
+	int retry;
+
+	n = 2048;
+	while(len > 0){
+		if(n > len)
+			n = ROUND(len, 4);
+		retry = 0;
+		while(waserror()){
+			sdioabort(Fn2);
+			if(++retry == 3)
+				nexterror();
+		}
+		sdiorwext(Fn2, write, buf, n, Enumbase, 0);
+		poperror();
+		buf += n;
+		len -= n;
+	}
+}
+
+/*
+ * Configuration and control of chip cores via Silicon Backplane
+ */
+
+static void
+sbdisable(ulong regs, int pre, int ioctl)
+{
+	sbwindow(regs);
+	if((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){
+		cfgwritel(Fn1, regs + Ioctrl, 3|ioctl);
+		cfgreadl(Fn1, regs + Ioctrl);
+		return;
+	}
+	cfgwritel(Fn1, regs + Ioctrl, 3|pre);
+	cfgreadl(Fn1, regs + Ioctrl);
+	cfgwritel(Fn1, regs + Resetctrl, 1);
+	microdelay(10);
+	while((cfgreadl(Fn1, regs + Resetctrl) & 1) == 0)
+		;
+	cfgwritel(Fn1, regs + Ioctrl, 3|ioctl);
+	cfgreadl(Fn1, regs + Ioctrl);
+}
+
+static void
+sbreset(ulong regs, int pre, int ioctl)
+{
+	sbdisable(regs, pre, ioctl);
+	sbwindow(regs);
+	if(SBDEBUG) print("sbreset %#p %#lux %#lux ->", (void*)regs,
+		cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl));
+	while((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){
+		cfgwritel(Fn1, regs + Resetctrl, 0);
+		microdelay(40);
+	}
+	cfgwritel(Fn1, regs + Ioctrl, 1|ioctl);
+	cfgreadl(Fn1, regs + Ioctrl);
+	if(SBDEBUG) print("%#lux %#lux\n",
+		cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl));
+}
+
+static void
+corescan(Ctlr *ctl, ulong r)
+{
+	uchar *buf;
+	int i, coreid, corerev;
+	ulong addr;
+
+	buf = sdmalloc(Corescansz);
+	if(buf == nil)
+		error(Enomem);
+	sbmem(0, buf, Corescansz, r);
+	coreid = 0;
+	corerev = 0;
+	for(i = 0; i < Corescansz; i += 4){
+		switch(buf[i]&0xF){
+		case 0xF:	/* end */
+			sdfree(buf);
+			return;
+		case 0x1:	/* core info */
+			if((buf[i+4]&0xF) != 0x1)
+				break;
+			coreid = (buf[i+1] | buf[i+2]<<8) & 0xFFF;
+			i += 4;
+			corerev = buf[i+3];
+			break;
+		case 0x05:	/* address */
+			addr = buf[i+1]<<8 | buf[i+2]<<16 | buf[i+3]<<24;
+			addr &= ~0xFFF;
+			if(SBDEBUG) print("core %x %s %#p\n", coreid, buf[i]&0xC0? "ctl" : "mem", (void*)addr);
+			switch(coreid){
+			case 0x800:
+				if((buf[i] & 0xC0) == 0)
+					ctl->chipcommon = addr;
+				break;
+			case ARMcm3:
+			case ARM7tdmi:
+			case ARMcr4:
+				ctl->armcore = coreid;
+				if(buf[i] & 0xC0){
+					if(ctl->armctl == 0)
+						ctl->armctl = addr;
+				}else{
+					if(ctl->armregs == 0)
+						ctl->armregs = addr;
+				}
+				break;
+			case 0x80E:
+				if(buf[i] & 0xC0)
+					ctl->socramctl = addr;
+				else if(ctl->socramregs == 0)
+					ctl->socramregs = addr;
+				ctl->socramrev = corerev;
+				break;
+			case 0x829:
+				if((buf[i] & 0xC0) == 0)
+					ctl->sdregs = addr;
+				ctl->sdiorev = corerev;
+				break;
+			case 0x812:
+				if(buf[i] & 0xC0)
+					ctl->d11ctl = addr;
+				break;
+			}
+		}
+	}
+	sdfree(buf);
+}
+
+static void
+ramscan(Ctlr *ctl)
+{
+	ulong r, n, size;
+	int banks, i;
+
+	if(ctl->armcore == ARMcr4){
+		r = ctl->armregs;
+		sbwindow(r);
+		n = cfgreadl(Fn1, r + Cr4Cap);
+		if(SBDEBUG) print("cr4 banks %lux\n", n);
+		banks = ((n>>4) & 0xF) + (n & 0xF);
+		size = 0;
+		for(i = 0; i < banks; i++){
+			cfgwritel(Fn1, r + Cr4Bankidx, i);
+			n = cfgreadl(Fn1, r + Cr4Bankinfo);
+			if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1));
+			size += 8192 * ((n & 0x3F) + 1);
+		}
+		ctl->socramsize = size;
+		ctl->rambase = 0x198000;
+		return;
+	}
+	if(ctl->socramrev <= 7 || ctl->socramrev == 12){
+		print("ether4330: SOCRAM rev %d not supported\n", ctl->socramrev);
+		error(Eio);
+	}
+	sbreset(ctl->socramctl, 0, 0);
+	r = ctl->socramregs;
+	sbwindow(r);
+	n = cfgreadl(Fn1, r + Coreinfo);
+	if(SBDEBUG) print("socramrev %d coreinfo %lux\n", ctl->socramrev, n);
+	banks = (n>>4) & 0xF;
+	size = 0;
+	for(i = 0; i < banks; i++){
+		cfgwritel(Fn1, r + Bankidx, i);
+		n = cfgreadl(Fn1, r + Bankinfo);
+		if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1));
+		size += 8192 * ((n & 0x3F) + 1);
+	}
+	ctl->socramsize = size;
+	ctl->rambase = 0;
+	if(ctl->chipid == 43430){
+		cfgwritel(Fn1, r + Bankidx, 3);
+		cfgwritel(Fn1, r + Bankpda, 0);
+	}
+}
+
+static void
+sbinit(Ctlr *ctl)
+{
+	ulong r;
+	int chipid;
+	char buf[16];
+
+	sbwindow(Enumbase);
+	r = cfgreadl(Fn1, Enumbase);
+	chipid = r & 0xFFFF;
+	sprint(buf, chipid > 43000 ? "%d" : "%#x", chipid);
+	print("ether4330: chip %s rev %ld type %ld\n", buf, (r>>16)&0xF, (r>>28)&0xF);
+	switch(chipid){
+		case 0x4330:
+		case 43362:
+		case 43430:
+		case 0x4345:
+			ctl->chipid = chipid;
+			ctl->chiprev = (r>>16)&0xF;
+			break;
+		default:
+			print("ether4330: chipid %#x (%d) not supported\n", chipid, chipid);
+			error(Eio);
+	}
+	r = cfgreadl(Fn1, Enumbase + 63*4);
+	corescan(ctl, r);
+	if(ctl->armctl == 0 || ctl->d11ctl == 0 ||
+	   (ctl->armcore == ARMcm3 && (ctl->socramctl == 0 || ctl->socramregs == 0)))
+		error("corescan didn't find essential cores\n");
+	if(ctl->armcore == ARMcr4)
+		sbreset(ctl->armctl, Cr4Cpuhalt, Cr4Cpuhalt);
+	else	
+		sbdisable(ctl->armctl, 0, 0);
+	sbreset(ctl->d11ctl, 8|4, 4);
+	ramscan(ctl);
+	if(SBDEBUG) print("ARM %#p D11 %#p SOCRAM %#p,%#p %lud bytes @ %#p\n",
+		(void*)ctl->armctl, (void*)ctl->d11ctl, (void*)ctl->socramctl, (void*)ctl->socramregs, ctl->socramsize, (void*)ctl->rambase);
+	cfgw(Clkcsr, 0);
+	microdelay(10);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	cfgw(Clkcsr, Nohwreq | ReqALP);
+	while((cfgr(Clkcsr) & (HTavail|ALPavail)) == 0)
+		microdelay(10);
+	cfgw(Clkcsr, Nohwreq | ForceALP);
+	microdelay(65);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	cfgw(Pullups, 0);
+	sbwindow(ctl->chipcommon);
+	cfgwritel(Fn1, ctl->chipcommon + Gpiopullup, 0);
+	cfgwritel(Fn1, ctl->chipcommon + Gpiopulldown, 0);
+	if(ctl->chipid != 0x4330 && ctl->chipid != 43362)
+		return;
+	cfgwritel(Fn1, ctl->chipcommon + Chipctladdr, 1);
+	if(cfgreadl(Fn1, ctl->chipcommon + Chipctladdr) != 1)
+		print("ether4330: can't set Chipctladdr\n");
+	else{
+		r = cfgreadl(Fn1, ctl->chipcommon + Chipctldata);
+		if(SBDEBUG) print("chipcommon PMU (%lux) %lux", cfgreadl(Fn1, ctl->chipcommon + Chipctladdr), r);
+		/* set SDIO drive strength >= 6mA */
+		r &= ~0x3800;
+		if(ctl->chipid == 0x4330)
+			r |= 3<<11;
+		else
+			r |= 7<<11;
+		cfgwritel(Fn1, ctl->chipcommon + Chipctldata, r);
+		if(SBDEBUG) print("-> %lux (= %lux)\n", r, cfgreadl(Fn1, ctl->chipcommon + Chipctldata));
+	}
+}
+
+static void
+sbenable(Ctlr *ctl)
+{
+	int i;
+
+	if(SBDEBUG) print("enabling HT clock...");
+	cfgw(Clkcsr, 0);
+	delay(1);
+	cfgw(Clkcsr, ReqHT);
+	for(i = 0; (cfgr(Clkcsr) & HTavail) == 0; i++){
+		if(i == 50){
+			print("ether4330: can't enable HT clock: csr %x\n", cfgr(Clkcsr));
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	cfgw(Clkcsr, cfgr(Clkcsr) | ForceHT);
+	delay(10);
+	if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr));
+	sbwindow(ctl->sdregs);
+	cfgwritel(Fn1, ctl->sdregs + Sbmboxdata, 4 << 16);	/* protocol version */
+	cfgwritel(Fn1, ctl->sdregs + Intmask, FrameInt | MailboxInt | Fcchange);
+	sdioset(Fn0, Ioenable, 1<<Fn2);
+	for(i = 0; !(sdiord(Fn0, Ioready) & 1<<Fn2); i++){
+		if(i == 10){
+			print("ether4330: can't enable SDIO function 2 - ioready %x\n", sdiord(Fn0, Ioready));
+			error(Eio);
+		}
+		tsleep(&up->sleep, return0, nil, 100);
+	}
+	sdiowr(Fn0, Intenable, (1<<Fn1) | (1<<Fn2) | 1);
+}
+
+
+/*
+ * Firmware and config file uploading
+ */
+
+/*
+ * Condense config file contents (in buffer buf with length n)
+ * to 'var=value\0' list for firmware:
+ *	- remove comments (starting with '#') and blank lines
+ *	- remove carriage returns
+ *	- convert newlines to nulls
+ *	- mark end with two nulls
+ *	- pad with nulls to multiple of 4 bytes total length
+ */
+static int
+condense(uchar *buf, int n)
+{
+	uchar *p, *ep, *lp, *op;
+	int c, skipping;
+
+	skipping = 0;	/* true if in a comment */
+	ep = buf + n;	/* end of input */
+	op = buf;	/* end of output */
+	lp = buf;	/* start of current output line */
+	for(p = buf; p < ep; p++){
+		switch(c = *p){
+		case '#':
+			skipping = 1;
+			break;
+		case '\0':
+		case '\n':
+			skipping = 0;
+			if(op != lp){
+				*op++ = '\0';
+				lp = op;
+			}
+			break;
+		case '\r':
+			break;
+		default:
+			if(!skipping)
+				*op++ = c;
+			break;
+		}
+	}
+	if(!skipping && op != lp)
+		*op++ = '\0';
+	*op++ = '\0';
+	for(n = op - buf; n & 03; n++)
+		*op++ = '\0';
+	return n;
+}
+
+/*
+ * Try to find firmware file in /boot or in /sys/lib/firmware.
+ * Throw an error if not found.
+ */
+static Chan*
+findfirmware(char *file)
+{
+	char nbuf[64];
+	Chan *c;
+
+	if(!waserror()){
+		snprint(nbuf, sizeof nbuf, "/boot/%s", file);
+		c = namec(nbuf, Aopen, OREAD, 0);
+		poperror();
+	}else if(!waserror()){
+		snprint(nbuf, sizeof nbuf, "/sys/lib/firmware/%s", file);
+		c = namec(nbuf, Aopen, OREAD, 0);
+		poperror();
+	}else{
+		c = nil;
+		snprint(up->genbuf, sizeof up->genbuf, "can't find %s in /boot or /sys/lib/firmware", file);
+		error(up->genbuf);
+	}
+	return c;	
+}
+
+static int
+upload(Ctlr *ctl, char *file, int isconfig)
+{
+	Chan *c;
+	uchar *buf;
+	uchar *cbuf;
+	int off, n;
+
+	buf = cbuf = nil;
+	c = findfirmware(file);
+	if(waserror()){
+		cclose(c);
+		sdfree(buf);
+		sdfree(cbuf);
+		nexterror();
+	}
+	buf = sdmalloc(Uploadsz);
+	if(buf == nil)
+		error(Enomem);
+	if(Firmwarecmp){
+		cbuf = sdmalloc(Uploadsz);
+		if(cbuf == nil)
+			error(Enomem);
+	}
+	off = 0;
+	for(;;){
+		n = devtab[c->type]->read(c, buf, Uploadsz, off);
+		if(n <= 0)
+			break;
+		if(isconfig){
+			n = condense(buf, n);
+			off = ctl->socramsize - n - 4;
+		}else if(off == 0)
+			memmove(ctl->resetvec.c, buf, sizeof(ctl->resetvec.c));
+		while(n&3)
+			buf[n++] = 0;
+		sbmem(1, buf, n, ctl->rambase + off);
+		if(isconfig)
+			break;
+		off += n;
+	}
+	if(Firmwarecmp){
+		if(FWDEBUG) print("compare...");
+		if(!isconfig)
+			off = 0;
+		for(;;){
+			if(!isconfig){
+				n = devtab[c->type]->read(c, buf, Uploadsz, off);
+				if(n <= 0)
+					break;
+			while(n&3)
+				buf[n++] = 0;
+			}
+			sbmem(0, cbuf, n, ctl->rambase + off);
+			if(memcmp(buf, cbuf, n) != 0){
+				print("ether4330: firmware load failed offset %d\n", off);
+				error(Eio);
+			}
+			if(isconfig)
+				break;
+			off += n;
+		}
+	}
+	if(FWDEBUG) print("\n");
+	poperror();
+	cclose(c);
+	sdfree(buf);
+	sdfree(cbuf);
+	return n;
+}
+
+/*
+ * Upload regulatory file (.clm) to firmware.
+ * Packet format is
+ *	[2]flag [2]type [4]len [4]crc [len]data
+ */
+static void
+reguload(Ctlr *ctl, char *file)
+{
+	Chan *c;
+	uchar *buf;
+	int off, n, flag;
+	enum {
+		Reguhdr = 2+2+4+4,
+		Regusz	= 1400,
+		Regutyp	= 2,
+		Flagclm	= 1<<12,
+		Firstpkt= 1<<1,
+		Lastpkt	= 1<<2,
+	};
+
+	buf = nil;
+	c = findfirmware(file);
+	if(waserror()){
+		cclose(c);
+		free(buf);
+		nexterror();
+	}
+	buf = malloc(Reguhdr+Regusz+1);
+	if(buf == nil)
+		error(Enomem);
+	put2(buf+2, Regutyp);
+	put2(buf+8, 0);
+	off = 0;
+	flag = Flagclm | Firstpkt;
+	while((flag&Lastpkt) == 0){
+		n = devtab[c->type]->read(c, buf+Reguhdr, Regusz+1, off);
+		if(n <= 0)
+			break;
+		if(n == Regusz+1)
+			--n;
+		else{
+			while(n&7)
+				buf[Reguhdr+n++] = 0;
+			flag |= Lastpkt;
+		}
+		put2(buf+0, flag);
+		put4(buf+4, n);
+		wlsetvar(ctl, "clmload", buf, Reguhdr + n);
+		off += n;
+		flag &= ~Firstpkt;
+	}
+	poperror();
+	cclose(c);
+	free(buf);
+}
+
+static void
+fwload(Ctlr *ctl)
+{
+	uchar buf[4];
+	uint i, n;
+
+	i = 0;
+	while(firmware[i].chipid != ctl->chipid ||
+		   firmware[i].chiprev != ctl->chiprev){
+		if(++i == nelem(firmware)){
+			print("ether4330: no firmware for chipid %x (%d) chiprev %d\n",
+				ctl->chipid, ctl->chipid, ctl->chiprev);
+			error("no firmware");
+		}
+	}
+	ctl->regufile = firmware[i].regufile;
+	cfgw(Clkcsr, ReqALP);
+	while((cfgr(Clkcsr) & ALPavail) == 0)
+		microdelay(10);
+	memset(buf, 0, 4);
+	sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4);
+	if(FWDEBUG) print("firmware load...");
+	upload(ctl, firmware[i].fwfile, 0);
+	if(FWDEBUG) print("config load...");
+	n = upload(ctl, firmware[i].cfgfile, 1);
+	n /= 4;
+	n = (n & 0xFFFF) | (~n << 16);
+	put4(buf, n);
+	sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4);
+	if(ctl->armcore == ARMcr4){
+		sbwindow(ctl->sdregs);
+		cfgwritel(Fn1, ctl->sdregs + Intstatus, ~0);
+		if(ctl->resetvec.i != 0){
+			if(SBDEBUG) print("%ux\n", ctl->resetvec.i);
+			sbmem(1, ctl->resetvec.c, sizeof(ctl->resetvec.c), 0);
+		}
+		sbreset(ctl->armctl, Cr4Cpuhalt, 0);
+	}else
+		sbreset(ctl->armctl, 0, 0);
+}
+
+/*
+ * Communication of data and control packets
+ */
+
+void
+intwait(Ctlr *ctlr, int wait)
+{
+	ulong ints, mbox;
+	int i;
+
+	if(waserror())
+		return;
+	for(;;){
+		sdiocardintr(wait);
+		sbwindow(ctlr->sdregs);
+		i = sdiord(Fn0, Intpend);
+		if(i == 0){
+			tsleep(&up->sleep, return0, 0, 10);
+			continue;
+		}
+		ints = cfgreadl(Fn1, ctlr->sdregs + Intstatus);
+		cfgwritel(Fn1, ctlr->sdregs + Intstatus, ints);
+		if(0) print("INTS: (%x) %lux -> %lux\n", i, ints, cfgreadl(Fn1, ctlr->sdregs + Intstatus));
+		if(ints & MailboxInt){
+			mbox = cfgreadl(Fn1, ctlr->sdregs + Hostmboxdata);
+			cfgwritel(Fn1, ctlr->sdregs + Sbmbox, 2);	/* ack */
+			if(mbox & 0x8)
+				print("ether4330: firmware ready\n");
+		}
+		if(ints & FrameInt)
+			break;
+	}
+	poperror();
+}
+
+static Block*
+wlreadpkt(Ctlr *ctl)
+{
+	Block *b;
+	Sdpcm *p;
+	int len, lenck;
+
+	b = allocb(2048);
+	p = (Sdpcm*)b->wp;
+	qlock(&ctl->pktlock);
+	for(;;){
+		packetrw(0, b->wp, sizeof(*p));
+		len = p->len[0] | p->len[1]<<8;
+		if(len == 0){
+			freeb(b);
+			b = nil;
+			break;
+		}
+		lenck = p->lenck[0] | p->lenck[1]<<8;
+		if(lenck != (len ^ 0xFFFF) ||
+		   len < sizeof(*p) || len > 2048){
+			print("ether4330: wlreadpkt error len %.4x lenck %.4x\n", len, lenck);
+			cfgw(Framectl, Rfhalt);
+			while(cfgr(Rfrmcnt+1))
+				;
+			while(cfgr(Rfrmcnt))
+				;
+			continue;
+		}
+		if(len > sizeof(*p))
+			packetrw(0, b->wp + sizeof(*p), len - sizeof(*p));
+		b->wp += len;
+		break;
+	}
+	qunlock(&ctl->pktlock);
+	return b;
+}
+
+static void
+txstart(Ether *edev)
+{
+	Ctlr *ctl;
+	Sdpcm *p;
+	Block *b;
+	int len, off;
+
+	ctl = edev->ctlr;
+	if(!canqlock(&ctl->tlock))
+		return;
+	if(waserror()){
+		qunlock(&ctl->tlock);
+		return;
+	}
+	for(;;){
+		lock(&ctl->txwinlock);
+		if(ctl->txseq == ctl->txwindow){
+			//print("f");
+			unlock(&ctl->txwinlock);
+			break;
+		}
+		if(ctl->fcmask & 1<<2){
+			//print("x");
+			unlock(&ctl->txwinlock);
+			break;
+		}
+		unlock(&ctl->txwinlock);
+		b = qget(edev->oq);
+		if(b == nil)
+			break;
+		off = ((uintptr)b->rp & 3) + sizeof(Sdpcm);
+		b = padblock(b, off + 4);
+		len = BLEN(b);
+		p = (Sdpcm*)b->rp;
+		memset(p, 0, off);	/* TODO: refactor dup code */
+		put2(p->len, len);
+		put2(p->lenck, ~len);
+		p->chanflg = 2;
+		p->seq = ctl->txseq;
+		p->doffset = off;
+		put4(b->rp + off, 0x20);	/* BDC header */
+		if(iodebug) dump("send", b->rp, len);
+		qlock(&ctl->pktlock);
+		if(waserror()){
+			if(iodebug) print("halt frame %x %x\n", cfgr(Wfrmcnt+1), cfgr(Wfrmcnt+1));
+			cfgw(Framectl, Wfhalt);
+			while(cfgr(Wfrmcnt+1))
+				;
+			while(cfgr(Wfrmcnt))
+				;
+			qunlock(&ctl->pktlock);
+			nexterror();
+		}
+		packetrw(1, b->rp, len);
+		ctl->txseq++;
+		poperror();
+		qunlock(&ctl->pktlock);
+		freeb(b);
+	}
+	poperror();
+	qunlock(&ctl->tlock);
+}
+
+static void
+rproc(void *a)
+{
+	Ether *edev;
+	Ctlr *ctl;
+	Block *b;
+	Sdpcm *p;
+	Cmd *q;
+	int flowstart;
+	int bdc;
+
+	edev = a;
+	ctl = edev->ctlr;
+	flowstart = 0;
+	for(;;){
+		if(flowstart){
+			//print("F");
+			flowstart = 0;
+			txstart(edev);
+		}
+		b = wlreadpkt(ctl);
+		if(b == nil){
+			intwait(ctl, 1);
+			continue;
+		}
+		p = (Sdpcm*)b->rp;
+		if(p->window != ctl->txwindow || p->fcmask != ctl->fcmask){
+			lock(&ctl->txwinlock);
+			if(p->window != ctl->txwindow){
+				if(ctl->txseq == ctl->txwindow)
+					flowstart = 1;
+				ctl->txwindow = p->window;
+			}
+			if(p->fcmask != ctl->fcmask){
+				if((p->fcmask & 1<<2) == 0)
+					flowstart = 1;
+				ctl->fcmask = p->fcmask;
+			}
+			unlock(&ctl->txwinlock);
+		}
+		switch(p->chanflg & 0xF){
+		case 0:
+			if(iodebug) dump("rsp", b->rp, BLEN(b));
+			if(BLEN(b) < sizeof(Sdpcm) + sizeof(Cmd))
+				break;
+			q = (Cmd*)(b->rp + sizeof(*p));
+			if((q->id[0] | q->id[1]<<8) != ctl->reqid)
+				break;
+			ctl->rsp = b;
+			wakeup(&ctl->cmdr);
+			continue;
+		case 1:
+			if(iodebug) dump("event", b->rp, BLEN(b));
+			if(BLEN(b) > p->doffset + 4){
+				bdc = 4 + (b->rp[p->doffset + 3] << 2);
+				if(BLEN(b) > p->doffset + bdc){
+					b->rp += p->doffset + bdc;	/* skip BDC header */
+					bcmevent(ctl, b->rp, BLEN(b));
+					break;
+				}
+			}
+			if(iodebug && BLEN(b) != p->doffset)
+				print("short event %ld %d\n", BLEN(b), p->doffset);
+			break;
+		case 2:
+			if(iodebug) dump("packet", b->rp, BLEN(b));
+			if(BLEN(b) > p->doffset + 4){
+				bdc = 4 + (b->rp[p->doffset + 3] << 2);
+				if(BLEN(b) >= p->doffset + bdc + ETHERHDRSIZE){
+					b->rp += p->doffset + bdc;	/* skip BDC header */
+					etheriq(edev, b);
+					continue;
+				}
+			}
+			break;
+		default:
+			dump("ether4330: bad packet", b->rp, BLEN(b));
+			break;
+		}
+		freeb(b);
+	}
+}
+
+static void
+linkdown(Ctlr *ctl)
+{
+	Ether *edev;
+	Netfile *f;
+	int i;
+
+	edev = ctl->edev;
+	if(edev == nil || ctl->status != Connected)
+		return;
+	ctl->status = Disconnected;
+	/* send eof to aux/wpa */
+	for(i = 0; i < edev->nfile; i++){
+		f = edev->f[i];
+		if(f == nil || f->in == nil || f->inuse == 0 || f->type != 0x888e)
+			continue;
+		qwrite(f->in, 0, 0);
+	}
+}
+
+/*
+ * Command interface between host and firmware
+ */
+
+static char *eventnames[] = {
+	[0] = "set ssid",
+	[1] = "join",
+	[2] = "start",
+	[3] = "auth",
+	[4] = "auth ind",
+	[5] = "deauth",
+	[6] = "deauth ind",
+	[7] = "assoc",
+	[8] = "assoc ind",
+	[9] = "reassoc",
+	[10] = "reassoc ind",
+	[11] = "disassoc",
+	[12] = "disassoc ind",
+	[13] = "quiet start",
+	[14] = "quiet end",
+	[15] = "beacon rx",
+	[16] = "link",
+	[17] = "mic error",
+	[18] = "ndis link",
+	[19] = "roam",
+	[20] = "txfail",
+	[21] = "pmkid cache",
+	[22] = "retrograde tsf",
+	[23] = "prune",
+	[24] = "autoauth",
+	[25] = "eapol msg",
+	[26] = "scan complete",
+	[27] = "addts ind",
+	[28] = "delts ind",
+	[29] = "bcnsent ind",
+	[30] = "bcnrx msg",
+	[31] = "bcnlost msg",
+	[32] = "roam prep",
+	[33] = "pfn net found",
+	[34] = "pfn net lost",
+	[35] = "reset complete",
+	[36] = "join start",
+	[37] = "roam start",
+	[38] = "assoc start",
+	[39] = "ibss assoc",
+	[40] = "radio",
+	[41] = "psm watchdog",
+	[44] = "probreq msg",
+	[45] = "scan confirm ind",
+	[46] = "psk sup",
+	[47] = "country code changed",
+	[48] = "exceeded medium time",
+	[49] = "icv error",
+	[50] = "unicast decode error",
+	[51] = "multicast decode error",
+	[52] = "trace",
+	[53] = "bta hci event",
+	[54] = "if",
+	[55] = "p2p disc listen complete",
+	[56] = "rssi",
+	[57] = "pfn scan complete",
+	[58] = "extlog msg",
+	[59] = "action frame",
+	[60] = "action frame complete",
+	[61] = "pre assoc ind",
+	[62] = "pre reassoc ind",
+	[63] = "channel adopted",
+	[64] = "ap started",
+	[65] = "dfs ap stop",
+	[66] = "dfs ap resume",
+	[67] = "wai sta event",
+	[68] = "wai msg",
+	[69] = "escan result",
+	[70] = "action frame off chan complete",
+	[71] = "probresp msg",
+	[72] = "p2p probreq msg",
+	[73] = "dcs request",
+	[74] = "fifo credit map",
+	[75] = "action frame rx",
+	[76] = "wake event",
+	[77] = "rm complete",
+	[78] = "htsfsync",
+	[79] = "overlay req",
+	[80] = "csa complete ind",
+	[81] = "excess pm wake event",
+	[82] = "pfn scan none",
+	[83] = "pfn scan allgone",
+	[84] = "gtk plumbed",
+	[85] = "assoc ind ndis",
+	[86] = "reassoc ind ndis",
+	[87] = "assoc req ie",
+	[88] = "assoc resp ie",
+	[89] = "assoc recreated",
+	[90] = "action frame rx ndis",
+	[91] = "auth req",
+	[92] = "tdls peer event",
+	[127] = "bcmc credit support"
+};
+
+static char*
+evstring(uint event)
+{
+	static char buf[12];
+
+	if(event >= nelem(eventnames) || eventnames[event] == 0){
+		/* not reentrant but only called from one kproc */
+		snprint(buf, sizeof buf, "%d", event);
+		return buf;
+	}
+	return eventnames[event];
+}
+
+static void
+bcmevent(Ctlr *ctl, uchar *p, int len)
+{
+	int flags;
+	long event, status, reason;
+
+	if(len < ETHERHDRSIZE + 10 + 46)
+		return;
+	p += ETHERHDRSIZE + 10;			/* skip bcm_ether header */
+	len -= ETHERHDRSIZE + 10;
+	flags = nhgets(p + 2);
+	event = nhgets(p + 6);
+	status = nhgetl(p + 8);
+	reason = nhgetl(p + 12);
+	if(EVENTDEBUG)
+		print("ether4330: [%s] status %ld flags %#x reason %ld\n", 
+			evstring(event), status, flags, reason);
+	switch(event){
+	case 19:	/* E_ROAM */
+		if(status == 0)
+			break;
+	/* fall through */
+	case 0:		/* E_SET_SSID */
+		ctl->joinstatus = 1 + status;
+		wakeup(&ctl->joinr);
+		break;
+	case 16:	/* E_LINK */
+		if(flags&1)	/* link up */
+			break;
+	/* fall through */
+	case 5:		/* E_DEAUTH */
+	case 6:		/* E_DEAUTH_IND */
+	case 12:	/* E_DISASSOC_IND */
+		linkdown(ctl);
+		break;
+	case 26:	/* E_SCAN_COMPLETE */
+		break;
+	case 69:	/* E_ESCAN_RESULT */
+		wlscanresult(ctl->edev, p + 48, len - 48);
+		break;
+	default:
+		if(status){
+			if(!EVENTDEBUG)
+				print("ether4330: [%s] error status %ld flags %#x reason %ld\n",
+					evstring(event), status, flags, reason);
+			dump("event", p, len);
+		}
+	}
+}
+
+static int
+joindone(void *a)
+{
+	return ((Ctlr*)a)->joinstatus;
+}
+
+static int
+waitjoin(Ctlr *ctl)
+{
+	int n;
+
+	sleep(&ctl->joinr, joindone, ctl);
+	n = ctl->joinstatus;
+	ctl->joinstatus = 0;
+	return n - 1;
+}
+
+static int
+cmddone(void *a)
+{
+	return ((Ctlr*)a)->rsp != nil;
+}
+
+static void
+wlcmd(Ctlr *ctl, int write, int op, void *data, int dlen, void *res, int rlen)
+{
+	Block *b;
+	Sdpcm *p;
+	Cmd *q;
+	int len, tlen;
+
+	if(write)
+		tlen = dlen + rlen;
+	else
+		tlen = MAX(dlen, rlen);
+	len = sizeof(Sdpcm) + sizeof(Cmd) + tlen;
+	b = allocb(len);
+	qlock(&ctl->cmdlock);
+	if(waserror()){
+		freeb(b);
+		qunlock(&ctl->cmdlock);
+		nexterror();
+	}
+	memset(b->wp, 0, len);
+	qlock(&ctl->pktlock);
+	p = (Sdpcm*)b->wp;
+	put2(p->len, len);
+	put2(p->lenck, ~len);
+	p->seq = ctl->txseq;
+	p->doffset = sizeof(Sdpcm);
+	b->wp += sizeof(*p);
+	
+	q = (Cmd*)b->wp;
+	put4(q->cmd, op);
+	put4(q->len, tlen);
+	put2(q->flags, write? 2 : 0);
+	put2(q->id, ++ctl->reqid);
+	put4(q->status, 0);
+	b->wp += sizeof(*q);
+
+	if(dlen > 0)
+		memmove(b->wp, data, dlen);
+	if(write)
+		memmove(b->wp + dlen, res, rlen);
+	b->wp += tlen;
+
+	if(iodebug) dump("cmd", b->rp, len);
+	packetrw(1, b->rp, len);
+	ctl->txseq++;
+	qunlock(&ctl->pktlock);
+	freeb(b);
+	b = nil;
+	USED(b);
+	sleep(&ctl->cmdr, cmddone, ctl);
+	b = ctl->rsp;
+	ctl->rsp = nil;
+	assert(b != nil);
+	p = (Sdpcm*)b->rp;
+	q = (Cmd*)(b->rp + p->doffset);
+	if(q->status[0] | q->status[1] | q->status[2] | q->status[3]){
+		print("ether4330: cmd %d error status %ld\n", op, get4(q->status));
+		dump("ether4330: cmd error", b->rp, BLEN(b));
+		error("wlcmd error");
+	}
+	if(!write)
+		memmove(res, q + 1, rlen);
+	freeb(b);
+	qunlock(&ctl->cmdlock);
+	poperror();
+}
+
+static void
+wlcmdint(Ctlr *ctl, int op, int val)
+{
+	uchar buf[4];
+
+	put4(buf, val);
+	wlcmd(ctl, 1, op, buf, 4, nil, 0);
+}
+
+static void
+wlgetvar(Ctlr *ctl, char *name, void *val, int len)
+{
+	wlcmd(ctl, 0, GetVar, name, strlen(name) + 1, val, len);
+}
+
+static void
+wlsetvar(Ctlr *ctl, char *name, void *val, int len)
+{
+	if(VARDEBUG){
+		char buf[32];
+		snprint(buf, sizeof buf, "wlsetvar %s:", name);
+		dump(buf, val, len);
+	}
+	wlcmd(ctl, 1, SetVar, name, strlen(name) + 1, val, len);
+}
+
+static void
+wlsetint(Ctlr *ctl, char *name, int val)
+{
+	uchar buf[4];
+
+	put4(buf, val);
+	wlsetvar(ctl, name, buf, 4);
+}
+
+static void
+wlwepkey(Ctlr *ctl, int i)
+{
+	uchar params[164];
+	uchar *p;
+
+	memset(params, 0, sizeof params);
+	p = params;
+	p = put4(p, i);		/* index */
+	p = put4(p, ctl->keys[i].len);
+	memmove(p, ctl->keys[i].dat, ctl->keys[i].len);
+	p += 32 + 18*4;		/* keydata, pad */
+	if(ctl->keys[i].len == WMinKeyLen)
+		p = put4(p, 1);		/* algo = WEP1 */
+	else
+		p = put4(p, 3);		/* algo = WEP128 */
+	put4(p, 2);		/* flags = Primarykey */
+
+	wlsetvar(ctl, "wsec_key", params, sizeof params);
+}
+
+static void
+memreverse(char *dst, char *src, int len)
+{
+	src += len;
+	while(len-- > 0)
+		*dst++ = *--src;
+}
+
+static void
+wlwpakey(Ctlr *ctl, int id, uvlong iv, uchar *ea)
+{
+	uchar params[164];
+	uchar *p;
+	int pairwise;
+
+	if(id == CMrxkey)
+		return;
+	pairwise = (id == CMrxkey || id == CMtxkey);
+	memset(params, 0, sizeof params);
+	p = params;
+	if(pairwise)
+		p = put4(p, 0);
+	else
+		p = put4(p, id - CMrxkey0);	/* group key id */
+	p = put4(p, ctl->keys[0].len);
+	memmove((char*)p,  ctl->keys[0].dat, ctl->keys[0].len);
+	p += 32 + 18*4;		/* keydata, pad */
+	if(ctl->cryptotype == Wpa)
+		p = put4(p, 2);	/* algo = TKIP */
+	else
+		p = put4(p, 4);	/* algo = AES_CCM */
+	if(pairwise)
+		p = put4(p, 0);
+	else
+		p = put4(p, 2);		/* flags = Primarykey */
+	p += 3*4;
+	p = put4(p, 0); //pairwise);		/* iv initialised */
+	p += 4;
+	p = put4(p, iv>>16);	/* iv high */
+	p = put2(p, iv&0xFFFF);	/* iv low */
+	p += 2 + 2*4;		/* align, pad */
+	if(pairwise)
+		memmove(p, ea, Eaddrlen);
+
+	wlsetvar(ctl, "wsec_key", params, sizeof params);
+}
+
+static void
+wljoin(Ctlr *ctl, char *ssid, int chan)
+{
+	uchar params[72];
+	uchar *p;
+	int n;
+
+	if(chan != 0)
+		chan |= 0x2b00;		/* 20Mhz channel width */
+	p = params;
+	n = strlen(ssid);
+	n = MIN(n, 32);
+	p = put4(p, n);
+	memmove(p, ssid, n);
+	memset(p + n, 0, 32 - n);
+	p += 32;
+	p = put4(p, 0xff);	/* scan type */
+	if(chan != 0){
+		p = put4(p, 2);		/* num probes */
+		p = put4(p, 120);	/* active time */
+		p = put4(p, 390);	/* passive time */
+	}else{
+		p = put4(p, -1);	/* num probes */
+		p = put4(p, -1);	/* active time */
+		p = put4(p, -1);	/* passive time */
+	}
+	p = put4(p, -1);	/* home time */
+	memset(p, 0xFF, Eaddrlen);	/* bssid */
+	p += Eaddrlen;
+	p = put2(p, 0);		/* pad */
+	if(chan != 0){
+		p = put4(p, 1);		/* num chans */
+		p = put2(p, chan);	/* chan spec */
+		p = put2(p, 0);		/* pad */
+		assert(p == params + sizeof(params));
+	}else{
+		p = put4(p, 0);		/* num chans */
+		assert(p == params + sizeof(params) - 4);
+	}
+
+	wlsetvar(ctl, "join", params, chan? sizeof params : sizeof params - 4);
+	ctl->status = Connecting;
+	switch(waitjoin(ctl)){
+		case 0:
+			ctl->status = Connected;
+			break;
+		case 3:
+			ctl->status = Disconnected;
+			error("wifi join: network not found");
+		case 1:
+			ctl->status = Disconnected;
+			error("wifi join: failed");
+		default:
+			ctl->status = Disconnected;
+			error("wifi join: error");
+	}
+}
+
+static void
+wlscanstart(Ctlr *ctl)
+{
+	/* version[4] action[2] sync_id[2] ssidlen[4] ssid[32] bssid[6] bss_type[1]
+		scan_type[1] nprobes[4] active_time[4] passive_time[4] home_time[4]
+		nchans[2] nssids[2] chans[nchans][2] ssids[nssids][32] */
+	/* hack - this is only correct on a little-endian cpu */
+	static uchar params[4+2+2+4+32+6+1+1+4*4+2+2+14*2+32+4] = {
+		1,0,0,0,
+		1,0,
+		0x34,0x12,
+		0,0,0,0,
+		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+		0xff,0xff,0xff,0xff,0xff,0xff,
+		2,
+		0,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		0xff,0xff,0xff,0xff,
+		14,0,
+		1,0,
+		0x01,0x2b,0x02,0x2b,0x03,0x2b,0x04,0x2b,0x05,0x2e,0x06,0x2e,0x07,0x2e,
+		0x08,0x2b,0x09,0x2b,0x0a,0x2b,0x0b,0x2b,0x0c,0x2b,0x0d,0x2b,0x0e,0x2b,
+		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	};
+
+	wlcmdint(ctl, 49, 0);	/* PASSIVE_SCAN */
+	wlsetvar(ctl, "escan", params, sizeof params);
+}
+
+static uchar*
+gettlv(uchar *p, uchar *ep, int tag)
+{
+	int len;
+
+	while(p + 1 < ep){
+		len = p[1];
+		if(p + 2 + len > ep)
+			return nil;
+		if(p[0] == tag)
+			return p;
+		p += 2 + len;
+	}
+	return nil;
+}
+
+static void
+addscan(Block *bp, uchar *p, int len)
+{
+	char bssid[24];
+	char *auth, *auth2;
+	uchar *t, *et;
+	int ielen;
+	static uchar wpaie1[4] = { 0x00, 0x50, 0xf2, 0x01 };
+
+	snprint(bssid, sizeof bssid, ";bssid=%E", p + 8);
+	if(strstr((char*)bp->rp, bssid) != nil)
+		return;
+	bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim,
+		"ssid=%.*s%s;signal=%d;noise=%d;chan=%d",
+		p[18], (char*)p+19, bssid,
+		(short)get2(p+78), (signed char)p[80],
+		get2(p+72) & 0xF);
+	auth = auth2 = "";
+	if(get2(p + 16) & 0x10)
+		auth = ";wep";
+	ielen = get4(p + 0x78);
+	if(ielen > 0){
+		t = p + get4(p + 0x74);
+		et = t + ielen;
+		if(et > p + len)
+			return;
+		if(gettlv(t, et, 0x30) != nil){
+			auth = "";
+			auth2 = ";wpa2";
+		}
+		while((t = gettlv(t, et, 0xdd)) != nil){
+			if(t[1] > 4 && memcmp(t+2, wpaie1, 4) == 0){
+				auth = ";wpa";
+				break;
+			}
+			t += 2 + t[1];
+		}
+	}
+	bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim,
+		"%s%s\n", auth, auth2);
+}
+
+
+static void
+wlscanresult(Ether *edev, uchar *p, int len)
+{
+	Ctlr *ctlr;
+	Netfile **ep, *f, **fp;
+	Block *bp;
+	int nbss, i;
+
+	ctlr = edev->ctlr;
+	if(get4(p) > len)
+		return;
+	/* TODO: more syntax checking */
+	bp = ctlr->scanb;
+	if(bp == nil)
+		ctlr->scanb = bp = allocb(8192);
+	nbss = get2(p+10);
+	p += 12;
+	len -= 12;
+	if(0) dump("SCAN", p, len);
+	if(nbss){
+		addscan(bp, p, len);
+		return;
+	}
+	i = edev->scan;
+	ep = &edev->f[Ntypes];
+	for(fp = edev->f; fp < ep && i > 0; fp++){
+		f = *fp;
+		if(f == nil || f->scan == 0)
+			continue;
+		if(i == 1)
+			qpass(f->in, bp);
+		else
+			qpass(f->in, copyblock(bp, BLEN(bp)));
+		i--;
+	}
+	if(i)
+		freeb(bp);
+	ctlr->scanb = nil;
+}
+
+static void
+lproc(void *a)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+	int secs;
+
+	edev = a;
+	ctlr = edev->ctlr;
+	secs = 0;
+	for(;;){
+		tsleep(&up->sleep, return0, 0, 1000);
+		if(ctlr->scansecs){
+			if(secs == 0){
+				if(waserror())
+					ctlr->scansecs = 0;
+				else{
+					wlscanstart(ctlr);
+					poperror();
+				}
+				secs = ctlr->scansecs;
+			}
+			--secs;
+		}else
+			secs = 0;
+	}
+}
+
+static void
+wlinit(Ether *edev, Ctlr *ctlr)
+{
+	uchar ea[Eaddrlen];
+	uchar eventmask[16];
+	char version[128];
+	char *p;
+	static uchar keepalive[12] = {1, 0, 11, 0, 0xd8, 0xd6, 0, 0, 0, 0, 0, 0};
+
+	wlgetvar(ctlr, "cur_etheraddr", ea, Eaddrlen);
+	memmove(edev->ea, ea, Eaddrlen);
+	memmove(edev->addr, ea, Eaddrlen);
+	print("ether4330: addr %E\n", edev->ea);
+	wlsetint(ctlr, "assoc_listen", 10);
+	if(ctlr->chipid == 43430 || ctlr->chipid == 0x4345)
+		wlcmdint(ctlr, 0x56, 0);	/* powersave off */
+	else
+		wlcmdint(ctlr, 0x56, 2);	/* powersave FAST */
+	wlsetint(ctlr, "bus:txglom", 0);
+	wlsetint(ctlr, "bcn_timeout", 10);
+	wlsetint(ctlr, "assoc_retry_max", 3);
+	if(ctlr->chipid == 0x4330){
+		wlsetint(ctlr, "btc_wire", 4);
+		wlsetint(ctlr, "btc_mode", 1);
+		wlsetvar(ctlr, "mkeep_alive", keepalive, 11);
+	}
+	memset(eventmask, 0xFF, sizeof eventmask);
+#define ENABLE(n)	eventmask[n/8] |= 1<<(n%8)
+#define DISABLE(n)	eventmask[n/8] &= ~(1<<(n%8))
+	DISABLE(40);	/* E_RADIO */
+	DISABLE(44);	/* E_PROBREQ_MSG */
+	DISABLE(54);	/* E_IF */
+	DISABLE(71);	/* E_PROBRESP_MSG */
+	DISABLE(20);	/* E_TXFAIL */
+	DISABLE(124);	/* ? */
+	wlsetvar(ctlr, "event_msgs", eventmask, sizeof eventmask);
+	wlcmdint(ctlr, 0xb9, 0x28);	/* SET_SCAN_CHANNEL_TIME */
+	wlcmdint(ctlr, 0xbb, 0x28);	/* SET_SCAN_UNASSOC_TIME */
+	wlcmdint(ctlr, 0x102, 0x82);	/* SET_SCAN_PASSIVE_TIME */
+	wlcmdint(ctlr, 2, 0);		/* UP */
+	memset(version, 0, sizeof version);
+	wlgetvar(ctlr, "ver", version, sizeof version - 1);
+	if((p = strchr(version, '\n')) != nil)
+		*p = '\0';
+	if(0) print("ether4330: %s\n", version);
+	wlsetint(ctlr, "roam_off", 1);
+	wlcmdint(ctlr, 0x14, 1);	/* SET_INFRA 1 */
+	wlcmdint(ctlr, 10, 0);		/* SET_PROMISC */
+	//wlcmdint(ctlr, 0x8e, 0);	/* SET_BAND 0 */
+	//wlsetint(ctlr, "wsec", 1);
+	wlcmdint(ctlr, 2, 1);		/* UP */
+	ctlr->keys[0].len = WMinKeyLen;
+	//wlwepkey(ctlr, 0);
+}
+
+/*
+ * Plan 9 driver interface
+ */
+
+static long
+etherbcmifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	Ctlr *ctlr;
+	char *p;
+	int l;
+	static char *cryptoname[4] = {
+		[0]	"off",
+		[Wep]	"wep",
+		[Wpa]	"wpa",
+		[Wpa2]	"wpa2",
+	};
+	/* these strings are known by aux/wpa */
+	static char* connectstate[] = {
+		[Disconnected]	= "unassociated",
+		[Connecting] = "connecting",
+		[Connected] = "associated",
+	};
+
+	ctlr = edev->ctlr;
+	if(ctlr == nil)
+		return 0;
+	p = malloc(READSTR);
+	l = 0;
+
+	l += snprint(p+l, READSTR-l, "channel: %d\n", ctlr->chanid);
+	l += snprint(p+l, READSTR-l, "essid: %s\n", ctlr->essid);
+	l += snprint(p+l, READSTR-l, "crypt: %s\n", cryptoname[ctlr->cryptotype]);
+	l += snprint(p+l, READSTR-l, "oq: %d\n", qlen(edev->oq));
+	l += snprint(p+l, READSTR-l, "txwin: %d\n", ctlr->txwindow);
+	l += snprint(p+l, READSTR-l, "txseq: %d\n", ctlr->txseq);
+	l += snprint(p+l, READSTR-l, "status: %s\n", connectstate[ctlr->status]);
+	USED(l);
+	n = readstr(offset, a, n, p);
+	free(p);
+	return n;
+}
+
+static void
+etherbcmtransmit(Ether *edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if(ctlr == nil)
+		return;
+	txstart(edev);
+}
+
+static int
+parsehex(char *buf, int buflen, char *a)
+{
+	int i, k, n;
+
+	k = 0;
+	for(i = 0;k < buflen && *a; i++){
+		if(*a >= '0' && *a <= '9')
+			n = *a++ - '0';
+		else if(*a >= 'a' && *a <= 'f')
+			n = *a++ - 'a' + 10;
+		else if(*a >= 'A' && *a <= 'F')
+			n = *a++ - 'A' + 10;
+		else
+			break;
+
+		if(i & 1){
+			buf[k] |= n;
+			k++;
+		}
+		else
+			buf[k] = n<<4;
+	}
+	if(i & 1)
+		return -1;
+	return k;
+}
+
+static int
+wepparsekey(WKey* key, char* a) 
+{
+	int i, k, len, n;
+	char buf[WMaxKeyLen];
+
+	len = strlen(a);
+	if(len == WMinKeyLen || len == WMaxKeyLen){
+		memset(key->dat, 0, sizeof(key->dat));
+		memmove(key->dat, a, len);
+		key->len = len;
+
+		return 0;
+	}
+	else if(len == WMinKeyLen*2 || len == WMaxKeyLen*2){
+		k = 0;
+		for(i = 0; i < len; i++){
+			if(*a >= '0' && *a <= '9')
+				n = *a++ - '0';
+			else if(*a >= 'a' && *a <= 'f')
+				n = *a++ - 'a' + 10;
+			else if(*a >= 'A' && *a <= 'F')
+				n = *a++ - 'A' + 10;
+			else
+				return -1;
+	
+			if(i & 1){
+				buf[k] |= n;
+				k++;
+			}
+			else
+				buf[k] = n<<4;
+		}
+
+		memset(key->dat, 0, sizeof(key->dat));
+		memmove(key->dat, buf, k);
+		key->len = k;
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static int
+wpaparsekey(WKey *key, uvlong *ivp, char *a)
+{
+	int len;
+	char *e;
+
+	if(cistrncmp(a, "tkip:", 5) == 0 || cistrncmp(a, "ccmp:", 5) == 0)
+		a += 5;
+	else
+		return 1;
+	len = parsehex(key->dat, sizeof(key->dat), a);
+	if(len <= 0)
+		return 1;
+	key->len = len;
+	a += 2*len;
+	if(*a++ != '@')
+		return 1;
+	*ivp = strtoull(a, &e, 16);
+	if(e == a)
+		return -1;
+	return 0;
+}
+
+static void
+setauth(Ctlr *ctlr, Cmdbuf *cb, char *a)
+{
+	uchar wpaie[32];
+	int i;
+
+	i = parsehex((char*)wpaie, sizeof wpaie, a);
+	if(i < 2 || i != wpaie[1] + 2)
+		cmderror(cb, "bad wpa ie syntax");
+	if(wpaie[0] == 0xdd)
+		ctlr->cryptotype = Wpa;
+	else if(wpaie[0] == 0x30)
+		ctlr->cryptotype = Wpa2;
+	else
+		cmderror(cb, "bad wpa ie");
+	wlsetvar(ctlr, "wpaie", wpaie, i);
+	if(ctlr->cryptotype == Wpa){
+		wlsetint(ctlr, "wpa_auth", 4|2);	/* auth_psk | auth_unspecified */
+		wlsetint(ctlr, "auth", 0);
+		wlsetint(ctlr, "wsec", 2);		/* tkip */
+		wlsetint(ctlr, "wpa_auth", 4);		/* auth_psk */
+	}else{
+		wlsetint(ctlr, "wpa_auth", 0x80|0x40);	/* auth_psk | auth_unspecified */
+		wlsetint(ctlr, "auth", 0);
+		wlsetint(ctlr, "wsec", 4);		/* aes */
+		wlsetint(ctlr, "wpa_auth", 0x80);	/* auth_psk */
+	}
+}
+
+static int
+setcrypt(Ctlr *ctlr, Cmdbuf*, char *a)
+{
+	if(cistrcmp(a, "wep") == 0 || cistrcmp(a, "on") == 0)
+		ctlr->cryptotype = Wep;
+	else if(cistrcmp(a, "off") == 0 || cistrcmp(a, "none") == 0)
+		ctlr->cryptotype = 0;
+	else
+		return 0;
+	wlsetint(ctlr, "auth", ctlr->cryptotype);
+	return 1;
+}
+
+static long
+etherbcmctl(Ether* edev, void* buf, long n)
+{
+	Ctlr *ctlr;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+	uchar ea[Eaddrlen];
+	uvlong iv;
+	int i;
+
+	if((ctlr = edev->ctlr) == nil)
+		error(Enonexist);
+	USED(ctlr);
+
+	cb = parsecmd(buf, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, cmds, nelem(cmds));
+	switch(ct->index){
+	case CMauth:
+		setauth(ctlr, cb, cb->f[1]);
+		if(ctlr->essid[0])
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		break;
+	case CMchannel:
+		if((i = atoi(cb->f[1])) < 0 || i > 16)
+			cmderror(cb, "bad channel number");
+		//wlcmdint(ctlr, 30, i);	/* SET_CHANNEL */
+		ctlr->chanid = i;
+		break;
+	case CMcrypt:
+		if(setcrypt(ctlr, cb, cb->f[1])){
+			if(ctlr->essid[0])
+				wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		}else
+			cmderror(cb, "bad crypt type");
+		break;
+	case CMessid:
+		if(cistrcmp(cb->f[1], "default") == 0)
+			memset(ctlr->essid, 0, sizeof(ctlr->essid));
+		else{
+			strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid) - 1);
+			ctlr->essid[sizeof(ctlr->essid) - 1] = '\0';
+		}
+		if(!waserror()){
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+			poperror();
+		}
+		break;
+	case CMjoin:	/* join essid channel wep|on|off|wpakey */
+		if(strcmp(cb->f[1], "") != 0){	/* empty string for no change */
+			if(cistrcmp(cb->f[1], "default") != 0){
+				strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid)-1);
+				ctlr->essid[sizeof(ctlr->essid)-1] = 0;
+			}else
+				memset(ctlr->essid, 0, sizeof(ctlr->essid));
+		}else if(ctlr->essid[0] == 0)
+			cmderror(cb, "essid not set");
+		if((i = atoi(cb->f[2])) >= 0 && i <= 16)
+			ctlr->chanid = i;
+		else
+			cmderror(cb, "bad channel number");
+		if(!setcrypt(ctlr, cb, cb->f[3]))
+			setauth(ctlr, cb, cb->f[3]);
+		if(ctlr->essid[0])
+			wljoin(ctlr, ctlr->essid, ctlr->chanid);
+		break;
+	case CMkey1:
+	case CMkey2:
+	case CMkey3:
+	case CMkey4:
+		i = ct->index - CMkey1;
+		if(wepparsekey(&ctlr->keys[i], cb->f[1]))
+			cmderror(cb, "bad WEP key syntax");
+		wlsetint(ctlr, "wsec", 1);	/* wep enabled */
+		wlwepkey(ctlr, i);
+		break;
+	case CMrxkey:
+	case CMrxkey0:
+	case CMrxkey1:
+	case CMrxkey2:
+	case CMrxkey3:
+	case CMtxkey:
+		if(parseether(ea, cb->f[1]) < 0)
+			cmderror(cb, "bad ether addr");
+		if(wpaparsekey(&ctlr->keys[0], &iv, cb->f[2]))
+			cmderror(cb, "bad wpa key");
+		wlwpakey(ctlr, ct->index, iv, ea);
+		break;
+	case CMdebug:
+		iodebug = atoi(cb->f[1]);
+		break;
+	}
+	poperror();
+	free(cb);
+	return n;
+}
+
+static void
+etherbcmscan(void *a, uint secs)
+{
+	Ether* edev;
+	Ctlr* ctlr;
+
+	edev = a;
+	ctlr = edev->ctlr;
+	ctlr->scansecs = secs;
+}
+
+static void
+etherbcmattach(Ether* edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(waserror()){
+		print("ether4330: attach failed: %s\n", up->errstr);
+		qunlock(&ctlr->alock);
+		nexterror();
+	}
+	if(ctlr->edev == nil){
+		if(ctlr->chipid == 0){
+			sdioinit();
+			sbinit(ctlr);
+		}
+		fwload(ctlr);
+		sbenable(ctlr);
+		kproc("wifireader", rproc, edev);
+		kproc("wifitimer", lproc, edev);
+		if(ctlr->regufile)
+			reguload(ctlr, ctlr->regufile);
+		wlinit(edev, ctlr);
+		ctlr->edev = edev;
+	}
+	qunlock(&ctlr->alock);
+	poperror();
+}
+
+static void
+etherbcmshutdown(Ether*)
+{
+	sdioreset();
+}
+
+
+static int
+etherbcmpnp(Ether* edev)
+{
+	Ctlr *ctlr;
+
+	static int done = 0;
+	if(done)
+		return -1;
+	done = 1;
+
+	ctlr = malloc(sizeof(Ctlr));
+	ctlr->chanid = Wifichan;
+	edev->ctlr = ctlr;
+	edev->attach = etherbcmattach;
+	edev->transmit = etherbcmtransmit;
+	edev->ifstat = etherbcmifstat;
+	edev->ctl = etherbcmctl;
+	edev->scanbs = etherbcmscan;
+	edev->shutdown = etherbcmshutdown;
+	edev->arg = edev;
+
+	return 0;
+}
+
+void
+ether4330link(void)
+{
+	addethercard("4330", etherbcmpnp);
+}
diff -r 44eae9a7e98b sys/src/9/bcm/mkfile
--- a/sys/src/9/bcm/mkfile	Sun Oct 11 14:59:49 2020 +0200
+++ b/sys/src/9/bcm/mkfile	Tue Oct 13 05:09:49 2020 -0700
@@ -1,5 +1,5 @@
 CONF=pi2
-CONFLIST=pi pi2
+CONFLIST=pi pi2 pi2wifi
 CRAPLIST=pif picpuf
 EXTRACOPIES=
 
diff -r 44eae9a7e98b sys/src/9/bcm/pi2wifi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/pi2wifi	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,60 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	dup
+	arch
+	ssl
+	tls
+	cap
+	fs
+	ether	netif
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
+	draw	screen swcursor
+	mouse	mouse
+	uart	gpio
+	gpio	gpio
+	sd
+	usb
+
+link
+	loopbackmedium
+	ethermedium
+	archbcm2
+	usbdwc
+	sdhost
+	ether4330	emmc
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	ipmux
+
+misc
+	armv7
+	uartmini
+	sdmmc	emmc
+	dma
+	vcore
+	vfp3	coproc
+	irq
+
+port
+	int cpuserver = 0;
+
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
+	boot
+	/sys/lib/firmware/brcmfmac43430-sdio.bin
+	/sys/lib/firmware/brcmfmac43430-sdio.txt
diff -r 44eae9a7e98b sys/src/9/bcm/sdhost.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/sdhost.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,393 @@
+/*
+ * bcm2835 sdhost controller
+ *
+ * Copyright © 2016 Richard Miller <r.miller@acm.org>
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/sd.h"
+
+extern SDio *sdcardlink;
+
+#define SDHOSTREGS	(VIRTIO+0x202000)
+
+#define FIELD(v, o, w)	(((v) & ((1<<(w))-1))<<(o))
+#define FCLR(d, o, w)	((d) & ~(((1<<(w))-1)<<(o)))
+#define FINS(d, o, w, v) (FCLR((d), (o), (w))|FIELD((v), (o), (w)))
+
+enum {
+	Extfreq		= 250*Mhz,	/* guess external clock frequency if vcore doesn't say */
+	Initfreq	= 400000,	/* initialisation frequency for MMC */
+	SDfreq		= 25*Mhz,	/* standard SD frequency */
+	SDfreqhs	= 50*Mhz,	/* SD high speed frequency */
+	FifoDepth	= 4,		/* "Limit fifo usage due to silicon bug" (linux driver) */
+
+	GoIdle		= 0,		/* mmc/sdio go idle state */
+	MMCSelect	= 7,		/* mmc/sd card select command */
+	Setbuswidth	= 6,		/* mmc/sd set bus width command */
+	Switchfunc	= 6,		/* mmc/sd switch function command */
+	Stoptransmission = 12,	/* mmc/sd stop transmission command */
+	Appcmd = 55,			/* mmc/sd application command prefix */
+};
+
+enum {
+	/* Controller registers */
+	Cmd		= 0x00>>2,
+	Arg		= 0x04>>2,
+	Timeout		= 0x08>>2,
+	Clkdiv		= 0x0c>>2,
+
+	Resp0		= 0x10>>2,
+	Resp1		= 0x14>>2,
+	Resp2		= 0x18>>2,
+	Resp3		= 0x1c>>2,
+
+	Status		= 0x20>>2,
+	Poweron		= 0x30>>2,
+	Dbgmode		= 0x34>>2,
+	Hconfig		= 0x38>>2,
+	Blksize		= 0x3c>>2,
+	Data		= 0x40>>2,
+	Blkcount	= 0x50>>2,
+
+	/* Cmd */
+	Start		= 1<<15,
+	Failed		= 1<<14,
+	Respmask	= 7<<9,
+	Resp48busy	= 4<<9,
+	Respnone	= 2<<9,
+	Resp136		= 1<<9,
+	Resp48		= 0<<9,
+	Host2card	= 1<<7,
+	Card2host	= 1<<6,
+
+	/* Status */
+	Busyint		= 1<<10,
+	Blkint		= 1<<9,
+	Sdioint		= 1<<8,
+	Rewtimeout	= 1<<7,
+	Cmdtimeout	= 1<<6,
+	Crcerror	= 3<<4,
+	Fifoerror	= 1<<3,
+	Dataflag	= 1<<0,
+	Intstatus	= (Busyint|Blkint|Sdioint|Dataflag),
+	Errstatus	= (Rewtimeout|Cmdtimeout|Crcerror|Fifoerror),
+
+	/* Hconfig */
+	BusyintEn	= 1<<10,
+	BlkintEn	= 1<<8,
+	SdiointEn	= 1<<5,
+	DataintEn	= 1<<4,
+	Slowcard	= 1<<3,
+	Extbus4		= 1<<2,
+	Intbuswide	= 1<<1,
+	Cmdrelease	= 1<<0,
+};
+
+static int cmdinfo[64] = {
+[0]  Start | Respnone,
+[2]  Start | Resp136,
+[3]  Start | Resp48,
+[5]  Start | Resp48,
+[6]  Start | Resp48,
+[63]  Start | Resp48 | Card2host,
+[7]  Start | Resp48busy,
+[8]  Start | Resp48,
+[9]  Start | Resp136,
+[11] Start | Resp48,
+[12] Start | Resp48busy,
+[13] Start | Resp48,
+[16] Start | Resp48,
+[17] Start | Resp48 | Card2host,
+[18] Start | Resp48 | Card2host,
+[24] Start | Resp48 | Host2card,
+[25] Start | Resp48 | Host2card,
+[41] Start | Resp48,
+[52] Start | Resp48,
+[53] Start | Resp48,
+[55] Start | Resp48,
+};
+
+typedef struct Ctlr Ctlr;
+
+struct Ctlr {
+	Rendez	r;
+	int	bcount;
+	int	done;
+	ulong	extclk;
+	int	appcmd;
+};
+
+static Ctlr sdhost;
+
+static void sdhostinterrupt(Ureg*, void*);
+
+static void
+WR(int reg, u32int val)
+{
+	u32int *r = (u32int*)SDHOSTREGS;
+
+	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
+	r[reg] = val;
+}
+
+static int
+datadone(void*)
+{
+	return sdhost.done;
+}
+
+static void
+sdhostclock(uint freq)
+{
+	uint div;
+
+	div = sdhost.extclk / freq;
+	if(sdhost.extclk / freq > freq)
+		div++;
+	if(div < 2)
+		div = 2;
+	WR(Clkdiv, div - 2);
+}
+
+static int
+sdhostinit(void)
+{
+	u32int *r;
+	ulong clk;
+	int i;
+
+	/* disconnect emmc and connect sdhost to SD card gpio pins */
+	for(i = 48; i <= 53; i++)
+		gpiosel(i, Alt0);
+	clk = getclkrate(ClkCore);
+	if(clk == 0){
+		clk = Extfreq;
+		print("sdhost: assuming external clock %lud Mhz\n", clk/1000000);
+	}
+	sdhost.extclk = clk;
+	sdhostclock(Initfreq);
+	r = (u32int*)SDHOSTREGS;
+	WR(Poweron, 0);
+	WR(Timeout, 0xF00000);
+	WR(Dbgmode, FINS(r[Dbgmode], 9, 10, (FifoDepth | FifoDepth<<5)));
+	return 0;
+}
+
+static int
+sdhostinquiry(char *inquiry, int inqlen)
+{
+	return snprint(inquiry, inqlen, "BCM SDHost Controller");
+}
+
+static void
+sdhostenable(void)
+{
+	u32int *r;
+
+	r = (u32int*)SDHOSTREGS;
+	USED(r);
+	WR(Poweron, 1);
+	delay(10);
+	WR(Hconfig, Intbuswide | Slowcard | BusyintEn);
+	WR(Clkdiv, 0x7FF);
+	intrenable(IRQsdhost, sdhostinterrupt, nil, 0, "sdhost");
+}
+
+static int
+sdhostcmd(u32int cmd, u32int arg, u32int *resp)
+{
+	u32int *r;
+	u32int c;
+	int i;
+	ulong now;
+
+	r = (u32int*)SDHOSTREGS;
+	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
+	c = cmd | cmdinfo[cmd];
+	/*
+	 * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix
+	 */
+	if(cmd == Switchfunc && !sdhost.appcmd)
+		c |= Card2host;
+	if(cmd != Stoptransmission && (i = (r[Dbgmode] & 0xF)) > 2){
+		print("sdhost: previous command stuck: Dbg=%d Cmd=%ux\n", i, r[Cmd]);
+		error(Eio);
+	}
+	/*
+	 * GoIdle indicates new card insertion: reset bus width & speed
+	 */
+	if(cmd == GoIdle){
+		WR(Hconfig, r[Hconfig] & ~Extbus4);
+		sdhostclock(Initfreq);
+	}
+
+	if(r[Status] & (Errstatus|Dataflag))
+		WR(Status, Errstatus|Dataflag);
+	sdhost.done = 0;
+	WR(Arg, arg);
+	WR(Cmd, c);
+	now = m->ticks;
+	while(((i=r[Cmd])&(Start|Failed)) == Start)
+		if(m->ticks-now > HZ)
+			break;
+	if((i&(Start|Failed)) != 0){
+		if(r[Status] != Cmdtimeout)
+			print("sdhost: cmd %ux arg %ux error stat %ux\n", i, arg, r[Status]);
+		i = r[Status];
+		WR(Status, i);
+		error(Eio);
+	}
+	switch(c & Respmask){
+	case Resp136:
+		resp[0] = r[Resp0];
+		resp[1] = r[Resp1];
+		resp[2] = r[Resp2];
+		resp[3] = r[Resp3];
+		break;
+	case Resp48:
+	case Resp48busy:
+		resp[0] = r[Resp0];
+		break;
+	case Respnone:
+		resp[0] = 0;
+		break;
+	}
+	if((c & Respmask) == Resp48busy){
+		tsleep(&sdhost.r, datadone, 0, 3000);
+	}
+	switch(cmd) {
+	case MMCSelect:
+		/*
+		 * Once card is selected, use faster clock
+		 */
+		delay(1);
+		sdhostclock(SDfreq);
+		delay(1);
+		break;
+	case Setbuswidth:
+		if(sdhost.appcmd){
+			/*
+			 * If card bus width changes, change host bus width
+			 */
+			switch(arg){
+			case 0:
+				WR(Hconfig, r[Hconfig] & ~Extbus4);
+				break;
+			case 2:
+				WR(Hconfig, r[Hconfig] | Extbus4);
+				break;
+			}
+		}else{
+			/*
+			 * If card switched into high speed mode, increase clock speed
+			 */
+			if((arg&0x8000000F) == 0x80000001){
+				delay(1);
+				sdhostclock(SDfreqhs);
+				delay(1);
+			}
+		}
+		break;
+	}
+	sdhost.appcmd = (cmd == Appcmd);
+	return 0;
+}
+
+void
+sdhostiosetup(int write, void *buf, int bsize, int bcount)
+{
+	USED(write);
+	USED(buf);
+
+	sdhost.bcount = bcount;
+	WR(Blksize, bsize);
+	WR(Blkcount, bcount);
+}
+
+static void
+sdhostio(int write, uchar *buf, int len)
+{
+	u32int *r;
+	int piolen;
+	u32int w;
+
+	r = (u32int*)SDHOSTREGS;
+	assert((len&3) == 0);
+	assert((((uintptr)buf)&3) == 0);
+	okay(1);
+	if(waserror()){
+		okay(0);
+		nexterror();
+	}
+	/*
+	 * According to comments in the linux driver, the hardware "doesn't
+	 * manage the FIFO DREQs properly for multi-block transfers" on input,
+	 * so the dma must be stopped early and the last 3 words fetched with pio
+	 */
+	piolen = 0;
+	if(!write && sdhost.bcount > 1){
+		piolen = (FifoDepth-1) * sizeof(u32int);
+		len -= piolen;
+	}
+	if(write)
+		dmastart(DmaChanSdhost, DmaDevSdhost, DmaM2D,
+			buf, &r[Data], len);
+	else
+		dmastart(DmaChanSdhost, DmaDevSdhost, DmaD2M,
+			&r[Data], buf, len);
+	if(dmawait(DmaChanSdhost) < 0)
+		error(Eio);
+	if(!write){
+		cachedinvse(buf, len);
+		buf += len;
+		for(; piolen > 0; piolen -= sizeof(u32int)){
+			if((r[Dbgmode] & 0x1F00) == 0){
+				print("sdhost: FIFO empty after short dma read\n");
+				error(Eio);
+			}
+			w = r[Data];
+			*((u32int*)buf) = w;
+			buf += sizeof(u32int);
+		}
+	}
+	poperror();
+	okay(0);
+}
+
+static void
+sdhostinterrupt(Ureg*, void*)
+{	
+	u32int *r;
+	int i;
+
+	r = (u32int*)SDHOSTREGS;
+	i = r[Status];
+	WR(Status, i);
+	if(i & Busyint){
+		sdhost.done = 1;
+		wakeup(&sdhost.r);
+	}
+}
+
+SDio sdiohost = {
+	"sdhost",
+	sdhostinit,
+	sdhostenable,
+	sdhostinquiry,
+	sdhostcmd,
+	sdhostiosetup,
+	sdhostio,
+};
+
+void
+sdhostlink(void)
+{
+	sdcardlink = &sdiohost;
+}
diff -r 44eae9a7e98b sys/src/9/bcm/sdmmc.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/9/bcm/sdmmc.c	Tue Oct 13 05:09:49 2020 -0700
@@ -0,0 +1,355 @@
+/*
+ * mmc / sd memory card
+ *
+ * Copyright © 2012 Richard Miller <r.miller@acm.org>
+ *
+ * Assumes only one card on the bus
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+#include "../port/sd.h"
+
+#define CSD(end, start)	rbits(csd, start, (end)-(start)+1)
+
+typedef struct Ctlr Ctlr;
+
+enum {
+	Inittimeout	= 15,
+	Multiblock	= 1,
+
+	/* Commands */
+	GO_IDLE_STATE	= 0,
+	ALL_SEND_CID	= 2,
+	SEND_RELATIVE_ADDR= 3,
+	SWITCH_FUNC	= 6,
+	SELECT_CARD	= 7,
+	SD_SEND_IF_COND	= 8,
+	SEND_CSD	= 9,
+	STOP_TRANSMISSION= 12,
+	SEND_STATUS	= 13,
+	SET_BLOCKLEN	= 16,
+	READ_SINGLE_BLOCK= 17,
+	READ_MULTIPLE_BLOCK= 18,
+	WRITE_BLOCK	= 24,
+	WRITE_MULTIPLE_BLOCK= 25,
+	APP_CMD		= 55,	/* prefix for following app-specific commands */
+	SET_BUS_WIDTH	= 6,
+	SD_SEND_OP_COND	= 41,
+
+	/* Command arguments */
+	/* SD_SEND_IF_COND */
+	Voltage		= 1<<8,
+	Checkpattern	= 0x42,
+
+	/* SELECT_CARD */
+	Rcashift	= 16,
+
+	/* SD_SEND_OP_COND */
+	Hcs	= 1<<30,	/* host supports SDHC & SDXC */
+	Ccs	= 1<<30,	/* card is SDHC or SDXC */
+	V3_3	= 3<<20,	/* 3.2-3.4 volts */
+
+	/* SET_BUS_WIDTH */
+	Width1	= 0<<0,
+	Width4	= 2<<0,
+
+	/* SWITCH_FUNC */
+	Dfltspeed	= 0<<0,
+	Hispeed		= 1<<0,
+	Checkfunc	= 0x00FFFFF0,
+	Setfunc		= 0x80FFFFF0,
+	Funcbytes	= 64,
+
+	/* OCR (operating conditions register) */
+	Powerup	= 1<<31,
+};
+
+struct Ctlr {
+	SDev	*dev;
+	SDio	*io;
+	/* SD card registers */
+	u16int	rca;
+	u32int	ocr;
+	u32int	cid[4];
+	u32int	csd[4];
+};
+
+SDio *sdcardlink;
+
+extern SDifc sdmmcifc;
+
+static uint
+rbits(u32int *p, uint start, uint len)
+{
+	uint w, off, v;
+
+	w   = start / 32;
+	off = start % 32;
+	if(off == 0)
+		v = p[w];
+	else
+		v = p[w] >> off | p[w+1] << (32-off);
+	if(len < 32)
+		return v & ((1<<len) - 1);
+	else
+		return v;
+}
+
+static void
+identify(SDunit *unit, u32int *csd)
+{
+	uint csize, mult;
+
+	unit->secsize = 1 << CSD(83, 80);
+	switch(CSD(127, 126)){
+	case 0:				/* CSD version 1 */
+		csize = CSD(73, 62);
+		mult = CSD(49, 47);
+		unit->sectors = (csize+1) * (1<<(mult+2));
+		break;
+	case 1:				/* CSD version 2 */
+		csize = CSD(69, 48);
+		unit->sectors = (csize+1) * 512LL*KiB / unit->secsize;
+		break;
+	}
+	if(unit->secsize == 1024){
+		unit->sectors <<= 1;
+		unit->secsize = 512;
+	}
+}
+
+static SDev*
+mmcpnp(void)
+{
+	SDev *sdev;
+	Ctlr *ctl;
+
+	if(sdcardlink == nil)
+		sdcardlink = &sdio;
+	if(sdcardlink->init() < 0)
+		return nil;
+	sdev = malloc(sizeof(SDev));
+	if(sdev == nil)
+		return nil;
+	ctl = malloc(sizeof(Ctlr));
+	if(ctl == nil){
+		free(sdev);
+		return nil;
+	}
+	sdev->idno = 'M';
+	sdev->ifc = &sdmmcifc;
+	sdev->nunit = 1;
+	sdev->ctlr = ctl;
+	ctl->dev = sdev;
+	ctl->io = sdcardlink;
+	return sdev;
+}
+
+static int
+mmcverify(SDunit *unit)
+{
+	int n;
+	Ctlr *ctl;
+
+	ctl = unit->dev->ctlr;
+	n = ctl->io->inquiry((char*)&unit->inquiry[8], sizeof(unit->inquiry)-8);
+	if(n < 0)
+		return 0;
+	unit->inquiry[0] = 0;
+	unit->inquiry[1] = 0x80;
+	unit->inquiry[4] = sizeof(unit->inquiry)-4;
+	return 1;
+}
+
+static int
+mmcenable(SDev* dev)
+{
+	Ctlr *ctl;
+
+	ctl = dev->ctlr;
+	ctl->io->enable();
+	return 1;
+}
+
+static void
+mmcswitchfunc(SDio *io, int arg)
+{
+	uchar *buf;
+	int n;
+	u32int r[4];
+
+	n = Funcbytes;
+	buf = sdmalloc(n);
+	if(waserror()){
+		print("mmcswitchfunc error\n");
+		sdfree(buf);
+		nexterror();
+	}
+	io->iosetup(0, buf, n, 1);
+	io->cmd(SWITCH_FUNC, arg, r);
+	io->io(0, buf, n);
+	sdfree(buf);
+	poperror();
+}
+
+static int
+mmconline(SDunit *unit)
+{
+	int hcs, i;
+	u32int r[4];
+	Ctlr *ctl;
+	SDio *io;
+
+	ctl = unit->dev->ctlr;
+	io = ctl->io;
+	assert(unit->subno == 0);
+
+	if(waserror()){
+		unit->sectors = 0;
+		return 0;
+	}
+	if(unit->sectors != 0){
+		io->cmd(SEND_STATUS, ctl->rca<<Rcashift, r);
+		poperror();
+		return 1;
+	}
+	if(waserror()){
+		unit->sectors = 0;
+		poperror();
+		return 2;
+	}
+	io->cmd(GO_IDLE_STATE, 0, r);
+	hcs = 0;
+	if(!waserror()){
+		io->cmd(SD_SEND_IF_COND, Voltage|Checkpattern, r);
+		if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
+			hcs = Hcs;
+		poperror();
+	}
+	for(i = 0; i < Inittimeout; i++){
+		delay(100);
+		io->cmd(APP_CMD, 0, r);
+		io->cmd(SD_SEND_OP_COND, hcs|V3_3, r);
+		if(r[0] & Powerup)
+			break;
+	}
+	if(i == Inittimeout){
+		print("sdmmc: card won't power up\n");
+		error(Eio);
+	}
+	poperror();
+	ctl->ocr = r[0];
+	io->cmd(ALL_SEND_CID, 0, r);
+	memmove(ctl->cid, r, sizeof ctl->cid);
+	io->cmd(SEND_RELATIVE_ADDR, 0, r);
+	ctl->rca = r[0]>>16;
+	io->cmd(SEND_CSD, ctl->rca<<Rcashift, r);
+	memmove(ctl->csd, r, sizeof ctl->csd);
+	identify(unit, ctl->csd);
+	io->cmd(SELECT_CARD, ctl->rca<<Rcashift, r);
+	io->cmd(SET_BLOCKLEN, unit->secsize, r);
+	io->cmd(APP_CMD, ctl->rca<<Rcashift, r);
+	io->cmd(SET_BUS_WIDTH, Width4, r);
+	if(!waserror()){
+		mmcswitchfunc(io, Hispeed|Setfunc);
+		poperror();
+	}
+	poperror();
+	return 1;
+}
+
+static int
+mmcrctl(SDunit *unit, char *p, int l)
+{
+	Ctlr *ctl;
+	int i, n;
+
+	ctl = unit->dev->ctlr;
+	assert(unit->subno == 0);
+	if(unit->sectors == 0){
+		mmconline(unit);
+		if(unit->sectors == 0)
+			return 0;
+	}
+	n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctl->rca, ctl->ocr);
+	for(i = nelem(ctl->cid)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctl->cid[i]);
+	n += snprint(p+n, l-n, " csd ");
+	for(i = nelem(ctl->csd)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctl->csd[i]);
+	n += snprint(p+n, l-n, "\ngeometry %llud %ld %lld 255 63\n",
+		unit->sectors, unit->secsize, unit->sectors / (255*63));
+	return n;
+}
+
+static long
+mmcbio(SDunit *unit, int lun, int write, void *data, long nb, uvlong bno)
+{
+	int len, tries;
+	ulong b;
+	u32int r[4];
+	uchar *buf;
+	Ctlr *ctl;
+	SDio *io;
+
+	USED(lun);
+	ctl = unit->dev->ctlr;
+	io = ctl->io;
+	assert(unit->subno == 0);
+	if(unit->sectors == 0)
+		error("media change");
+	buf = data;
+	len = unit->secsize;
+	if(Multiblock){
+		b = bno;
+		tries = 0;
+		while(waserror())
+			if(++tries == 3)
+				nexterror();
+		io->iosetup(write, buf, len, nb);
+		if(waserror()){
+			io->cmd(STOP_TRANSMISSION, 0, r);
+			nexterror();
+		}
+		io->cmd(write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK,
+			ctl->ocr & Ccs? b: b * len, r);
+		io->io(write, buf, nb * len);
+		poperror();
+		io->cmd(STOP_TRANSMISSION, 0, r);
+		poperror();
+		b += nb;
+	}else{
+		for(b = bno; b < bno + nb; b++){
+			io->iosetup(write, buf, len, 1);
+			io->cmd(write? WRITE_BLOCK : READ_SINGLE_BLOCK,
+				ctl->ocr & Ccs? b: b * len, r);
+			io->io(write, buf, len);
+			buf += len;
+		}
+	}
+	return (b - bno) * len;
+}
+
+static int
+mmcrio(SDreq*)
+{
+	return -1;
+}
+
+SDifc sdmmcifc = {
+	.name	= "mmc",
+	.pnp	= mmcpnp,
+	.enable	= mmcenable,
+	.verify	= mmcverify,
+	.online	= mmconline,
+	.rctl	= mmcrctl,
+	.bio	= mmcbio,
+	.rio	= mmcrio,
+};

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-16  3:42   ` kokamoto
@ 2020-10-16 21:31     ` Eli Cohen
  2020-10-16 22:52       ` kokamoto
  0 siblings, 1 reply; 8+ messages in thread
From: Eli Cohen @ 2020-10-16 21:31 UTC (permalink / raw)
  To: 9front

I managed to boot over tls "manually," here is how I did it:

127 holes free
0x0047e000 0x191e6000 416710656
416710656 bytes free

Plan 9 from Bell Labs
board rev: 0xa02082 firmware rev: 1590181998
cpu0: 1200MHz ARM Cortex-A53 r0p4
fp: 32 registers,  simd
fp: arm arch VFPv3+ with null subarch; rev 4
#l0: 4330: 10Mbps port 0x0 irq -1 ea 000000000000
998M memory: 402M kernel data, 596M user, 3576M swap
cpu1: 1200MHz ARM Cortex-A53 r0p4
cpu2: 1200MHz ARM Cortex-A53 r0p4
cpu3: 1200MHz ARM Cortex-A53 r0p4
ether4330: chip 43430 rev 1 type 1
ether4330: firmware ready
ether4330: addr ...

/dev/sdM0: BCM SDHost Controller
/dev/sdM0/data
/dev/sdM0/dos     dos
/dev/sdM0/fscache     cwfs64x
/dev/sdM0/fsworm
/dev/sdM0/nvram
/dev/sdM0/other
/dev/sdM0/plan9
bootargs is (tcp, tls, il, local!device)[local!/dev/sdM0/fscache] !rc
% factotum -u
user[glenda]:
% aux/wpa -2 -p -s ... /net/ether0
!Adding key: essid=... proto=wpapsk
password:
!
% ip/ipconfig
% bootargs is (tcp, tls, il, local!device)[local!/dev/sdM0/fscache] tls
user[glenda]:
fs address is? ...
auth address is? [...]

!Adding key: dom=... proto=dp9ik
user[glenda]:
password:
!

init: starting /bin/rc
...

I think the problem booting over tls automatically may be with the
wificrypt=wpa2 option. I looked in /sys/src/9/boot/net.rc and I don't
think it pays attention to this option, but aux/wpa needs the -2
option to work with this driver. all I have done so far was copy over
enough of Richard Miller's work with very slight changes just to get
it working at all... 9front has a wifi stack this driver doesn't use
at all, etc.

as far as the bcm64 kernel goes, I don't know if anyone has gotten
this working with it. khm and I both tried, but I eventually
encountered a problem with wlreadpkt returning a bad value I couldn't
get past. There must be some difference I can't find for getting it
working with a 64 bit kernel.

- Eli


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-16 21:31     ` Eli Cohen
@ 2020-10-16 22:52       ` kokamoto
  2020-10-17  2:41         ` Eli Cohen
  2020-10-17  5:52         ` kokamoto
  0 siblings, 2 replies; 8+ messages in thread
From: kokamoto @ 2020-10-16 22:52 UTC (permalink / raw)
  To: 9front


> /dev/sdM0/other
> /dev/sdM0/plan9
> bootargs is (tcp, tls, il, local!device)[local!/dev/sdM0/fscache] !rc

I didn't know this usage. I'll try it later.
> I think the problem booting over tls automatically may be with the
> wificrypt=wpa2 option. 

Yes, and then, if I booted directly by tls option, /net/ether0/ifstats has
no definitions of essid, crypt, etc, and reports no authentication is neccessary...

By the way, this implementation of rpi3B wifi runs much quicker than
that I tried before on vanilla Plan9.   It's can be used as everyday usage.

Kenji



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-16 22:52       ` kokamoto
@ 2020-10-17  2:41         ` Eli Cohen
  2020-10-17  2:49           ` ori
  2020-10-17  5:52         ` kokamoto
  1 sibling, 1 reply; 8+ messages in thread
From: Eli Cohen @ 2020-10-17  2:41 UTC (permalink / raw)
  To: 9front

> Yes, and then, if I booted directly by tls option, /net/ether0/ifstats has
> no definitions of essid, crypt, etc, and reports no authentication is neccessary...

/sys/src/9/boot/net.rc could simply be modified to do that with a -2
in the right place, or better yet take the wificrypt variable. or best
yet probably, to get this driver working with the 9front wifi stack. I
started trying to do that but it may be beyond me.

> By the way, this implementation of rpi3B wifi runs much quicker than
> that I tried before on vanilla Plan9.   It's can be used as everyday usage.

Cool! it's certainly nothing I changed. Richard Miller has updated his
release as recently as May, vanilla Plan9 might be faster now too.

- Eli


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-17  2:41         ` Eli Cohen
@ 2020-10-17  2:49           ` ori
  0 siblings, 0 replies; 8+ messages in thread
From: ori @ 2020-10-17  2:49 UTC (permalink / raw)
  To: echoline, 9front

> or best yet probably, to get this driver working with the
> 9front wifi stack. I started trying to do that but it may
> be beyond me.

I think that's what will have to happen to get it
committed. What are you getting stuck on?



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [9front] rpi3 wifi
  2020-10-16 22:52       ` kokamoto
  2020-10-17  2:41         ` Eli Cohen
@ 2020-10-17  5:52         ` kokamoto
  1 sibling, 0 replies; 8+ messages in thread
From: kokamoto @ 2020-10-17  5:52 UTC (permalink / raw)
  To: 9front

>> /dev/sdM0/other
>> /dev/sdM0/plan9
>> bootargs is (tcp, tls, il, local!device)[local!/dev/sdM0/fscache] !rc

Ok, I tied this method, and found that it's root is from tls, however,
it runs much slower, and comparable with the vanilla plan9 case.
It's not fine for everyday use.

The method I reported uses local file system, and this does file server's
root file system...

Kenji

PS: I'm writing this mail on terminal above.



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2020-10-17  5:52 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-23  5:26 rpi3 wifi Kurt H Maier
2020-10-13 12:16 ` [9front] " Eli Cohen
2020-10-16  3:42   ` kokamoto
2020-10-16 21:31     ` Eli Cohen
2020-10-16 22:52       ` kokamoto
2020-10-17  2:41         ` Eli Cohen
2020-10-17  2:49           ` ori
2020-10-17  5:52         ` kokamoto

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).