plan9fox/sys/src/9/pc/wavelan.c
cinap_lenrek f0a314605f devether: remove (unimplemented) detach, allow device creation on attach
we allow devether to create ethernet cards on attach. this is useull
for virtual cards like the sink driver, so we can create a sink
by simply: bind -a '#l2:sink ea=112233445566' /net

the detach routine was never called, so remove it from the few drivers
that attempted to implement it.
2018-02-25 03:42:38 +01:00

1267 lines
27 KiB
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, &ltv->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, &ltv);
}
int
ltv_ins(Ctlr* ctlr, int type)
{
Wltv ltv;
ltv.len = 2;
ltv.type = type;
ltv.val = 0;
if(w_inltv(ctlr, &ltv))
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(&ltv, 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, &ltv);
}
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(&ltv,0,sizeof(ltv));
ltv.len = WNameLen/2+2;
ltv.type = type;
if(w_inltv(ctlr, &ltv))
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, &ltv);
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 = &ether->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, &ltv, 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, &ltv))
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, &ltv)){
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 %lx type %s",
ether->ctlrno, ether->irq, 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,
};