ref: 9d09242bee8489b6cdee246489063488bb52b1fb
dir: /sys/src/9/bcm/ether4330.c/
/* * Broadcom bcm4330 wifi (sdio interface) */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "../port/netif.h" #include "../port/etherif.h" #include "../port/sd.h" #define CACHELINESZ 64 /* temp */ static SDiocmd IO_SEND_OP_COND = { 5, 3, 0, 0, "IO_SEND_OP_COND" }; static 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; uchar dat[WKeyLen]; }; struct Ctlr { Ether *edev; SDio *sdio; int iodebug; QLock sdiolock; 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"; static 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 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(Ctlr *ctl, SDiocmd *cmd, ulong arg) { u32int resp[4]; SDio *io; qlock(&ctl->sdiolock); if(waserror()){ if(SDIODEBUG) print("sdiocmd error: cmd %s arg %lux\n", cmd->name, arg); qunlock(&ctl->sdiolock); nexterror(); } io = ctl->sdio; (*io->cmd)(io, cmd, arg, resp); qunlock(&ctl->sdiolock); poperror(); return resp[0]; } static ulong trysdiocmd(Ctlr *ctl, SDiocmd *cmd, ulong arg) { ulong r; if(waserror()) return 0; r = sdiocmd(ctl, cmd, arg); poperror(); return r; } static int sdiord(Ctlr *ctl, int fn, int addr) { int r; r = sdiocmd(ctl, &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(Ctlr *ctl, int fn, int addr, int data) { int r; int retry; r = 0; for(retry = 0; retry < 10; retry++){ r = sdiocmd(ctl, &IO_RW_DIRECT, (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF)); if((r & 0xCF00) == 0) return; } print("ether4330: sdiowr(%x, %x, %x) fail: %2.2ux %2.2ux\n", fn, addr, data, (r>>8)&0xFF, r&0xFF); error(Eio); } static void sdiorwext(Ctlr *ctl, int fn, int write, void *a, int len, int addr, int incr) { SDiocmd cmd = { 53, 1, 0, 0, "IO_RW_EXTENDED" }; int bsize, blk, bcount, m; u32int resp[4]; SDio *io; 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(&ctl->sdiolock); if(waserror()){ print("ether4330: sdiorwext fail: %s\n", up->errstr); qunlock(&ctl->sdiolock); nexterror(); } io = ctl->sdio; cmd.data = write? 2 : 1; /* Host2card : Card2host */ if(blk){ cmd.data += 2; /* Multiblock | Blkcnten */ (*io->iosetup)(io, write, a, bsize, bcount); }else (*io->iosetup)(io, write, a, bcount, 1); (*io->cmd)(io, &cmd, write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF), resp); (*io->io)(io, write, a, m); qunlock(&ctl->sdiolock); poperror(); len -= m; a = (char*)a + m; if(incr) addr += m; } } static void sdioset(Ctlr *ctl, int fn, int addr, int bits) { sdiowr(ctl, fn, addr, sdiord(ctl, fn, addr) | bits); } static void sdioinit(Ctlr *ctl) { ulong ocr, rca; int i; if(ctl->sdio == nil){ /* take over /dev/sdM (emmc) */ ctl->sdio = annexsdio("M"); /* 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); } } (*ctl->sdio->bus)(ctl->sdio, 1, SDfreq); sdiocmd(ctl, &GO_IDLE_STATE, 0); ocr = trysdiocmd(ctl, &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(ctl, &IO_SEND_OP_COND, V3_3); tsleep(&up->sleep, return0, nil, 100); } rca = sdiocmd(ctl, &SEND_RELATIVE_ADDR, 0) >> Rcashift; sdiocmd(ctl, &SELECT_CARD, rca << Rcashift); sdioset(ctl, Fn0, Highspeed, 2); sdioset(ctl, Fn0, Busifc, 2); /* bus width 4 */ (*ctl->sdio->bus)(ctl->sdio, 4, SDfreqhs); sdiowr(ctl, Fn0, Fbr1+Blksize, 64); sdiowr(ctl, Fn0, Fbr1+Blksize+1, 64>>8); sdiowr(ctl, Fn0, Fbr2+Blksize, 512); sdiowr(ctl, Fn0, Fbr2+Blksize+1, 512>>8); sdioset(ctl, Fn0, Ioenable, 1<<Fn1); sdiowr(ctl, Fn0, Intenable, 0); for(i = 0; !(sdiord(ctl, 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(Ctlr *ctl) { sdiowr(ctl, Fn0, Ioabort, 1<<3); /* reset */ } static void sdioabort(Ctlr *ctl, int fn) { sdiowr(ctl, Fn0, Ioabort, fn); } /* * Chip register and memory access via SDIO */ static void cfgw(Ctlr *ctl, ulong off, int val) { sdiowr(ctl, Fn1, off, val); } static int cfgr(Ctlr *ctl, ulong off) { return sdiord(ctl, Fn1, off); } static ulong cfgreadl(Ctlr *ctl, int fn, ulong off) { uchar cbuf[2*CACHELINESZ]; uchar *p; p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ); memset(p, 0, 4); sdiorwext(ctl, 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(Ctlr *ctl, 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(ctl, fn); if(++retry == 3) nexterror(); } sdiorwext(ctl, fn, 1, p, 4, off|Sb32bit, 1); poperror(); } static void sbwindow(Ctlr *ctl, ulong addr) { addr &= ~(Sbwsize-1); cfgw(ctl, Sbaddr, addr>>8); cfgw(ctl, Sbaddr+1, addr>>16); cfgw(ctl, Sbaddr+2, addr>>24); } static void sbrw(Ctlr *ctl, 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(ctl, Fn1, write, buf, n, off|Sb32bit, 1); off += n; buf += n; len -= n; } while(len > 0){ sdiowr(ctl, Fn1, off|Sb32bit, *buf); off++; buf++; len--; } }else{ if(len >= 4){ n = len; n &= ~3; sdiorwext(ctl, Fn1, write, buf, n, off|Sb32bit, 1); off += n; buf += n; len -= n; } while(len > 0){ *buf = sdiord(ctl, Fn1, off|Sb32bit); off++; buf++; len--; } } poperror(); } static void sbmem(Ctlr *ctl, 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(ctl, off); sbrw(ctl, Fn1, write, buf, n, off & (Sbwsize-1)); off += n; buf += n; len -= n; n = Sbwsize; } } static void packetrw(Ctlr *ctl, 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(ctl, Fn2); if(++retry == 3) nexterror(); } sdiorwext(ctl, Fn2, write, buf, n, Enumbase, 0); poperror(); buf += n; len -= n; } } /* * Configuration and control of chip cores via Silicon Backplane */ static void sbdisable(Ctlr *ctl, ulong regs, int pre, int ioctl) { sbwindow(ctl, regs); if((cfgreadl(ctl, Fn1, regs + Resetctrl) & 1) != 0){ cfgwritel(ctl, Fn1, regs + Ioctrl, 3|ioctl); cfgreadl(ctl, Fn1, regs + Ioctrl); return; } cfgwritel(ctl, Fn1, regs + Ioctrl, 3|pre); cfgreadl(ctl, Fn1, regs + Ioctrl); cfgwritel(ctl, Fn1, regs + Resetctrl, 1); microdelay(10); while((cfgreadl(ctl, Fn1, regs + Resetctrl) & 1) == 0) ; cfgwritel(ctl, Fn1, regs + Ioctrl, 3|ioctl); cfgreadl(ctl, Fn1, regs + Ioctrl); } static void sbreset(Ctlr *ctl, ulong regs, int pre, int ioctl) { sbdisable(ctl, regs, pre, ioctl); sbwindow(ctl, regs); if(SBDEBUG) print("sbreset %#lux %#lux %#lux ->", regs, cfgreadl(ctl, Fn1, regs+Ioctrl), cfgreadl(ctl, Fn1, regs+Resetctrl)); while((cfgreadl(ctl, Fn1, regs + Resetctrl) & 1) != 0){ cfgwritel(ctl, Fn1, regs + Resetctrl, 0); microdelay(40); } cfgwritel(ctl, Fn1, regs + Ioctrl, 1|ioctl); cfgreadl(ctl, Fn1, regs + Ioctrl); if(SBDEBUG) print("%#lux %#lux\n", cfgreadl(ctl, Fn1, regs+Ioctrl), cfgreadl(ctl, 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(ctl, 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(ctl, r); n = cfgreadl(ctl, 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(ctl, Fn1, r + Cr4Bankidx, i); n = cfgreadl(ctl, 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, ctl->socramctl, 0, 0); r = ctl->socramregs; sbwindow(ctl, r); n = cfgreadl(ctl, 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(ctl, Fn1, r + Bankidx, i); n = cfgreadl(ctl, 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(ctl, Fn1, r + Bankidx, 3); cfgwritel(ctl, Fn1, r + Bankpda, 0); } } static void sbinit(Ctlr *ctl) { ulong r; int chipid; char buf[16]; sbwindow(ctl, Enumbase); r = cfgreadl(ctl, 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(ctl, 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, ctl->armctl, Cr4Cpuhalt, Cr4Cpuhalt); else sbdisable(ctl, ctl->armctl, 0, 0); sbreset(ctl, 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(ctl, Clkcsr, 0); microdelay(10); if(SBDEBUG) print("chipclk: %x\n", cfgr(ctl, Clkcsr)); cfgw(ctl, Clkcsr, Nohwreq | ReqALP); while((cfgr(ctl, Clkcsr) & (HTavail|ALPavail)) == 0) microdelay(10); cfgw(ctl, Clkcsr, Nohwreq | ForceALP); microdelay(65); if(SBDEBUG) print("chipclk: %x\n", cfgr(ctl, Clkcsr)); cfgw(ctl, Pullups, 0); sbwindow(ctl, ctl->chipcommon); cfgwritel(ctl, Fn1, ctl->chipcommon + Gpiopullup, 0); cfgwritel(ctl, Fn1, ctl->chipcommon + Gpiopulldown, 0); if(ctl->chipid != 0x4330 && ctl->chipid != 43362) return; cfgwritel(ctl, Fn1, ctl->chipcommon + Chipctladdr, 1); if(cfgreadl(ctl, Fn1, ctl->chipcommon + Chipctladdr) != 1) print("ether4330: can't set Chipctladdr\n"); else{ r = cfgreadl(ctl, Fn1, ctl->chipcommon + Chipctldata); if(SBDEBUG) print("chipcommon PMU (%lux) %lux", cfgreadl(ctl, Fn1, ctl->chipcommon + Chipctladdr), r); /* set SDIO drive strength >= 6mA */ r &= ~0x3800; if(ctl->chipid == 0x4330) r |= 3<<11; else r |= 7<<11; cfgwritel(ctl, Fn1, ctl->chipcommon + Chipctldata, r); if(SBDEBUG) print("-> %lux (= %lux)\n", r, cfgreadl(ctl, Fn1, ctl->chipcommon + Chipctldata)); } } static void sbenable(Ctlr *ctl) { int i; if(SBDEBUG) print("enabling HT clock..."); cfgw(ctl, Clkcsr, 0); delay(1); cfgw(ctl, Clkcsr, ReqHT); for(i = 0; (cfgr(ctl, Clkcsr) & HTavail) == 0; i++){ if(i == 50){ print("ether4330: can't enable HT clock: csr %x\n", cfgr(ctl, Clkcsr)); error(Eio); } tsleep(&up->sleep, return0, nil, 100); } cfgw(ctl, Clkcsr, cfgr(ctl, Clkcsr) | ForceHT); delay(10); if(SBDEBUG) print("chipclk: %x\n", cfgr(ctl, Clkcsr)); sbwindow(ctl, ctl->sdregs); cfgwritel(ctl, Fn1, ctl->sdregs + Sbmboxdata, 4 << 16); /* protocol version */ cfgwritel(ctl, Fn1, ctl->sdregs + Intmask, FrameInt | MailboxInt | Fcchange); sdioset(ctl, Fn0, Ioenable, 1<<Fn2); for(i = 0; !(sdiord(ctl, Fn0, Ioready) & 1<<Fn2); i++){ if(i == 10){ print("ether4330: can't enable SDIO function 2 - ioready %x\n", sdiord(ctl, Fn0, Ioready)); error(Eio); } tsleep(&up->sleep, return0, nil, 100); } sdiowr(ctl, 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(ctl, 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(ctl, 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(ctl, Clkcsr, ReqALP); while((cfgr(ctl, Clkcsr) & ALPavail) == 0) microdelay(10); memset(buf, 0, 4); sbmem(ctl, 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(ctl, 1, buf, 4, ctl->rambase + ctl->socramsize - 4); if(ctl->armcore == ARMcr4){ sbwindow(ctl, ctl->sdregs); cfgwritel(ctl, Fn1, ctl->sdregs + Intstatus, ~0); if(ctl->resetvec.i != 0){ if(SBDEBUG) print("%ux\n", ctl->resetvec.i); sbmem(ctl, 1, ctl->resetvec.c, sizeof(ctl->resetvec.c), 0); } sbreset(ctl, ctl->armctl, Cr4Cpuhalt, 0); }else sbreset(ctl, ctl->armctl, 0, 0); } /* * Communication of data and control packets */ static void intwait(Ctlr *ctl, int wait) { ulong ints, mbox; int i; if(waserror()) return; for(;;){ if(ctl->sdio->cardintr != nil) (*ctl->sdio->cardintr)(ctl->sdio, wait); sbwindow(ctl, ctl->sdregs); i = sdiord(ctl, Fn0, Intpend); if(i == 0){ tsleep(&up->sleep, return0, 0, 10); continue; } ints = cfgreadl(ctl, Fn1, ctl->sdregs + Intstatus); cfgwritel(ctl, Fn1, ctl->sdregs + Intstatus, ints); if(0) print("INTS: (%x) %lux -> %lux\n", i, ints, cfgreadl(ctl, Fn1, ctl->sdregs + Intstatus)); if(ints & MailboxInt){ mbox = cfgreadl(ctl, Fn1, ctl->sdregs + Hostmboxdata); cfgwritel(ctl, Fn1, ctl->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(ctl, 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(ctl, Framectl, Rfhalt); while(cfgr(ctl, Rfrmcnt+1)) ; while(cfgr(ctl, Rfrmcnt)) ; continue; } if(len > Sdpcmsz) packetrw(ctl, 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(ctl->iodebug) dump("send", b->rp, len); qlock(&ctl->pktlock); if(waserror()){ if(ctl->iodebug) print("halt frame %x %x\n", cfgr(ctl, Wfrmcnt+1), cfgr(ctl, Wfrmcnt+1)); cfgw(ctl, Framectl, Wfhalt); while(cfgr(ctl, Wfrmcnt+1)) ; while(cfgr(ctl, Wfrmcnt)) ; qunlock(&ctl->pktlock); nexterror(); } packetrw(ctl, 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(ctl->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(ctl->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(ctl->iodebug && BLEN(b) != p->doffset) print("short event %lld %d\n", BLEN(b), p->doffset); break; case 2: if(ctl->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 == Disconnected) return; ctl->status = Disconnected; edev->link = 0; /* 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 */ ctl->edev->link = 1; 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(ctl->iodebug) dump("cmd", b->rp, len); packetrw(ctl, 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->edev->link = 1; 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 rxmode(Ether *edev, int prom) { Ctlr *ctlr = edev->ctlr; wlsetint(ctlr, "allmulti", edev->nmaddr > 0); /* SET_PROMISC */ wlcmdint(ctlr, 10, prom); wlsetint(ctlr, "arp_ol", !prom); wlsetint(ctlr, "ndoe", !prom); } 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 */ rxmode(edev, edev->prom); //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 *ctl; 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", }; ctl = edev->ctlr; if(ctl == nil) return 0; p = malloc(READSTR); l = 0; l += snprint(p+l, READSTR-l, "channel: %d\n", ctl->chanid); l += snprint(p+l, READSTR-l, "essid: %s\n", ctl->essid); l += snprint(p+l, READSTR-l, "crypt: %s\n", cryptoname[ctl->cryptotype]); l += snprint(p+l, READSTR-l, "oq: %d\n", qlen(edev->oq)); l += snprint(p+l, READSTR-l, "txwin: %d\n", ctl->txwindow); l += snprint(p+l, READSTR-l, "txseq: %d\n", ctl->txseq); /* * hack: prevent aux/wpa from trying to connect while bypassed * as wljoin() generates spurious traffic which poisons the * switch port tables. */ if(edev->bypass == nil) l += snprint(p+l, READSTR-l, "status: %s\n", connectstate[ctl->status]); USED(l); n = readstr(offset, a, n, p); free(p); return n; } static void etherbcmtransmit(Ether *edev) { if(edev->ctlr == nil) return; txstart(edev); } static int wepparsekey(WKey* key, char *s) { uchar buf[WMaxKeyLen]; int len; len = strlen(s); if(len == WMinKeyLen || len == WMaxKeyLen){ key->len = len; memmove(key->dat, s, len); return 0; } else if(len == WMinKeyLen*2 || len == WMaxKeyLen*2){ len /= 2; if(dec16(buf, len, s, len*2) == len){ key->len = len; memmove(key->dat, buf, len); memset(buf, 0, sizeof buf); return 0; } } return -1; } static int wpaparsekey(WKey *key, uvlong *ivp, char *s) { uchar buf[WKeyLen]; int len; char *e; if(cistrncmp(s, "tkip:", 5) == 0 || cistrncmp(s, "ccmp:", 5) == 0) s += 5; else return 1; if((e = strchr(s, '@')) == nil) return 1; len = dec16(buf, sizeof buf, s, e - s); if(len <= 0) return 1; key->len = len; memmove(key->dat, buf, len); memset(buf, 0, sizeof buf); *ivp = strtoull(++e, nil, 16); return 0; } static void setauth(Ctlr *ctlr, Cmdbuf *cb, char *a) { uchar wpaie[32]; int i; i = dec16(wpaie, sizeof wpaie, a, strlen(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"); memset(cb->f[1], 0, strlen(cb->f[1])); 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"); memset(cb->f[2], 0, strlen(cb->f[2])); wlwpakey(ctlr, ct->index, iv, ea); break; case CMdebug: ctlr->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 *ctl; ctl = edev->ctlr; qlock(&ctl->alock); if(waserror()){ qunlock(&ctl->alock); nexterror(); } if(ctl->edev == nil){ if(ctl->chipid == 0){ sdioinit(ctl); sbinit(ctl); } fwload(ctl); sbenable(ctl); ctl->edev = edev; kproc("wifireader", rproc, edev); kproc("wifitimer", lproc, edev); if(ctl->regufile) reguload(ctl, ctl->regufile); wlinit(edev, ctl); } qunlock(&ctl->alock); poperror(); } static void etherbcmshutdown(Ether *edev) { Ctlr *ctl = edev->ctlr; if(ctl->sdio != nil) sdioreset(ctl); } static void etherbcmprom(void *arg, int on) { Ether *edev = arg; rxmode(edev, on); } static void etherbcmmulti(void *arg, uchar*, int) { Ether *edev = arg; rxmode(edev, edev->prom); } static int etherbcmpnp(Ether* edev) { static Ctlr *ctlr; if(ctlr != nil) return -1; ctlr = malloc(sizeof(Ctlr)); ctlr->chanid = Wifichan; edev->dmat = malloc(sizeof(DMAT)); edev->ctlr = ctlr; edev->attach = etherbcmattach; edev->transmit = etherbcmtransmit; edev->ifstat = etherbcmifstat; edev->ctl = etherbcmctl; edev->scanbs = etherbcmscan; edev->shutdown = etherbcmshutdown; edev->promiscuous = etherbcmprom; edev->multicast = etherbcmmulti; edev->arg = edev; return 0; } void ether4330link(void) { addethercard("4330", etherbcmpnp); }