ref: dfb4b522830edab8f3856289d326d6cf5e930644
dir: /sys/src/9/pc/wavelan.c/
/* Lucent Wavelan IEEE 802.11 pcmcia. There is almost no documentation for the card. the driver is done using both the FreeBSD, Linux and original Plan 9 drivers as `documentation'. Has been used with the card plugged in during all up time. no cards removals/insertions yet. For known BUGS see the comments below. Besides, the driver keeps interrupts disabled for just too long. When it gets robust, locks should be revisited. BUGS: check endian, alignment and mem/io issues; receive watchdog interrupts. TODO: automatic power management; multicast filtering; improve locking. */ #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/etherif.h" #include "wavelan.h" enum { MSperTick= 50, /* ms between ticks of kproc */ }; /* * When we're using a PCI device and memory-mapped I/O, * the registers are spaced out as though each takes 32 bits, * even though they are only 16-bit registers. Thus, * ctlr->mmb[reg] is the right way to access register reg, * even though a priori you'd expect to use ctlr->mmb[reg/2]. */ void csr_outs(Ctlr *ctlr, int reg, ushort arg) { if(ctlr->mmb) ctlr->mmb[reg] = arg; else outs(ctlr->iob+reg, arg); } ushort csr_ins(Ctlr *ctlr, int reg) { if(ctlr->mmb) return ctlr->mmb[reg]; else return ins(ctlr->iob+reg); } static void csr_ack(Ctlr *ctlr, int ev) { csr_outs(ctlr, WR_EvAck, ev); } static void csr_inss(Ctlr *ctlr, int reg, void *dat, int ndat) { ushort *rp, *wp; if(ctlr->mmb){ rp = &ctlr->mmb[reg]; wp = dat; while(ndat-- > 0) *wp++ = *rp; }else inss(ctlr->iob+reg, dat, ndat); } static void csr_outss(Ctlr *ctlr, int reg, void *dat, int ndat) { ushort *rp, *wp; if(ctlr->mmb){ rp = dat; wp = &ctlr->mmb[reg]; while(ndat-- > 0) *wp = *rp++; }else outss(ctlr->iob+reg, dat, ndat); } // w_... routines do not ilock the Ctlr and should // be called locked. void w_intdis(Ctlr* ctlr) { csr_outs(ctlr, WR_IntEna, 0); csr_ack(ctlr, 0xffff); } static void w_intena(Ctlr* ctlr) { csr_outs(ctlr, WR_IntEna, WEvs); } int w_cmd(Ctlr *ctlr, ushort cmd, ushort arg) { int i, rc; for(i=0; i<WTmOut; i++) if((csr_ins(ctlr, WR_Cmd)&WCmdBusy) == 0) break; if(i==WTmOut){ print("#l%d: issuing cmd %.4ux: %.4ux\n", ctlr->ctlrno, cmd, csr_ins(ctlr, WR_Cmd)); return -1; } csr_outs(ctlr, WR_Parm0, arg); csr_outs(ctlr, WR_Cmd, cmd); for(i=0; i<WTmOut; i++) if(csr_ins(ctlr, WR_EvSts)&WCmdEv) break; if(i==WTmOut){ /* * WCmdIni can take a really long time. */ enum { IniTmOut = 2000 }; for(i=0; i<IniTmOut; i++){ if(csr_ins(ctlr, WR_EvSts)&WCmdEv) break; microdelay(100); } if(i < IniTmOut) if(0) print("#l%d: long cmd %.4ux %d\n", ctlr->ctlrno, cmd, i); if(i == IniTmOut){ print("#l%d: execing cmd %.4ux: %.4ux\n", ctlr->ctlrno, cmd, csr_ins(ctlr, WR_EvSts)); return -1; } } rc = csr_ins(ctlr, WR_Sts); csr_ack(ctlr, WCmdEv); if((rc&WCmdMsk) != (cmd&WCmdMsk)){ print("#l%d: cmd %.4ux: status %.4ux\n", ctlr->ctlrno, cmd, rc); return -1; } if(rc&WResSts){ /* * Don't print; this happens on every WCmdAccWr for some reason. */ if(0) print("#l%d: cmd %.4ux: status %.4ux\n", ctlr->ctlrno, cmd, rc); return -1; } return 0; } static int w_seek(Ctlr* ctlr, ushort id, ushort offset, int chan) { int i, rc; static ushort sel[] = { WR_Sel0, WR_Sel1 }; static ushort off[] = { WR_Off0, WR_Off1 }; if(chan != 0 && chan != 1) panic("wavelan: bad chan"); csr_outs(ctlr, sel[chan], id); csr_outs(ctlr, off[chan], offset); for (i=0; i<WTmOut; i++){ rc = csr_ins(ctlr, off[chan]); if((rc & (WBusyOff|WErrOff)) == 0) return 0; } return -1; } int w_inltv(Ctlr* ctlr, Wltv* ltv) { int len; ushort code; if(w_cmd(ctlr, WCmdAccRd, ltv->type)){ DEBUG("wavelan: access read failed\n"); return -1; } if(w_seek(ctlr,ltv->type,0,1)){ DEBUG("wavelan: seek failed\n"); return -1; } len = csr_ins(ctlr, WR_Data1); if(len > ltv->len) return -1; ltv->len = len; if((code=csr_ins(ctlr, WR_Data1)) != ltv->type){ USED(code); DEBUG("wavelan: type %x != code %x\n",ltv->type,code); return -1; } if(ltv->len > 0) csr_inss(ctlr, WR_Data1, <v->val, ltv->len-1); return 0; } static void w_outltv(Ctlr* ctlr, Wltv* ltv) { if(w_seek(ctlr,ltv->type, 0, 1)) return; csr_outss(ctlr, WR_Data1, ltv, ltv->len+1); w_cmd(ctlr, WCmdAccWr, ltv->type); } void ltv_outs(Ctlr* ctlr, int type, ushort val) { Wltv ltv; ltv.len = 2; ltv.type = type; ltv.val = val; w_outltv(ctlr, <v); } int ltv_ins(Ctlr* ctlr, int type) { Wltv ltv; ltv.len = 2; ltv.type = type; ltv.val = 0; if(w_inltv(ctlr, <v)) return -1; return ltv.val; } static void ltv_outstr(Ctlr* ctlr, int type, char* val) { Wltv ltv; int len; len = strlen(val); if(len > sizeof(ltv.s)) len = sizeof(ltv.s); memset(<v, 0, sizeof(ltv)); ltv.len = (sizeof(ltv.type)+sizeof(ltv.slen)+sizeof(ltv.s))/2; ltv.type = type; // This should be ltv.slen = len; according to Axel Belinfante ltv.slen = len; strncpy(ltv.s, val, len); w_outltv(ctlr, <v); } static char Unkname[] = "who knows"; static char Nilname[] = "card does not tell"; static char* ltv_inname(Ctlr* ctlr, int type) { static Wltv ltv; int len; memset(<v,0,sizeof(ltv)); ltv.len = WNameLen/2+2; ltv.type = type; if(w_inltv(ctlr, <v)) return Unkname; len = ltv.slen; if(len == 0 || ltv.s[0] == 0) return Nilname; if(len >= sizeof ltv.s) len = sizeof ltv.s - 1; ltv.s[len] = '\0'; return ltv.s; } static int w_read(Ctlr* ctlr, int type, int off, void* buf, ulong len) { if(w_seek(ctlr, type, off, 1)){ DEBUG("wavelan: w_read: seek failed"); return 0; } csr_inss(ctlr, WR_Data1, buf, len/2); return len; } static int w_write(Ctlr* ctlr, int type, int off, void* buf, ulong len) { if(w_seek(ctlr, type, off, 0)){ DEBUG("wavelan: w_write: seek failed\n"); return 0; } csr_outss(ctlr, WR_Data0, buf, len/2); csr_outs(ctlr, WR_Data0, 0xdead); csr_outs(ctlr, WR_Data0, 0xbeef); if(w_seek(ctlr, type, off + len, 0)){ DEBUG("wavelan: write seek failed\n"); return 0; } if(csr_ins(ctlr, WR_Data0) == 0xdead && csr_ins(ctlr, WR_Data0) == 0xbeef) return len; DEBUG("wavelan: Hermes bug byte.\n"); return 0; } static int w_alloc(Ctlr* ctlr, int len) { int rc; int i,j; if(w_cmd(ctlr, WCmdMalloc, len)==0) for (i = 0; i<WTmOut; i++) if(csr_ins(ctlr, WR_EvSts) & WAllocEv){ csr_ack(ctlr, WAllocEv); rc=csr_ins(ctlr, WR_Alloc); if(w_seek(ctlr, rc, 0, 0)) return -1; len = len/2; for (j=0; j<len; j++) csr_outs(ctlr, WR_Data0, 0); return rc; } return -1; } static int w_enable(Ether* ether) { Wltv ltv; Ctlr* ctlr = (Ctlr*) ether->ctlr; if(!ctlr) return -1; w_intdis(ctlr); w_cmd(ctlr, WCmdDis, 0); w_intdis(ctlr); if(w_cmd(ctlr, WCmdIni, 0)) return -1; w_intdis(ctlr); ltv_outs(ctlr, WTyp_Tick, 8); ltv_outs(ctlr, WTyp_MaxLen, ctlr->maxlen); ltv_outs(ctlr, WTyp_Ptype, ctlr->ptype); ltv_outs(ctlr, WTyp_CreateIBSS, ctlr->createibss); ltv_outs(ctlr, WTyp_RtsThres, ctlr->rtsthres); ltv_outs(ctlr, WTyp_TxRate, ctlr->txrate); ltv_outs(ctlr, WTyp_ApDens, ctlr->apdensity); ltv_outs(ctlr, WTyp_PMWait, ctlr->pmwait); ltv_outs(ctlr, WTyp_PM, ctlr->pmena); if(*ctlr->netname) ltv_outstr(ctlr, WTyp_NetName, ctlr->netname); if(*ctlr->wantname) ltv_outstr(ctlr, WTyp_WantName, ctlr->wantname); ltv_outs(ctlr, WTyp_Chan, ctlr->chan); if(*ctlr->nodename) ltv_outstr(ctlr, WTyp_NodeName, ctlr->nodename); ltv.len = 4; ltv.type = WTyp_Mac; memmove(ltv.addr, ether->ea, Eaddrlen); w_outltv(ctlr, <v); ltv_outs(ctlr, WTyp_Prom, (ether->prom?1:0)); if(ctlr->hascrypt && ctlr->crypt){ ltv_outs(ctlr, WTyp_Crypt, ctlr->crypt); ltv_outs(ctlr, WTyp_TxKey, ctlr->txkey); w_outltv(ctlr, &ctlr->keys); ltv_outs(ctlr, WTyp_XClear, ctlr->xclear); } // BUG: set multicast addresses if(w_cmd(ctlr, WCmdEna, 0)){ DEBUG("wavelan: Enable failed"); return -1; } ctlr->txdid = w_alloc(ctlr, 1518 + sizeof(WFrame) + 8); ctlr->txmid = w_alloc(ctlr, 1518 + sizeof(WFrame) + 8); if(ctlr->txdid == -1 || ctlr->txmid == -1) DEBUG("wavelan: alloc failed"); ctlr->txbusy = 0; w_intena(ctlr); return 0; } static void w_rxdone(Ether* ether) { Ctlr* ctlr = (Ctlr*) ether->ctlr; int len, sp; WFrame f; Block* bp=0; Etherpkt* ep; sp = csr_ins(ctlr, WR_RXId); len = w_read(ctlr, sp, 0, &f, sizeof(f)); if(len == 0){ DEBUG("wavelan: read frame error\n"); goto rxerror; } if(f.sts&WF_Err){ goto rxerror; } switch(f.sts){ case WF_1042: case WF_Tunnel: case WF_WMP: len = f.dlen + WSnapHdrLen; bp = iallocb(ETHERHDRSIZE + len + 2); if(!bp) goto rxerror; ep = (Etherpkt*) bp->wp; memmove(ep->d, f.addr1, Eaddrlen); memmove(ep->s, f.addr2, Eaddrlen); memmove(ep->type,&f.type,2); bp->wp += ETHERHDRSIZE; if(w_read(ctlr, sp, WF_802_11_Off, bp->wp, len+2) == 0){ DEBUG("wavelan: read 802.11 error\n"); goto rxerror; } bp->wp = bp->rp+(ETHERHDRSIZE+f.dlen); break; default: len = ETHERHDRSIZE + f.dlen + 2; bp = iallocb(len); if(!bp) goto rxerror; if(w_read(ctlr, sp, WF_802_3_Off, bp->wp, len) == 0){ DEBUG("wavelan: read 800.3 error\n"); goto rxerror; } bp->wp += len; } ctlr->nrx++; etheriq(ether, bp); ctlr->signal = ((ctlr->signal*15)+((f.qinfo>>8) & 0xFF))/16; ctlr->noise = ((ctlr->noise*15)+(f.qinfo & 0xFF))/16; return; rxerror: freeb(bp); ctlr->nrxerr++; } static void w_txstart(Ether* ether) { Etherpkt *pkt; Ctlr *ctlr; Block *bp; int len, off; if((ctlr = ether->ctlr) == nil || (ctlr->state & (Attached|Power)) != (Attached|Power) || ctlr->txbusy) return; if((bp = qget(ether->oq)) == nil) return; pkt = (Etherpkt*)bp->rp; // // If the packet header type field is > 1500 it is an IP or // ARP datagram, otherwise it is an 802.3 packet. See RFC1042. // memset(&ctlr->txf, 0, sizeof(ctlr->txf)); if(((pkt->type[0]<<8)|pkt->type[1]) > 1500){ ctlr->txf.framectl = WF_Data; memmove(ctlr->txf.addr1, pkt->d, Eaddrlen); memmove(ctlr->txf.addr2, pkt->s, Eaddrlen); memmove(ctlr->txf.dstaddr, pkt->d, Eaddrlen); memmove(ctlr->txf.srcaddr, pkt->s, Eaddrlen); memmove(&ctlr->txf.type, pkt->type, 2); bp->rp += ETHERHDRSIZE; len = BLEN(bp); off = WF_802_11_Off; ctlr->txf.dlen = len+ETHERHDRSIZE-WSnapHdrLen; hnputs((uchar*)&ctlr->txf.dat[0], WSnap0); hnputs((uchar*)&ctlr->txf.dat[1], WSnap1); hnputs((uchar*)&ctlr->txf.len, len+ETHERHDRSIZE-WSnapHdrLen); } else{ len = BLEN(bp); off = WF_802_3_Off; ctlr->txf.dlen = len; } w_write(ctlr, ctlr->txdid, 0, &ctlr->txf, sizeof(ctlr->txf)); w_write(ctlr, ctlr->txdid, off, bp->rp, len+2); if(w_cmd(ctlr, WCmdReclaim|WCmdTx, ctlr->txdid)){ DEBUG("wavelan: transmit failed\n"); ctlr->ntxerr++; } else{ ctlr->txbusy = 1; ctlr->txtmout = 2; } freeb(bp); } static void w_txdone(Ctlr* ctlr, int sts) { ctlr->txbusy = 0; ctlr->txtmout = 0; if(sts & WTxErrEv) ctlr->ntxerr++; else ctlr->ntx++; } /* save the stats info in the ctlr struct */ static void w_stats(Ctlr* ctlr, int len) { int i, rc; ulong* p = (ulong*)&ctlr->WStats; ulong* pend = (ulong*)&ctlr->end; for (i = 0; i < len && p < pend; i++){ rc = csr_ins(ctlr, WR_Data1); if(rc > 0xf000) rc = ~rc & 0xffff; p[i] += rc; } } /* send the base station scan info to any readers */ static void w_scaninfo(Ether* ether, Ctlr *ctlr, int len) { int i, j; Netfile **ep, *f, **fp; Block *bp; WScan *wsp; ushort *scanbuf; scanbuf = malloc(len*2); if(scanbuf == nil) return; for (i = 0; i < len ; i++) scanbuf[i] = csr_ins(ctlr, WR_Data1); /* calculate number of samples */ len /= 25; if(len == 0) goto out; i = ether->scan; ep = ðer->f[Ntypes]; for(fp = ether->f; fp < ep && i > 0; fp++){ f = *fp; if(f == nil || f->scan == 0) continue; bp = iallocb(100*len); if(bp == nil) break; for(j = 0; j < len; j++){ wsp = (WScan*)(&scanbuf[j*25]); if(wsp->ssid_len > 32) wsp->ssid_len = 32; bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim, "ssid=%.*s;bssid=%E;signal=%d;noise=%d;chan=%d%s\n", wsp->ssid_len, wsp->ssid, wsp->bssid, wsp->signal, wsp->noise, wsp->chan, (wsp->capinfo&(1<<4))?";wep":""); } qpass(f->in, bp); i--; } out: free(scanbuf); } static int w_info(Ether *ether, Ctlr* ctlr) { int sp; Wltv ltv; sp = csr_ins(ctlr, WR_InfoId); ltv.len = ltv.type = 0; w_read(ctlr, sp, 0, <v, 4); ltv.len--; switch(ltv.type){ case WTyp_Stats: w_stats(ctlr, ltv.len); return 0; case WTyp_Scan: w_scaninfo(ether, ctlr, ltv.len); return 0; } return -1; } /* set scanning interval */ static void w_scanbs(void *a, uint secs) { Ether *ether = a; Ctlr* ctlr = (Ctlr*) ether->ctlr; ctlr->scanticks = secs*(1000/MSperTick); } static void w_intr(Ether *ether) { int rc, txid; Ctlr* ctlr = (Ctlr*) ether->ctlr; if((ctlr->state & Power) == 0) return; if((ctlr->state & Attached) == 0){ csr_ack(ctlr, 0xffff); csr_outs(ctlr, WR_IntEna, 0); return; } rc = csr_ins(ctlr, WR_EvSts); csr_ack(ctlr, ~WEvs); // Not interested in them if(rc & WRXEv){ w_rxdone(ether); csr_ack(ctlr, WRXEv); } if(rc & WTXEv){ w_txdone(ctlr, rc); csr_ack(ctlr, WTXEv); } if(rc & WAllocEv){ ctlr->nalloc++; txid = csr_ins(ctlr, WR_Alloc); csr_ack(ctlr, WAllocEv); if(txid == ctlr->txdid){ if((rc & WTXEv) == 0) w_txdone(ctlr, rc); } } if(rc & WInfoEv){ ctlr->ninfo++; w_info(ether, ctlr); csr_ack(ctlr, WInfoEv); } if(rc & WTxErrEv){ w_txdone(ctlr, rc); csr_ack(ctlr, WTxErrEv); } if(rc & WIDropEv){ ctlr->nidrop++; csr_ack(ctlr, WIDropEv); } w_txstart(ether); } // Watcher to ensure that the card still works properly and // to request WStats updates once a minute. // BUG: it runs much more often, see the comment below. static void w_timer(void* arg) { Ether* ether = (Ether*) arg; Ctlr* ctlr = (Ctlr*)ether->ctlr; ctlr->timerproc = up; while(waserror()) ; for(;;){ tsleep(&up->sleep, return0, 0, MSperTick); ctlr = (Ctlr*)ether->ctlr; if(ctlr == 0) break; if((ctlr->state & (Attached|Power)) != (Attached|Power)) continue; ctlr->ticks++; ilock(ctlr); // Seems that the card gets frames BUT does // not send the interrupt; this is a problem because // I suspect it runs out of receive buffers and // stops receiving until a transmit watchdog // reenables the card. // The problem is serious because it leads to // poor rtts. // This can be seen clearly by commenting out // the next if and doing a ping: it will stop // receiving (although the icmp replies are being // issued from the remote) after a few seconds. // Of course this `bug' could be because I'm reading // the card frames in the wrong way; due to the // lack of documentation I cannot know. if(csr_ins(ctlr, WR_EvSts)&WEvs){ ctlr->tickintr++; w_intr(ether); } if((ctlr->ticks % 10) == 0) { if(ctlr->txtmout && --ctlr->txtmout == 0){ ctlr->nwatchdogs++; w_txdone(ctlr, WTxErrEv); if(w_enable(ether)){ DEBUG("wavelan: wdog enable failed\n"); } w_txstart(ether); } if((ctlr->ticks % 120) == 0) if(ctlr->txbusy == 0) w_cmd(ctlr, WCmdEnquire, WTyp_Stats); if(ctlr->scanticks > 0) if((ctlr->ticks % ctlr->scanticks) == 0) if(ctlr->txbusy == 0) w_cmd(ctlr, WCmdEnquire, WTyp_Scan); } iunlock(ctlr); } pexit("terminated", 1); } void w_multicast(void *ether, uchar*, int add) { /* BUG: use controller's multicast filter */ if (add) w_promiscuous(ether, 1); } void w_attach(Ether* ether) { Ctlr* ctlr; char name[64]; int rc; if(ether->ctlr == 0) return; snprint(name, sizeof(name), "#l%dtimer", ether->ctlrno); ctlr = (Ctlr*) ether->ctlr; if((ctlr->state & Attached) == 0){ ilock(ctlr); rc = w_enable(ether); iunlock(ctlr); if(rc == 0){ ctlr->state |= Attached; kproc(name, w_timer, ether); } else print("#l%d: enable failed\n",ether->ctlrno); } } void w_detach(Ether* ether) { Ctlr* ctlr; char name[64]; if(ether->ctlr == nil) return; snprint(name, sizeof(name), "#l%dtimer", ether->ctlrno); ctlr = (Ctlr*) ether->ctlr; if(ctlr->state & Attached){ ilock(ctlr); w_intdis(ctlr); if(ctlr->timerproc){ if(!postnote(ctlr->timerproc, 1, "kill", NExit)) print("timerproc note not posted\n"); print("w_detach, killing 0x%p\n", ctlr->timerproc); } ctlr->state &= ~Attached; iunlock(ctlr); } ether->ctlr = nil; } void w_power(Ether* ether, int on) { Ctlr *ctlr; ctlr = (Ctlr*) ether->ctlr; ilock(ctlr); iprint("w_power %d\n", on); if(on){ if((ctlr->state & Power) == 0){ if (wavelanreset(ether, ctlr) < 0){ iprint("w_power: reset failed\n"); iunlock(ctlr); w_detach(ether); free(ctlr); return; } if(ctlr->state & Attached) w_enable(ether); ctlr->state |= Power; } }else{ if(ctlr->state & Power){ if(ctlr->state & Attached) w_intdis(ctlr); ctlr->state &= ~Power; } } iunlock(ctlr); } #define PRINTSTAT(fmt,val) l += snprint(p+l, READSTR-l, (fmt), (val)) #define PRINTSTR(fmt) l += snprint(p+l, READSTR-l, (fmt)) long w_ifstat(Ether* ether, void* a, long n, ulong offset) { Ctlr *ctlr = (Ctlr*) ether->ctlr; char *k, *p; int i, l, txid; ether->oerrs = ctlr->ntxerr; ether->crcs = ctlr->nrxfcserr; ether->frames = 0; ether->buffs = ctlr->nrxdropnobuf; ether->overflows = 0; // // Offset must be zero or there's a possibility the // new data won't match the previous read. // if(n == 0 || offset != 0) return 0; p = smalloc(READSTR); l = 0; PRINTSTAT("Signal: %d\n", ctlr->signal-149); PRINTSTAT("Noise: %d\n", ctlr->noise-149); PRINTSTAT("SNR: %ud\n", ctlr->signal-ctlr->noise); PRINTSTAT("Interrupts: %lud\n", ctlr->nints); PRINTSTAT("Double Interrupts: %lud\n", ctlr->ndoubleint); PRINTSTAT("TxPackets: %lud\n", ctlr->ntx); PRINTSTAT("RxPackets: %lud\n", ctlr->nrx); PRINTSTAT("TxErrors: %lud\n", ctlr->ntxerr); PRINTSTAT("RxErrors: %lud\n", ctlr->nrxerr); PRINTSTAT("TxRequests: %lud\n", ctlr->ntxrq); PRINTSTAT("AllocEvs: %lud\n", ctlr->nalloc); PRINTSTAT("InfoEvs: %lud\n", ctlr->ninfo); PRINTSTAT("InfoDrop: %lud\n", ctlr->nidrop); PRINTSTAT("WatchDogs: %lud\n", ctlr->nwatchdogs); PRINTSTAT("Ticks: %ud\n", ctlr->ticks); PRINTSTAT("TickIntr: %ud\n", ctlr->tickintr); k = ((ctlr->state & Attached) ? "attached" : "not attached"); PRINTSTAT("Card %s", k); k = ((ctlr->state & Power) ? "on" : "off"); PRINTSTAT(", power %s", k); k = ((ctlr->txbusy)? ", txbusy" : ""); PRINTSTAT("%s\n", k); if(ctlr->hascrypt){ PRINTSTR("Keys: "); for (i = 0; i < WNKeys; i++){ if(ctlr->keys.keys[i].len == 0) PRINTSTR("none "); else if(SEEKEYS == 0) PRINTSTR("set "); else PRINTSTAT("%s ", ctlr->keys.keys[i].dat); } PRINTSTR("\n"); } // real card stats ilock(ctlr); PRINTSTR("\nCard stats: \n"); PRINTSTAT("Status: %ux\n", csr_ins(ctlr, WR_Sts)); PRINTSTAT("Event status: %ux\n", csr_ins(ctlr, WR_EvSts)); i = ltv_ins(ctlr, WTyp_Ptype); PRINTSTAT("Port type: %d\n", i); PRINTSTAT("Transmit rate: %d\n", ltv_ins(ctlr, WTyp_TxRate)); PRINTSTAT("Current Transmit rate: %d\n", ltv_ins(ctlr, WTyp_CurTxRate)); PRINTSTAT("Channel: %d\n", ltv_ins(ctlr, WTyp_Chan)); PRINTSTAT("AP density: %d\n", ltv_ins(ctlr, WTyp_ApDens)); PRINTSTAT("Promiscuous mode: %d\n", ltv_ins(ctlr, WTyp_Prom)); if(i == WPTypeAdHoc) PRINTSTAT("SSID name: %s\n", ltv_inname(ctlr, WTyp_NetName)); else { Wltv ltv; PRINTSTAT("Current name: %s\n", ltv_inname(ctlr, WTyp_CurName)); ltv.type = WTyp_BaseID; ltv.len = 4; if(w_inltv(ctlr, <v)) print("#l%d: unable to read base station mac addr\n", ether->ctlrno); l += snprint(p+l, READSTR-l, "Base station: %2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n", ltv.addr[0], ltv.addr[1], ltv.addr[2], ltv.addr[3], ltv.addr[4], ltv.addr[5]); } PRINTSTAT("Net name: %s\n", ltv_inname(ctlr, WTyp_WantName)); PRINTSTAT("Node name: %s\n", ltv_inname(ctlr, WTyp_NodeName)); if(ltv_ins(ctlr, WTyp_HasCrypt) == 0) PRINTSTR("WEP: not supported\n"); else { if(ltv_ins(ctlr, WTyp_Crypt) == 0) PRINTSTR("WEP: disabled\n"); else{ PRINTSTR("WEP: enabled\n"); k = ((ctlr->xclear)? "excluded": "included"); PRINTSTAT("Clear packets: %s\n", k); txid = ltv_ins(ctlr, WTyp_TxKey); PRINTSTAT("Transmit key id: %d\n", txid); } } iunlock(ctlr); PRINTSTAT("ntxuframes: %lud\n", ctlr->ntxuframes); PRINTSTAT("ntxmframes: %lud\n", ctlr->ntxmframes); PRINTSTAT("ntxfrags: %lud\n", ctlr->ntxfrags); PRINTSTAT("ntxubytes: %lud\n", ctlr->ntxubytes); PRINTSTAT("ntxmbytes: %lud\n", ctlr->ntxmbytes); PRINTSTAT("ntxdeferred: %lud\n", ctlr->ntxdeferred); PRINTSTAT("ntxsretries: %lud\n", ctlr->ntxsretries); PRINTSTAT("ntxmultiretries: %lud\n", ctlr->ntxmultiretries); PRINTSTAT("ntxretrylimit: %lud\n", ctlr->ntxretrylimit); PRINTSTAT("ntxdiscards: %lud\n", ctlr->ntxdiscards); PRINTSTAT("nrxuframes: %lud\n", ctlr->nrxuframes); PRINTSTAT("nrxmframes: %lud\n", ctlr->nrxmframes); PRINTSTAT("nrxfrags: %lud\n", ctlr->nrxfrags); PRINTSTAT("nrxubytes: %lud\n", ctlr->nrxubytes); PRINTSTAT("nrxmbytes: %lud\n", ctlr->nrxmbytes); PRINTSTAT("nrxfcserr: %lud\n", ctlr->nrxfcserr); PRINTSTAT("nrxdropnobuf: %lud\n", ctlr->nrxdropnobuf); PRINTSTAT("nrxdropnosa: %lud\n", ctlr->nrxdropnosa); PRINTSTAT("nrxcantdecrypt: %lud\n", ctlr->nrxcantdecrypt); PRINTSTAT("nrxmsgfrag: %lud\n", ctlr->nrxmsgfrag); PRINTSTAT("nrxmsgbadfrag: %lud\n", ctlr->nrxmsgbadfrag); USED(l); n = readstr(offset, a, n, p); free(p); return n; } #undef PRINTSTR #undef PRINTSTAT static int parsekey(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; } int w_option(Ctlr* ctlr, char* buf, long n) { char *p; int i, r; Cmdbuf *cb; r = 0; cb = parsecmd(buf, n); if(cb->nf < 2) r = -1; else if(cistrcmp(cb->f[0], "essid") == 0){ if(cistrcmp(cb->f[1],"default") == 0) p = ""; else p = cb->f[1]; if(ctlr->ptype == WPTypeAdHoc){ memset(ctlr->netname, 0, sizeof(ctlr->netname)); strncpy(ctlr->netname, p, WNameLen-1); } else{ memset(ctlr->wantname, 0, sizeof(ctlr->wantname)); strncpy(ctlr->wantname, p, WNameLen-1); } } else if(cistrcmp(cb->f[0], "station") == 0){ memset(ctlr->nodename, 0, sizeof(ctlr->nodename)); strncpy(ctlr->nodename, cb->f[1], WNameLen-1); } else if(cistrcmp(cb->f[0], "channel") == 0){ if((i = atoi(cb->f[1])) >= 1 && i <= 16) ctlr->chan = i; else r = -1; } else if(cistrcmp(cb->f[0], "mode") == 0){ if(cistrcmp(cb->f[1], "managed") == 0) ctlr->ptype = WPTypeManaged; else if(cistrcmp(cb->f[1], "wds") == 0) ctlr->ptype = WPTypeWDS; else if(cistrcmp(cb->f[1], "adhoc") == 0) ctlr->ptype = WPTypeAdHoc; else if((i = atoi(cb->f[1])) >= 0 && i <= 3) ctlr->ptype = i; else r = -1; } else if(cistrcmp(cb->f[0], "ibss") == 0){ if(cistrcmp(cb->f[1], "on") == 0) ctlr->createibss = 1; else ctlr->createibss = 0; } else if(cistrcmp(cb->f[0], "crypt") == 0){ if(cistrcmp(cb->f[1], "off") == 0) ctlr->crypt = 0; else if(cistrcmp(cb->f[1], "on") == 0 && ctlr->hascrypt) ctlr->crypt = 1; else r = -1; } else if(cistrcmp(cb->f[0], "clear") == 0){ if(cistrcmp(cb->f[1], "on") == 0) ctlr->xclear = 0; else if(cistrcmp(cb->f[1], "off") == 0 && ctlr->hascrypt) ctlr->xclear = 1; else r = -1; } else if(cistrncmp(cb->f[0], "key", 3) == 0){ if((i = atoi(cb->f[0]+3)) >= 1 && i <= WNKeys){ ctlr->txkey = i-1; if(parsekey(&ctlr->keys.keys[ctlr->txkey], cb->f[1])) r = -1; } else r = -1; } else if(cistrcmp(cb->f[0], "txkey") == 0){ if((i = atoi(cb->f[1])) >= 1 && i <= WNKeys) ctlr->txkey = i-1; else r = -1; } else if(cistrcmp(cb->f[0], "pm") == 0){ if(cistrcmp(cb->f[1], "off") == 0) ctlr->pmena = 0; else if(cistrcmp(cb->f[1], "on") == 0){ ctlr->pmena = 1; if(cb->nf == 3){ i = atoi(cb->f[2]); // check range here? what are the units? ctlr->pmwait = i; } } else r = -1; } else r = -2; free(cb); return r; } long w_ctl(Ether* ether, void* buf, long n) { Ctlr *ctlr; if((ctlr = ether->ctlr) == nil) error(Enonexist); if((ctlr->state & Attached) == 0) error(Eshutdown); ilock(ctlr); if(w_option(ctlr, buf, n)){ iunlock(ctlr); error(Ebadctl); } if(ctlr->txbusy) w_txdone(ctlr, WTxErrEv); w_enable(ether); w_txstart(ether); iunlock(ctlr); return n; } void w_transmit(Ether* ether) { Ctlr* ctlr = ether->ctlr; if(ctlr == 0) return; ilock(ctlr); ctlr->ntxrq++; w_txstart(ether); iunlock(ctlr); } void w_promiscuous(void* arg, int on) { Ether* ether = (Ether*)arg; Ctlr* ctlr = ether->ctlr; if(ctlr == nil) error("card not found"); if((ctlr->state & Attached) == 0) error("card not attached"); ilock(ctlr); ltv_outs(ctlr, WTyp_Prom, (on?1:0)); iunlock(ctlr); } void w_interrupt(Ureg* ,void* arg) { Ether* ether = (Ether*) arg; Ctlr* ctlr = (Ctlr*) ether->ctlr; if(ctlr == 0) return; ilock(ctlr); ctlr->nints++; w_intr(ether); iunlock(ctlr); } int wavelanreset(Ether* ether, Ctlr *ctlr) { Wltv ltv; iprint("wavelanreset, iob 0x%ux\n", ctlr->iob); w_intdis(ctlr); if(w_cmd(ctlr,WCmdIni,0)){ iprint("#l%d: init failed\n", ether->ctlrno); return -1; } w_intdis(ctlr); ltv_outs(ctlr, WTyp_Tick, 8); ctlr->chan = 0; ctlr->ptype = WDfltPType; ctlr->txkey = 0; ctlr->createibss = 0; ctlr->keys.len = sizeof(WKey)*WNKeys/2 + 1; ctlr->keys.type = WTyp_Keys; if(ctlr->hascrypt = ltv_ins(ctlr, WTyp_HasCrypt)) ctlr->crypt = 1; *ctlr->netname = *ctlr->wantname = 0; strcpy(ctlr->nodename, "Plan 9 STA"); ctlr->netname[WNameLen-1] = 0; ctlr->wantname[WNameLen-1] = 0; ctlr->nodename[WNameLen-1] =0; ltv.type = WTyp_Mac; ltv.len = 4; if(w_inltv(ctlr, <v)){ iprint("#l%d: unable to read mac addr\n", ether->ctlrno); return -1; } memmove(ether->ea, ltv.addr, Eaddrlen); if(ctlr->chan == 0) ctlr->chan = ltv_ins(ctlr, WTyp_Chan); ctlr->apdensity = WDfltApDens; ctlr->rtsthres = WDfltRtsThres; ctlr->txrate = WDfltTxRate; ctlr->maxlen = WMaxLen; ctlr->pmena = 0; ctlr->pmwait = 100; ctlr->signal = 1; ctlr->noise = 1; ctlr->state |= Power; // free old Ctlr struct if resetting after suspend if(ether->ctlr && ether->ctlr != ctlr) free(ether->ctlr); // link to ether ether->ctlr = ctlr; ether->mbps = 10; ether->attach = w_attach; ether->transmit = w_transmit; ether->ifstat = w_ifstat; ether->ctl = w_ctl; ether->power = w_power; ether->promiscuous = w_promiscuous; ether->multicast = w_multicast; ether->scanbs = w_scanbs; ether->arg = ether; intrenable(ether->irq, w_interrupt, ether, ether->tbdf, ether->name); DEBUG("#l%d: irq %d port %llux type %s", ether->ctlrno, ether->irq, (uvlong)ether->port, ether->type); DEBUG(" %2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux\n", ether->ea[0], ether->ea[1], ether->ea[2], ether->ea[3], ether->ea[4], ether->ea[5]); return 0; } char* wavenames[] = { "WaveLAN/IEEE", "TrueMobile 1150", "Instant Wireless ; Network PC CARD", "Instant Wireless Network PC Card", "Avaya Wireless PC Card", "AirLancer MC-11", "INTERSIL;HFA384x/IEEE;Version 01.02;", nil, };