9front - general discussion about 9front
 help / color / mirror / Atom feed
* [9front] Fwd: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)
       [not found] <1734B8F2A873B2D850DB1C299ECACF8F@9front.org>
@ 2023-10-22 18:43 ` Stanley Lieber
  2023-10-22 18:45   ` [9front] " Stanley Lieber
  0 siblings, 1 reply; 3+ messages in thread
From: Stanley Lieber @ 2023-10-22 18:43 UTC (permalink / raw)
  To: 9front

this is for rpi, yes? is there a list of specific hardware this is known to work with?

sl


-------- Original Message --------
From: commits@git.9front.org
Sent: October 22, 2023 10:53:02 AM EDT
To: 9front-commits@9front.org
Subject: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)


Thanks Richard Miller
---
diff f2cfee358f329519e913a20142d96b1e0029633c fb96a050f8a9d5f23da3557ba89251ffb73889bf
--- a/sys/src/9/bcm/emmc.c	Sun Oct 22 09:21:23 2023
+++ b/sys/src/9/bcm/emmc.c	Sun Oct 22 10:53:02 2023
@@ -129,6 +129,7 @@
 
 typedef struct Ctlr Ctlr;
 struct Ctlr {
+	Rendez	cardr;
 	Rendez	r;
 	int	fastclock;
 	ulong	extclk;
@@ -266,6 +267,23 @@
 	intrenable(IRQmmc, emmcinterrupt, nil, BUSUNKNOWN, io->name);
 }
 
+int
+sdiocardintr(int wait)
+{
+	u32int *r = (u32int*)EMMCREGS;
+	int i;
+
+	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(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
 {
@@ -434,21 +452,25 @@
 	i = r[Interrupt];
 	if(i&(Datadone|Err))
 		wakeup(&emmc.r);
+	if(i&Cardintr)
+		wakeup(&emmc.cardr);
 	WR(Irpten, r[Irpten] & ~i);
 }
 
+
+SDio sdio = {
+	"emmc",
+	emmcinit,
+	emmcenable,
+	emmcinquiry,
+	emmccmd,
+	emmciosetup,
+	emmcio,
+	emmcbus,
+};
+
 void
 emmclink(void)
 {
-	static SDio io = {
-		"emmc",
-		emmcinit,
-		emmcenable,
-		emmcinquiry,
-		emmccmd,
-		emmciosetup,
-		emmcio,
-		emmcbus,
-	};
-	addmmcio(&io);
+	addmmcio(&sdio);
 }
--- /dev/null	Sun Aug 27 23:09:05 2023
+++ b/sys/src/9/bcm/ether4330.c	Sun Oct 22 10:53:02 2023
@@ -0,0 +1,2411 @@
+/*
+ * 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"
+#include "../port/etherif.h"
+
+#define CACHELINESZ 64	/* temp */
+
+extern SDio	sdio;
+extern int	sdiocardintr(int);
+
+SDiocmd IO_SEND_OP_COND	= {  5, 3, 0, 0, "IO_SEND_OP_COND" };
+SDiocmd IO_RW_DIRECT	= { 52, 1, 0, 0, "IO_RW_DIRECT" };
+
+enum{
+	SDIODEBUG = 0,
+	SBDEBUG = 0,
+	EVENTDEBUG = 0,
+	VARDEBUG = 0,
+	FWDEBUG  = 0,
+
+	Corescansz = 512,
+	Uploadsz = 2048,
+	Sdpcmsz = 12,	/* sizeof(Sdpcmsz) */
+	Cmdsz = 16,	/* sizeof(Cmd) */
+
+	SDfreq = 25*Mhz,	/* standard SD frequency */
+	SDfreqhs = 50*Mhz,	/* high speed frequency */
+
+	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,
+
+	/* 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 },
+	{ 43430, 2,	"brcmfmac43436-sdio.bin", "brcmfmac43436-sdio.txt",  "brcmfmac43436-sdio.clm_blob" },
+	{ 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" },
+	{ 0x4345, 9, "brcmfmac43456-sdio.bin", "brcmfmac43456-sdio.txt", "brcmfmac43456-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(SDiocmd *cmd, ulong arg)
+{
+	u32int resp[4];
+
+	sdio.cmd(&sdio, cmd, arg, resp);
+	return resp[0];
+}
+
+static ulong
+sdiocmd(SDiocmd *cmd, ulong arg)
+{
+	ulong r;
+
+	qlock(&sdiolock);
+	if(waserror()){
+		if(SDIODEBUG) print("sdiocmd error: cmd %s arg %lux\n", cmd->name, arg);
+		qunlock(&sdiolock);
+		nexterror();
+	}
+	r = sdiocmd_locked(cmd, arg);
+	qunlock(&sdiolock);
+	poperror();
+	return r;
+
+}
+
+static ulong
+trysdiocmd(SDiocmd *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;
+	ulong arg;
+
+	r = 0;
+	for(retry = 0; retry < 10; retry++){
+		arg = (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF);
+		if((arg & ~0xFF) == (1<<31|0<<28|7<<9)){
+			switch(arg&3){
+			case 0:
+				sdio.bus(&sdio, 1, 0);
+				break;
+			case 2:
+				sdio.bus(&sdio, 4, 0);
+				break;
+			}
+		}
+		r = sdiocmd(&IO_RW_DIRECT, arg);
+		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;
+	SDiocmd cmd = { 53, 1, 0, 0, "IO_RW_EXTENDED" };
+
+	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();
+		}
+		cmd.data = write? 2 : 1;	/* Host2card : Card2host */
+		if(blk){
+			cmd.data += 2;		/* Multiblock | Blkcnten */
+			sdio.iosetup(&sdio, write, a, bsize, bcount);
+		}else
+			sdio.iosetup(&sdio, write, a, bcount, 1);
+		sdiocmd_locked(&cmd, write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF));
+		sdio.io(&sdio, 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);
+	sdio.enable(&sdio);
+	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);
+	sdio.bus(&sdio, 0, SDfreq);
+	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 %#lux %#lux %#lux ->", 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 %#lux\n", coreid, buf[i]&0xC0? "ctl" : "mem", 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 %#lux D11 %#lux SOCRAM %#lux,%#lux %lud bytes @ %#lux\n",
+		ctl->armctl, ctl->d11ctl, ctl->socramctl, ctl->socramregs, ctl->socramsize, 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 /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, "/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 /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, Sdpcmsz);
+		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 < Sdpcmsz || 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 > Sdpcmsz)
+			packetrw(0, b->wp + Sdpcmsz, len - Sdpcmsz);
+		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) + Sdpcmsz;
+		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) < Sdpcmsz + Cmdsz)
+				break;
+			q = (Cmd*)(b->rp + Sdpcmsz);
+			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 %lld %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 = Sdpcmsz + Cmdsz + 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 = Sdpcmsz;
+	b->wp += Sdpcmsz;
+	
+	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 += Cmdsz;
+
+	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)
+{
+	static Ctlr *ctlr;
+
+	if(ctlr != nil)
+		return -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);
+}
--- a/sys/src/9/bcm64/pi4	Sun Oct 22 09:21:23 2023
+++ b/sys/src/9/bcm64/pi4	Sun Oct 22 10:53:02 2023
@@ -32,6 +32,7 @@
 	archbcm4	pci
 	usbxhcipci	pci usbxhci archbcm4 
 	ethergenet	ethermii
+	ether4330	emmc
 	ethermedium
 	loopbackmedium
 	netdevmedium
--- a/sys/src/9/port/sd.h	Sun Oct 22 09:21:23 2023
+++ b/sys/src/9/port/sd.h	Sun Oct 22 10:53:02 2023
@@ -164,6 +164,31 @@
 	char	*name;
 };
 
+/* Commands */
+extern SDiocmd GO_IDLE_STATE;
+extern SDiocmd SEND_OP_COND;
+extern SDiocmd ALL_SEND_CID;
+extern SDiocmd SET_RELATIVE_ADDR;
+extern SDiocmd SEND_RELATIVE_ADDR;
+extern SDiocmd SWITCH;
+extern SDiocmd SWITCH_FUNC;
+extern SDiocmd SELECT_CARD;
+extern SDiocmd SEND_EXT_CSD;
+extern SDiocmd SD_SEND_IF_COND;
+extern SDiocmd SEND_CSD;
+extern SDiocmd STOP_TRANSMISSION;
+extern SDiocmd SEND_STATUS;
+extern SDiocmd SET_BLOCKLEN;
+extern SDiocmd READ_SINGLE_BLOCK;
+extern SDiocmd READ_MULTIPLE_BLOCK;
+extern SDiocmd WRITE_SINGLE_BLOCK;
+extern SDiocmd WRITE_MULTIPLE_BLOCK;
+
+/* prefix for following app-specific commands */
+extern SDiocmd APP_CMD;
+extern SDiocmd SD_SET_BUS_WIDTH;
+extern SDiocmd SD_SEND_OP_COND;
+
 struct SDio {
 	char	*name;
 	int	(*init)(SDio*);


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

* [9front] Re: Fwd: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)
  2023-10-22 18:43 ` [9front] Fwd: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org) Stanley Lieber
@ 2023-10-22 18:45   ` Stanley Lieber
  2023-10-22 19:01     ` adventures in9
  0 siblings, 1 reply; 3+ messages in thread
From: Stanley Lieber @ 2023-10-22 18:45 UTC (permalink / raw)
  To: 9front

On October 22, 2023 2:43:42 PM EDT, Stanley Lieber <sl@stanleylieber.com> wrote:
>this is for rpi, yes? is there a list of specific hardware this is known to work with?
>
>sl
>
>
>-------- Original Message --------
>From: commits@git.9front.org
>Sent: October 22, 2023 10:53:02 AM EDT
>To: 9front-commits@9front.org
>Subject: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)
>
>
>Thanks Richard Miller
>---
>diff f2cfee358f329519e913a20142d96b1e0029633c fb96a050f8a9d5f23da3557ba89251ffb73889bf
>--- a/sys/src/9/bcm/emmc.c	Sun Oct 22 09:21:23 2023
>+++ b/sys/src/9/bcm/emmc.c	Sun Oct 22 10:53:02 2023
>@@ -129,6 +129,7 @@
> 
> typedef struct Ctlr Ctlr;
> struct Ctlr {
>+	Rendez	cardr;
> 	Rendez	r;
> 	int	fastclock;
> 	ulong	extclk;
>@@ -266,6 +267,23 @@
> 	intrenable(IRQmmc, emmcinterrupt, nil, BUSUNKNOWN, io->name);
> }
> 
>+int
>+sdiocardintr(int wait)
>+{
>+	u32int *r = (u32int*)EMMCREGS;
>+	int i;
>+
>+	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(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
> {
>@@ -434,21 +452,25 @@
> 	i = r[Interrupt];
> 	if(i&(Datadone|Err))
> 		wakeup(&emmc.r);
>+	if(i&Cardintr)
>+		wakeup(&emmc.cardr);
> 	WR(Irpten, r[Irpten] & ~i);
> }
> 
>+
>+SDio sdio = {
>+	"emmc",
>+	emmcinit,
>+	emmcenable,
>+	emmcinquiry,
>+	emmccmd,
>+	emmciosetup,
>+	emmcio,
>+	emmcbus,
>+};
>+
> void
> emmclink(void)
> {
>-	static SDio io = {
>-		"emmc",
>-		emmcinit,
>-		emmcenable,
>-		emmcinquiry,
>-		emmccmd,
>-		emmciosetup,
>-		emmcio,
>-		emmcbus,
>-	};
>-	addmmcio(&io);
>+	addmmcio(&sdio);
> }
>--- /dev/null	Sun Aug 27 23:09:05 2023
>+++ b/sys/src/9/bcm/ether4330.c	Sun Oct 22 10:53:02 2023
>@@ -0,0 +1,2411 @@
>+/*
>+ * 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"
>+#include "../port/etherif.h"
>+
>+#define CACHELINESZ 64	/* temp */
>+
>+extern SDio	sdio;
>+extern int	sdiocardintr(int);
>+
>+SDiocmd IO_SEND_OP_COND	= {  5, 3, 0, 0, "IO_SEND_OP_COND" };
>+SDiocmd IO_RW_DIRECT	= { 52, 1, 0, 0, "IO_RW_DIRECT" };
>+
>+enum{
>+	SDIODEBUG = 0,
>+	SBDEBUG = 0,
>+	EVENTDEBUG = 0,
>+	VARDEBUG = 0,
>+	FWDEBUG  = 0,
>+
>+	Corescansz = 512,
>+	Uploadsz = 2048,
>+	Sdpcmsz = 12,	/* sizeof(Sdpcmsz) */
>+	Cmdsz = 16,	/* sizeof(Cmd) */
>+
>+	SDfreq = 25*Mhz,	/* standard SD frequency */
>+	SDfreqhs = 50*Mhz,	/* high speed frequency */
>+
>+	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,
>+
>+	/* 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 },
>+	{ 43430, 2,	"brcmfmac43436-sdio.bin", "brcmfmac43436-sdio.txt",  "brcmfmac43436-sdio.clm_blob" },
>+	{ 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" },
>+	{ 0x4345, 9, "brcmfmac43456-sdio.bin", "brcmfmac43456-sdio.txt", "brcmfmac43456-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(SDiocmd *cmd, ulong arg)
>+{
>+	u32int resp[4];
>+
>+	sdio.cmd(&sdio, cmd, arg, resp);
>+	return resp[0];
>+}
>+
>+static ulong
>+sdiocmd(SDiocmd *cmd, ulong arg)
>+{
>+	ulong r;
>+
>+	qlock(&sdiolock);
>+	if(waserror()){
>+		if(SDIODEBUG) print("sdiocmd error: cmd %s arg %lux\n", cmd->name, arg);
>+		qunlock(&sdiolock);
>+		nexterror();
>+	}
>+	r = sdiocmd_locked(cmd, arg);
>+	qunlock(&sdiolock);
>+	poperror();
>+	return r;
>+
>+}
>+
>+static ulong
>+trysdiocmd(SDiocmd *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;
>+	ulong arg;
>+
>+	r = 0;
>+	for(retry = 0; retry < 10; retry++){
>+		arg = (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF);
>+		if((arg & ~0xFF) == (1<<31|0<<28|7<<9)){
>+			switch(arg&3){
>+			case 0:
>+				sdio.bus(&sdio, 1, 0);
>+				break;
>+			case 2:
>+				sdio.bus(&sdio, 4, 0);
>+				break;
>+			}
>+		}
>+		r = sdiocmd(&IO_RW_DIRECT, arg);
>+		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;
>+	SDiocmd cmd = { 53, 1, 0, 0, "IO_RW_EXTENDED" };
>+
>+	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();
>+		}
>+		cmd.data = write? 2 : 1;	/* Host2card : Card2host */
>+		if(blk){
>+			cmd.data += 2;		/* Multiblock | Blkcnten */
>+			sdio.iosetup(&sdio, write, a, bsize, bcount);
>+		}else
>+			sdio.iosetup(&sdio, write, a, bcount, 1);
>+		sdiocmd_locked(&cmd, write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF));
>+		sdio.io(&sdio, 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);
>+	sdio.enable(&sdio);
>+	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);
>+	sdio.bus(&sdio, 0, SDfreq);
>+	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 %#lux %#lux %#lux ->", 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 %#lux\n", coreid, buf[i]&0xC0? "ctl" : "mem", 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 %#lux D11 %#lux SOCRAM %#lux,%#lux %lud bytes @ %#lux\n",
>+		ctl->armctl, ctl->d11ctl, ctl->socramctl, ctl->socramregs, ctl->socramsize, 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 /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, "/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 /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, Sdpcmsz);
>+		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 < Sdpcmsz || 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 > Sdpcmsz)
>+			packetrw(0, b->wp + Sdpcmsz, len - Sdpcmsz);
>+		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) + Sdpcmsz;
>+		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) < Sdpcmsz + Cmdsz)
>+				break;
>+			q = (Cmd*)(b->rp + Sdpcmsz);
>+			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 %lld %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 = Sdpcmsz + Cmdsz + 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 = Sdpcmsz;
>+	b->wp += Sdpcmsz;
>+	
>+	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 += Cmdsz;
>+
>+	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)
>+{
>+	static Ctlr *ctlr;
>+
>+	if(ctlr != nil)
>+		return -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);
>+}
>--- a/sys/src/9/bcm64/pi4	Sun Oct 22 09:21:23 2023
>+++ b/sys/src/9/bcm64/pi4	Sun Oct 22 10:53:02 2023
>@@ -32,6 +32,7 @@
> 	archbcm4	pci
> 	usbxhcipci	pci usbxhci archbcm4 
> 	ethergenet	ethermii
>+	ether4330	emmc
> 	ethermedium
> 	loopbackmedium
> 	netdevmedium
>--- a/sys/src/9/port/sd.h	Sun Oct 22 09:21:23 2023
>+++ b/sys/src/9/port/sd.h	Sun Oct 22 10:53:02 2023
>@@ -164,6 +164,31 @@
> 	char	*name;
> };
> 
>+/* Commands */
>+extern SDiocmd GO_IDLE_STATE;
>+extern SDiocmd SEND_OP_COND;
>+extern SDiocmd ALL_SEND_CID;
>+extern SDiocmd SET_RELATIVE_ADDR;
>+extern SDiocmd SEND_RELATIVE_ADDR;
>+extern SDiocmd SWITCH;
>+extern SDiocmd SWITCH_FUNC;
>+extern SDiocmd SELECT_CARD;
>+extern SDiocmd SEND_EXT_CSD;
>+extern SDiocmd SD_SEND_IF_COND;
>+extern SDiocmd SEND_CSD;
>+extern SDiocmd STOP_TRANSMISSION;
>+extern SDiocmd SEND_STATUS;
>+extern SDiocmd SET_BLOCKLEN;
>+extern SDiocmd READ_SINGLE_BLOCK;
>+extern SDiocmd READ_MULTIPLE_BLOCK;
>+extern SDiocmd WRITE_SINGLE_BLOCK;
>+extern SDiocmd WRITE_MULTIPLE_BLOCK;
>+
>+/* prefix for following app-specific commands */
>+extern SDiocmd APP_CMD;
>+extern SDiocmd SD_SET_BUS_WIDTH;
>+extern SDiocmd SD_SEND_OP_COND;
>+
> struct SDio {
> 	char	*name;
> 	int	(*init)(SDio*);
>
>

let me rephrase: is this known to actually work, and which hardware does it actually work with. asking for a fqa.

sl

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

* Re: [9front] Re: Fwd: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)
  2023-10-22 18:45   ` [9front] " Stanley Lieber
@ 2023-10-22 19:01     ` adventures in9
  0 siblings, 0 replies; 3+ messages in thread
From: adventures in9 @ 2023-10-22 19:01 UTC (permalink / raw)
  To: 9front

kws says it works on the rpi4 right now.
I'm testing pi3b, pi3b+ and piw0,
but I need to track down which firmware to use first

On Sun, Oct 22, 2023 at 11:47 AM Stanley Lieber <sl@stanleylieber.com> wrote:
>
> On October 22, 2023 2:43:42 PM EDT, Stanley Lieber <sl@stanleylieber.com> wrote:
> >this is for rpi, yes? is there a list of specific hardware this is known to work with?
> >
> >sl
> >
> >
> >-------- Original Message --------
> >From: commits@git.9front.org
> >Sent: October 22, 2023 10:53:02 AM EDT
> >To: 9front-commits@9front.org
> >Subject: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org)
> >
> >
> >Thanks Richard Miller
> >---
> >diff f2cfee358f329519e913a20142d96b1e0029633c fb96a050f8a9d5f23da3557ba89251ffb73889bf
> >--- a/sys/src/9/bcm/emmc.c     Sun Oct 22 09:21:23 2023
> >+++ b/sys/src/9/bcm/emmc.c     Sun Oct 22 10:53:02 2023
> >@@ -129,6 +129,7 @@
> >
> > typedef struct Ctlr Ctlr;
> > struct Ctlr {
> >+      Rendez  cardr;
> >       Rendez  r;
> >       int     fastclock;
> >       ulong   extclk;
> >@@ -266,6 +267,23 @@
> >       intrenable(IRQmmc, emmcinterrupt, nil, BUSUNKNOWN, io->name);
> > }
> >
> >+int
> >+sdiocardintr(int wait)
> >+{
> >+      u32int *r = (u32int*)EMMCREGS;
> >+      int i;
> >+
> >+      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(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
> > {
> >@@ -434,21 +452,25 @@
> >       i = r[Interrupt];
> >       if(i&(Datadone|Err))
> >               wakeup(&emmc.r);
> >+      if(i&Cardintr)
> >+              wakeup(&emmc.cardr);
> >       WR(Irpten, r[Irpten] & ~i);
> > }
> >
> >+
> >+SDio sdio = {
> >+      "emmc",
> >+      emmcinit,
> >+      emmcenable,
> >+      emmcinquiry,
> >+      emmccmd,
> >+      emmciosetup,
> >+      emmcio,
> >+      emmcbus,
> >+};
> >+
> > void
> > emmclink(void)
> > {
> >-      static SDio io = {
> >-              "emmc",
> >-              emmcinit,
> >-              emmcenable,
> >-              emmcinquiry,
> >-              emmccmd,
> >-              emmciosetup,
> >-              emmcio,
> >-              emmcbus,
> >-      };
> >-      addmmcio(&io);
> >+      addmmcio(&sdio);
> > }
> >--- /dev/null  Sun Aug 27 23:09:05 2023
> >+++ b/sys/src/9/bcm/ether4330.c        Sun Oct 22 10:53:02 2023
> >@@ -0,0 +1,2411 @@
> >+/*
> >+ * 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"
> >+#include "../port/etherif.h"
> >+
> >+#define CACHELINESZ 64        /* temp */
> >+
> >+extern SDio   sdio;
> >+extern int    sdiocardintr(int);
> >+
> >+SDiocmd IO_SEND_OP_COND       = {  5, 3, 0, 0, "IO_SEND_OP_COND" };
> >+SDiocmd IO_RW_DIRECT  = { 52, 1, 0, 0, "IO_RW_DIRECT" };
> >+
> >+enum{
> >+      SDIODEBUG = 0,
> >+      SBDEBUG = 0,
> >+      EVENTDEBUG = 0,
> >+      VARDEBUG = 0,
> >+      FWDEBUG  = 0,
> >+
> >+      Corescansz = 512,
> >+      Uploadsz = 2048,
> >+      Sdpcmsz = 12,   /* sizeof(Sdpcmsz) */
> >+      Cmdsz = 16,     /* sizeof(Cmd) */
> >+
> >+      SDfreq = 25*Mhz,        /* standard SD frequency */
> >+      SDfreqhs = 50*Mhz,      /* high speed frequency */
> >+
> >+      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,
> >+
> >+      /* 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 },
> >+      { 43430, 2,     "brcmfmac43436-sdio.bin", "brcmfmac43436-sdio.txt",  "brcmfmac43436-sdio.clm_blob" },
> >+      { 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" },
> >+      { 0x4345, 9, "brcmfmac43456-sdio.bin", "brcmfmac43456-sdio.txt", "brcmfmac43456-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(SDiocmd *cmd, ulong arg)
> >+{
> >+      u32int resp[4];
> >+
> >+      sdio.cmd(&sdio, cmd, arg, resp);
> >+      return resp[0];
> >+}
> >+
> >+static ulong
> >+sdiocmd(SDiocmd *cmd, ulong arg)
> >+{
> >+      ulong r;
> >+
> >+      qlock(&sdiolock);
> >+      if(waserror()){
> >+              if(SDIODEBUG) print("sdiocmd error: cmd %s arg %lux\n", cmd->name, arg);
> >+              qunlock(&sdiolock);
> >+              nexterror();
> >+      }
> >+      r = sdiocmd_locked(cmd, arg);
> >+      qunlock(&sdiolock);
> >+      poperror();
> >+      return r;
> >+
> >+}
> >+
> >+static ulong
> >+trysdiocmd(SDiocmd *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;
> >+      ulong arg;
> >+
> >+      r = 0;
> >+      for(retry = 0; retry < 10; retry++){
> >+              arg = (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF);
> >+              if((arg & ~0xFF) == (1<<31|0<<28|7<<9)){
> >+                      switch(arg&3){
> >+                      case 0:
> >+                              sdio.bus(&sdio, 1, 0);
> >+                              break;
> >+                      case 2:
> >+                              sdio.bus(&sdio, 4, 0);
> >+                              break;
> >+                      }
> >+              }
> >+              r = sdiocmd(&IO_RW_DIRECT, arg);
> >+              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;
> >+      SDiocmd cmd = { 53, 1, 0, 0, "IO_RW_EXTENDED" };
> >+
> >+      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();
> >+              }
> >+              cmd.data = write? 2 : 1;        /* Host2card : Card2host */
> >+              if(blk){
> >+                      cmd.data += 2;          /* Multiblock | Blkcnten */
> >+                      sdio.iosetup(&sdio, write, a, bsize, bcount);
> >+              }else
> >+                      sdio.iosetup(&sdio, write, a, bcount, 1);
> >+              sdiocmd_locked(&cmd, write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF));
> >+              sdio.io(&sdio, 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);
> >+      sdio.enable(&sdio);
> >+      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);
> >+      sdio.bus(&sdio, 0, SDfreq);
> >+      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 %#lux %#lux %#lux ->", 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 %#lux\n", coreid, buf[i]&0xC0? "ctl" : "mem", 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 %#lux D11 %#lux SOCRAM %#lux,%#lux %lud bytes @ %#lux\n",
> >+              ctl->armctl, ctl->d11ctl, ctl->socramctl, ctl->socramregs, ctl->socramsize, 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 /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, "/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 /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, Sdpcmsz);
> >+              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 < Sdpcmsz || 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 > Sdpcmsz)
> >+                      packetrw(0, b->wp + Sdpcmsz, len - Sdpcmsz);
> >+              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) + Sdpcmsz;
> >+              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) < Sdpcmsz + Cmdsz)
> >+                              break;
> >+                      q = (Cmd*)(b->rp + Sdpcmsz);
> >+                      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 %lld %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 = Sdpcmsz + Cmdsz + 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 = Sdpcmsz;
> >+      b->wp += Sdpcmsz;
> >+
> >+      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 += Cmdsz;
> >+
> >+      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)
> >+{
> >+      static Ctlr *ctlr;
> >+
> >+      if(ctlr != nil)
> >+              return -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);
> >+}
> >--- a/sys/src/9/bcm64/pi4      Sun Oct 22 09:21:23 2023
> >+++ b/sys/src/9/bcm64/pi4      Sun Oct 22 10:53:02 2023
> >@@ -32,6 +32,7 @@
> >       archbcm4        pci
> >       usbxhcipci      pci usbxhci archbcm4
> >       ethergenet      ethermii
> >+      ether4330       emmc
> >       ethermedium
> >       loopbackmedium
> >       netdevmedium
> >--- a/sys/src/9/port/sd.h      Sun Oct 22 09:21:23 2023
> >+++ b/sys/src/9/port/sd.h      Sun Oct 22 10:53:02 2023
> >@@ -164,6 +164,31 @@
> >       char    *name;
> > };
> >
> >+/* Commands */
> >+extern SDiocmd GO_IDLE_STATE;
> >+extern SDiocmd SEND_OP_COND;
> >+extern SDiocmd ALL_SEND_CID;
> >+extern SDiocmd SET_RELATIVE_ADDR;
> >+extern SDiocmd SEND_RELATIVE_ADDR;
> >+extern SDiocmd SWITCH;
> >+extern SDiocmd SWITCH_FUNC;
> >+extern SDiocmd SELECT_CARD;
> >+extern SDiocmd SEND_EXT_CSD;
> >+extern SDiocmd SD_SEND_IF_COND;
> >+extern SDiocmd SEND_CSD;
> >+extern SDiocmd STOP_TRANSMISSION;
> >+extern SDiocmd SEND_STATUS;
> >+extern SDiocmd SET_BLOCKLEN;
> >+extern SDiocmd READ_SINGLE_BLOCK;
> >+extern SDiocmd READ_MULTIPLE_BLOCK;
> >+extern SDiocmd WRITE_SINGLE_BLOCK;
> >+extern SDiocmd WRITE_MULTIPLE_BLOCK;
> >+
> >+/* prefix for following app-specific commands */
> >+extern SDiocmd APP_CMD;
> >+extern SDiocmd SD_SET_BUS_WIDTH;
> >+extern SDiocmd SD_SEND_OP_COND;
> >+
> > struct SDio {
> >       char    *name;
> >       int     (*init)(SDio*);
> >
> >
>
> let me rephrase: is this known to actually work, and which hardware does it actually work with. asking for a fqa.
>
> sl

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

end of thread, other threads:[~2023-10-22 19:06 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <1734B8F2A873B2D850DB1C299ECACF8F@9front.org>
2023-10-22 18:43 ` [9front] Fwd: [9front-commits] bcm: add wifi driver (keegan@undefinedbehaviour.org) Stanley Lieber
2023-10-22 18:45   ` [9front] " Stanley Lieber
2023-10-22 19:01     ` adventures in9

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).