/*
* usb/ether - usb ethernet adapter.
* BUG: This should use /dev/etherfile to
* use the kernel ether device code.
*/
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include "usb.h"
#include "usbfs.h"
#include "ether.h"
typedef struct Dirtab Dirtab;
enum
{
/* Qids. Maintain order (relative to dirtabs structs) */
Qroot = 0,
Qclone,
Qaddr,
Qifstats,
Qstats,
Qndir,
Qndata,
Qnctl,
Qnifstats,
Qnstats,
Qntype,
Qmax,
};
struct Dirtab
{
char *name;
int qid;
int mode;
};
typedef int (*Resetf)(Ether*);
/*
* Controllers by vid/vid. Used to locate
* specific adapters that do not implement cdc ethernet
* Keep null terminated.
*/
Cinfo cinfo[] =
{
/* Asix controllers.
* Only A88178 and A88772 are implemented.
* Others are easy to add by borrowing code
* from other systems.
*/
{0x0411, 0x003d, A8817x}, /* buffalo */
{0x0411, 0x006e, A88178},
{0x04f1, 0x3008, A8817x},
{0x050d, 0x5055, A88178},
{0x0557, 0x2009, A8817x},
{0x05ac, 0x1402, A88772}, /* Apple */
{0x077b, 0x2226, A8817x},
{0x07aa, 0x0017, A8817x},
{0x07d1, 0x3c05, A88772},
{0x0b95, 0x1720, A8817x}, /* asix */
{0x0b95, 0x1780, A88178}, /* Geoff */
{0x0b95, 0x7720, A88772},
{0x0b95, 0x772a, A88772},
{0x0db0, 0xa877, A88772},
{0x1189, 0x0893, A8817x},
{0x13b1, 0x0018, A88772},
{0x14ea, 0xab11, A88178},
{0x1557, 0x7720, A88772},
{0x1631, 0x6200, A8817x},
{0x1737, 0x0039, A88178},
{0x2001, 0x3c05, A88772},
{0x6189, 0x182d, A8817x},
/* SMSC controllers.
* LAN95xx family
*/
{0x0424, 0xec00, S95xx}, /* LAN9512 (as in raspberry pi) */
{0x0424, 0x9500, S95xx},
{0x0424, 0x9505, S95xx},
{0x0424, 0x9E00, S95xx},
{0x0424, 0x9E01, S95xx},
{0, 0, 0},
};
/*
* Each etherU%d is the root of our file system,
* which is added to the usb root directory. We only
* have to concern ourselfs with each /etherU%d subtree.
*
* NB: Maintain order in dirtabs, relative to the Qids enum.
*/
static Dirtab rootdirtab[] =
{
"/", Qroot, DMDIR|0555, /* etherU%d */
"clone", Qclone, 0666,
"addr", Qaddr, 0444,
"ifstats", Qifstats, 0444,
"stats", Qstats, 0444,
/* one dir per connection here */
nil, 0, 0,
};
static Dirtab conndirtab[] =
{
"%d", Qndir, DMDIR|0555,
"data", Qndata, 0666,
"ctl", Qnctl, 0666,
"ifstats", Qnifstats, 0444,
"stats", Qnstats, 0444,
"type", Qntype, 0444,
nil, 0,
};
int etherdebug;
Resetf ethers[] =
{
asixreset,
smscreset,
cdcreset, /* keep last */
};
static int
qtype(vlong q)
{
return q&0xFF;
}
static int
qnum(vlong q)
{
return (q >> 8) & 0xFFFFFF;
}
static uvlong
mkqid(int n, int t)
{
uvlong q;
q = (n&0xFFFFFF) << 8 | t&0xFF;
return q;
}
static void
freebuf(Ether *e, Buf *bp)
{
if(0)deprint(2, "%s: freebuf %#p\n", argv0, bp);
if(bp != nil){
qlock(e);
e->nbufs--;
qunlock(e);
sendp(e->bc, bp);
}
}
static Buf*
allocbuf(Ether *e)
{
Buf *bp;
bp = nbrecvp(e->bc);
if(bp == nil){
qlock(e);
if(e->nabufs < Nbufs){
bp = emallocz(sizeof(Buf), 1);
e->nabufs++;
setmalloctag(bp, getcallerpc(&e));
deprint(2, "%s: %d buffers\n", argv0, e->nabufs);
}
qunlock(e);
}
if(bp == nil){
deprint(2, "%s: blocked waiting for allocbuf\n", argv0);
bp = recvp(e->bc);
}
bp->rp = bp->data + Hdrsize;
bp->ndata = 0;
if(0)deprint(2, "%s: allocbuf %#p\n", argv0, bp);
qlock(e);
e->nbufs++;
qunlock(e);
return bp;
}
static Conn*
newconn(Ether *e)
{
int i;
Conn *c;
qlock(e);
for(i = 0; i < nelem(e->conns); i++){
c = e->conns[i];
if(c == nil || c->ref == 0){
if(c == nil){
c = emallocz(sizeof(Conn), 1);
c->rc = chancreate(sizeof(Buf*), 16);
c->nb = i;
}
c->ref = 1;
if(i == e->nconns)
e->nconns++;
e->conns[i] = c;
deprint(2, "%s: newconn %d\n", argv0, i);
qunlock(e);
return c;
}
}
qunlock(e);
return nil;
}
static char*
seprintaddr(char *s, char *se, uchar *addr)
{
int i;
for(i = 0; i < Eaddrlen; i++)
s = seprint(s, se, "%02x", addr[i]);
return s;
}
void
dumpframe(char *tag, void *p, int n)
{
Etherpkt *ep;
char buf[128];
char *s, *se;
int i;
ep = p;
if(n < Eaddrlen * 2 + 2){
fprint(2, "short packet (%d bytes)\n", n);
return;
}
se = buf+sizeof(buf);
s = seprint(buf, se, "%s [%d]: ", tag, n);
s = seprintaddr(s, se, ep->s);
s = seprint(s, se, " -> ");
s = seprintaddr(s, se, ep->d);
s = seprint(s, se, " type 0x%02ux%02ux ", ep->type[0], ep->type[1]);
n -= Eaddrlen * 2 + 2;
for(i = 0; i < n && i < 16; i++)
s = seprint(s, se, "%02x", ep->data[i]);
if(n >= 16)
fprint(2, "%s...\n", buf);
else
fprint(2, "%s\n", buf);
}
static char*
seprintstats(char *s, char *se, Ether *e)
{
qlock(e);
s = seprint(s, se, "in: %ld\n", e->nin);
s = seprint(s, se, "out: %ld\n", e->nout);
s = seprint(s, se, "input errs: %ld\n", e->nierrs);
s = seprint(s, se, "output errs: %ld\n", e->noerrs);
s = seprint(s, se, "mbps: %d\n", e->mbps);
s = seprint(s, se, "prom: %ld\n", e->prom.ref);
s = seprint(s, se, "addr: ");
s = seprintaddr(s, se, e->addr);
s = seprint(s, se, "\n");
qunlock(e);
return s;
}
static char*
seprintifstats(char *s, char *se, Ether *e)
{
int i;
Conn *c;
qlock(e);
s = seprint(s, se, "ctlr id: %#x\n", e->cid);
s = seprint(s, se, "phy: %#x\n", e->phy);
s = seprint(s, se, "exiting: %s\n", e->exiting ? "y" : "n");
s = seprint(s, se, "conns: %d\n", e->nconns);
s = seprint(s, se, "allocated bufs: %d\n", e->nabufs);
s = seprint(s, se, "used bufs: %d\n", e->nbufs);
for(i = 0; i < nelem(e->conns); i++){
c = e->conns[i];
if(c == nil)
continue;
if(c->ref == 0)
s = seprint(s, se, "c[%d]: free\n", i);
else{
s = seprint(s, se, "c[%d]: refs %ld t %#x h %d p %d\n",
c->nb, c->ref, c->type, c->headersonly, c->prom);
}
}
qunlock(e);
return s;
}
static void
etherdump(Ether *e)
{
char buf[256];
if(etherdebug == 0)
return;
seprintifstats(buf, buf+sizeof(buf), e);
fprint(2, "%s: ether %#p:\n%s\n", argv0, e, buf);
}
static Conn*
getconn(Ether *e, int i, int idleok)
{
Conn *c;
qlock(e);
if(i < 0 || i >= e->nconns)
c = nil;
else{
c = e->conns[i];
if(idleok == 0 && c != nil && c->ref == 0)
c = nil;
}
qunlock(e);
return c;
}
static void
filldir(Usbfs *fs, Dir *d, Dirtab *tab, int cn)
{
d->qid.path = mkqid(cn, tab->qid);
d->qid.path |= fs->qid;
d->mode = tab->mode;
if((d->mode & DMDIR) != 0)
d->qid.type = QTDIR;
else
d->qid.type = QTFILE;
if(tab->qid == Qndir)
snprint(d->name, Namesz, "%d", cn);
else
d->name = tab->name;
}
static int
rootdirgen(Usbfs *fs, Qid, int i, Dir *d, void *)
{
Ether *e;
Dirtab *tab;
int cn;
e = fs->aux;
i++; /* skip root */
cn = 0;
if(i < nelem(rootdirtab) - 1) /* null terminated */
tab = &rootdirtab[i];
else{
cn = i - nelem(rootdirtab) + 1;
if(cn < e->nconns)
tab = &conndirtab[0];
else
return -1;
}
filldir(fs, d, tab, cn);
return 0;
}
static int
conndirgen(Usbfs *fs, Qid q, int i, Dir *d, void *)
{
Dirtab *tab;
i++; /* skip root */
if(i < nelem(conndirtab) - 1) /* null terminated */
tab = &conndirtab[i];
else
return -1;
filldir(fs, d, tab, qnum(q.path));
return 0;
}
static int
fswalk(Usbfs *fs, Fid *fid, char *name)
{
int cn, i;
char *es;
Dirtab *tab;
Ether *e;
Qid qid;
e = fs->aux;
qid = fid->qid;
qid.path &= ~fs->qid;
if((qid.type & QTDIR) == 0){
werrstr("walk in non-directory");
return -1;
}
if(strcmp(name, "..") == 0){
/* must be /etherU%d; i.e. our root dir. */
fid->qid.path = mkqid(0, Qroot) | fs->qid;
fid->qid.vers = 0;
fid->qid.type = QTDIR;
return 0;
}
switch(qtype(qid.path)){
case Qroot:
if(name[0] >= '0' && name[0] <= '9'){
es = name;
cn = strtoul(name, &es, 10);
if(cn >= e->nconns || *es != 0){
werrstr(Enotfound);
return -1;
}
fid->qid.path = mkqid(cn, Qndir) | fs->qid;
fid->qid.vers = 0;
return 0;
}
/* fall */
case Qndir:
if(qtype(qid.path) == Qroot)
tab = rootdirtab;
else
tab = conndirtab;
cn = qnum(qid.path);
for(i = 0; tab[i].name != nil; tab++)
if(strcmp(tab[i].name, name) == 0){
fid->qid.path = mkqid(cn, tab[i].qid)|fs->qid;
fid->qid.vers = 0;
if((tab[i].mode & DMDIR) != 0)
fid->qid.type = QTDIR;
else
fid->qid.type = QTFILE;
return 0;
}
break;
default:
sysfatal("usb: ether: fswalk bug");
}
return -1;
}
static Dirtab*
qdirtab(vlong q)
{
int i, qt;
Dirtab *tab;
qt = qtype(q);
if(qt < nelem(rootdirtab) - 1){ /* null terminated */
tab = rootdirtab;
i = qt;
}else{
tab = conndirtab;
i = qt - (nelem(rootdirtab) - 1);
assert(i < nelem(conndirtab) - 1);
}
return &tab[i];
}
static int
fsstat(Usbfs *fs, Qid qid, Dir *d)
{
filldir(fs, d, qdirtab(qid.path), qnum(qid.path));
return 0;
}
static int
fsopen(Usbfs *fs, Fid *fid, int omode)
{
int qt;
vlong qid;
Conn *c;
Dirtab *tab;
Ether *e;
qid = fid->qid.path & ~fs->qid;
e = fs->aux;
qt = qtype(qid);
tab = qdirtab(qid);
omode &= 3;
if(omode != OREAD && (tab->mode&0222) == 0){
werrstr(Eperm);
return -1;
}
switch(qt){
case Qclone:
c = newconn(e);
if(c == nil){
werrstr("no more connections");
return -1;
}
fid->qid.type = QTFILE;
fid->qid.path = mkqid(c->nb, Qnctl)|fs->qid;
fid->qid.vers = 0;
break;
case Qndata:
case Qnctl:
case Qnifstats:
case Qnstats:
case Qntype:
c = getconn(e, qnum(qid), 1);
if(c == nil)
sysfatal("usb: ether: fsopen bug");
incref(c);
break;
}
etherdump(e);
return 0;
}
static int
prom(Ether *e, int set)
{
if(e->promiscuous != nil)
return e->promiscuous(e, set);
return 0;
}
static void
fsclunk(Usbfs *fs, Fid *fid)
{
int qt;
vlong qid;
Buf *bp;
Conn *c;
Ether *e;
e = fs->aux;
qid = fid->qid.path & ~fs->qid;
qt = qtype(qid);
switch(qt){
case Qndata:
case Qnctl:
case Qnifstats:
case Qnstats:
case Qntype:
if(fid->omode != ONONE){
c = getconn(e, qnum(qid), 0);
if(c == nil)
sysfatal("usb: ether: fsopen bug");
if(decref(c) == 0){
while((bp = nbrecvp(c->rc)) != nil)
freebuf(e, bp);
qlock(e);
if(c->prom != 0)
if(decref(&e->prom) == 0)
prom(e, 0);
c->prom = c->type = 0;
qunlock(e);
}
}
break;
}
etherdump(e);
}
int
parseaddr(uchar *m, char *s)
{
int i, n;
uchar v;
if(strlen(s) < 12)
return -1;
if(strlen(s) > 12 && strlen(s) < 17)
return -1;
for(i = n = 0; i < strlen(s); i++){
if(s[i] == ':')
continue;
if(s[i] >= 'A' && s[i] <= 'F')
v = 10 + s[i] - 'A';
else if(s[i] >= 'a' && s[i] <= 'f')
v = 10 + s[i] - 'a';
else if(s[i] >= '0' && s[i] <= '9')
v = s[i] - '0';
else
return -1;
if(n&1)
m[n/2] |= v;
else
m[n/2] = v<<4;
n++;
}
return 0;
}
static long
fsread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
{
int cn, qt;
char *s, *se;
char buf[2048]; /* keep this large for ifstats */
Buf *bp;
Conn *c;
Ether *e;
Qid q;
q = fid->qid;
q.path &= ~fs->qid;
e = fs->aux;
s = buf;
se = buf+sizeof(buf);
qt = qtype(q.path);
cn = qnum(q.path);
switch(qt){
case Qroot:
count = usbdirread(fs, q, data, count, offset, rootdirgen, nil);
break;
case Qaddr:
s = seprintaddr(s, se, e->addr);
count = usbreadbuf(data, count, offset, buf, s - buf);
break;
case Qnifstats:
/* BUG */
case Qifstats:
s = seprintifstats(s, se, e);
if(e->seprintstats != nil)
s = e->seprintstats(s, se, e);
count = usbreadbuf(data, count, offset, buf, s - buf);
break;
case Qnstats:
/* BUG */
case Qstats:
s = seprintstats(s, se, e);
count = usbreadbuf(data, count, offset, buf, s - buf);
break;
case Qndir:
count = usbdirread(fs, q, data, count, offset, conndirgen, nil);
break;
case Qndata:
c = getconn(e, cn, 0);
if(c == nil){
werrstr(Eio);
return -1;
}
bp = recvp(c->rc);
if(bp == nil)
return -1;
if(etherdebug > 1)
dumpframe("etherin", bp->rp, bp->ndata);
count = usbreadbuf(data, count, 0LL, bp->rp, bp->ndata);
freebuf(e, bp);
break;
case Qnctl:
s = seprint(s, se, "%11d ", cn);
count = usbreadbuf(data, count, offset, buf, s - buf);
break;
case Qntype:
c = getconn(e, cn, 0);
if(c == nil)
s = seprint(s, se, "%11d ", 0);
else
s = seprint(s, se, "%11d ", c->type);
count = usbreadbuf(data, count, offset, buf, s - buf);
break;
default:
sysfatal("usb: ether: fsread bug");
}
return count;
}
static int
typeinuse(Ether *e, int t)
{
int i;
for(i = 0; i < e->nconns; i++)
if(e->conns[i]->ref > 0 && e->conns[i]->type == t)
return 1;
return 0;
}
static int
isloopback(Ether *e, Buf *)
{
return e->prom.ref > 0; /* BUG: also loopbacks and broadcasts */
}
static int
etherctl(Ether *e, Conn *c, char *buf)
{
uchar addr[Eaddrlen];
int t;
deprint(2, "%s: etherctl: %s\n", argv0, buf);
if(strncmp(buf, "connect ", 8) == 0){
t = atoi(buf+8);
qlock(e);
if(typeinuse(e, t)){
werrstr("type already in use");
qunlock(e);
return -1;
}
c->type = atoi(buf+8);
qunlock(e);
return 0;
}
if(strncmp(buf, "nonblocking", 11) == 0){
if(buf[11] == '\n' || buf[11] == 0)
e->nblock = 1;
else
e->nblock = atoi(buf + 12);
deprint(2, "%s: nblock %d\n", argv0, e->nblock);
return 0;
}
if(strncmp(buf, "promiscuous", 11) == 0){
if(c->prom == 0)
incref(&e->prom);
c->prom = 1;
return prom(e, 1);
}
if(strncmp(buf, "headersonly", 11) == 0){
c->headersonly = 1;
return 0;
}
if(strncmp(buf, "addmulti ", 9) == 0 || strncmp(buf, "remmulti ", 9) == 0){
if(parseaddr(addr, buf+9) < 0){
werrstr("bad address");
return -1;
}
if(e->multicast == nil)
return 0;
if(strncmp(buf, "add", 3) == 0){
e->nmcasts++;
return e->multicast(e, addr, 1);
}else{
e->nmcasts--;
return e->multicast(e, addr, 0);
}
}
if(e->ctl != nil)
return e->ctl(e, buf);
werrstr(Ebadctl);
return -1;
}
static long
etherbread(Ether *e, Buf *bp)
{
deprint(2, "%s: etherbread\n", argv0);
bp->rp = bp->data + Hdrsize;
bp->ndata = -1;
bp->ndata = read(e->epin->dfd, bp->rp, sizeof(bp->data)-Hdrsize);
if(bp->ndata < 0){
deprint(2, "%s: etherbread: %r\n", argv0); /* keep { and } */
}else
deprint(2, "%s: etherbread: got %d bytes\n", argv0, bp->ndata);
return bp->ndata;
}
static long
etherbwrite(Ether *e, Buf *bp)
{
long n;
deprint(2, "%s: etherbwrite %d bytes\n", argv0, bp->ndata);
n = write(e->epout->dfd, bp->rp, bp->ndata);
if(n < 0){
deprint(2, "%s: etherbwrite: %r\n", argv0); /* keep { and } */
}else
deprint(2, "%s: etherbwrite wrote %ld bytes\n", argv0, n);
if(n <= 0)
return n;
if((bp->ndata % e->epout->maxpkt) == 0){
deprint(2, "%s: short pkt write\n", argv0);
write(e->epout->dfd, "", 1);
}
return n;
}
static long
fswrite(Usbfs *fs, Fid *fid, void *data, long count, vlong)
{
int cn, qt;
char buf[128];
Buf *bp;
Conn *c;
Ether *e;
Qid q;
q = fid->qid;
q.path &= ~fs->qid;
e = fs->aux;
qt = qtype(q.path);
cn = qnum(q.path);
switch(qt){
case Qndata:
c = getconn(e, cn, 0);
if(c == nil){
werrstr(Eio);
return -1;
}
bp = allocbuf(e);
if(count > sizeof(bp->data)-Hdrsize)
count = sizeof(bp->data)-Hdrsize;
memmove(bp->rp, data, count);
bp->ndata = count;
if(etherdebug > 1)
dumpframe("etherout", bp->rp, bp->ndata);
if(e->nblock == 0)
sendp(e->wc, bp);
else if(nbsendp(e->wc, bp) == 0){
deprint(2, "%s: (out) packet lost\n", argv0);
e->noerrs++;
freebuf(e, bp);
}
break;
case Qnctl:
c = getconn(e, cn, 0);
if(c == nil){
werrstr(Eio);
return -1;
}
if(count > sizeof(buf) - 1)
count = sizeof(buf) - 1;
memmove(buf, data, count);
buf[count] = 0;
if(etherctl(e, c, buf) < 0)
return -1;
break;
default:
sysfatal("usb: ether: fsread bug");
}
return count;
}
static int
openeps(Ether *e, int epin, int epout)
{
e->epin = openep(e->dev, epin);
if(e->epin == nil){
fprint(2, "ether: in: openep %d: %r\n", epin);
return -1;
}
if(epout == epin){
incref(e->epin);
e->epout = e->epin;
}else
e->epout = openep(e->dev, epout);
if(e->epout == nil){
fprint(2, "ether: out: openep %d: %r\n", epout);
closedev(e->epin);
return -1;
}
if(e->epin == e->epout)
opendevdata(e->epin, ORDWR);
else{
opendevdata(e->epin, OREAD);
opendevdata(e->epout, OWRITE);
}
if(e->epin->dfd < 0 || e->epout->dfd < 0){
fprint(2, "ether: open i/o ep data: %r\n");
closedev(e->epin);
closedev(e->epout);
return -1;
}
dprint(2, "ether: ep in %s maxpkt %d; ep out %s maxpkt %d\n",
e->epin->dir, e->epin->maxpkt, e->epout->dir, e->epout->maxpkt);
/* time outs are not activated for I/O endpoints */
if(usbdebug > 2 || etherdebug > 2){
devctl(e->epin, "debug 1");
devctl(e->epout, "debug 1");
devctl(e->dev, "debug 1");
}
return 0;
}
static int
usage(void)
{
werrstr("usage: usb/ether [-a addr] [-d] [-N nb]");
return -1;
}
static Usbfs etherfs = {
.walk = fswalk,
.open = fsopen,
.read = fsread,
.write = fswrite,
.stat = fsstat,
.clunk = fsclunk,
};
static void
shutdownchan(Channel *c)
{
Buf *bp;
while((bp=nbrecvp(c)) != nil)
free(bp);
chanfree(c);
}
static void
etherfree(Ether *e)
{
int i;
Buf *bp;
if(e->free != nil)
e->free(e);
closedev(e->epin);
closedev(e->epout);
if(e->rc == nil){ /* not really started */
free(e);
return;
}
for(i = 0; i < e->nconns; i++)
if(e->conns[i] != nil){
while((bp = nbrecvp(e->conns[i]->rc)) != nil)
free(bp);
chanfree(e->conns[i]->rc);
free(e->conns[i]);
}
shutdownchan(e->bc);
shutdownchan(e->rc);
shutdownchan(e->wc);
e->epin = e->epout = nil;
devctl(e->dev, "detach");
free(e);
}
static void
etherdevfree(void *a)
{
Ether *e = a;
if(e != nil)
etherfree(e);
}
/* must return 1 if c wants bp; 0 if not */
static int
cwantsbp(Conn *c, Buf *bp)
{
if(c->ref != 0 && (c->prom != 0 || c->type < 0 || c->type == bp->type))
return 1;
return 0;
}
static void
etherwriteproc(void *a)
{
Ether *e = a;
Buf *bp;
Channel *wc;
threadsetname("etherwrite");
wc = e->wc;
while(e->exiting == 0){
bp = recvp(wc);
if(bp == nil || e->exiting != 0){
free(bp);
break;
}
e->nout++;
if(e->bwrite(e, bp) < 0)
e->noerrs++;
if(isloopback(e, bp) && e->exiting == 0)
sendp(e->rc, bp); /* send to input queue */
else
freebuf(e, bp);
}
deprint(2, "%s: writeproc exiting\n", argv0);
closedev(e->dev);
}
static void
setbuftype(Buf *bp)
{
uchar *p;
bp->type = 0;
if(bp->ndata >= Ehdrsize){
p = bp->rp + Eaddrlen*2;
bp->type = p[0]<<8 | p[1];
}
}
static void
etherexiting(Ether *e)
{
devctl(e->dev, "detach");
e->exiting = 1;
close(e->epin->dfd);
e->epin->dfd = -1;
close(e->epout->dfd);
e->epout->dfd = -1;
nbsend(e->wc, nil);
}
static void
etherreadproc(void *a)
{
int i, n, nwants;
Buf *bp, *dbp;
Ether *e = a;
threadsetname("etherread");
while(e->exiting == 0){
bp = nbrecvp(e->rc);
if(bp == nil){
bp = allocbuf(e); /* leak() may think we leak */
if(e->bread(e, bp) < 0){
freebuf(e, bp);
break;
}
if(bp->ndata == 0){
/* may be a short packet; continue */
if(0)dprint(2, "%s: read: short\n", argv0);
freebuf(e, bp);
continue;
}else
setbuftype(bp);
}
e->nin++;
nwants = 0;
for(i = 0; i < e->nconns; i++)
nwants += cwantsbp(e->conns[i], bp);
for(i = 0; nwants > 0 && i < e->nconns; i++)
if(cwantsbp(e->conns[i], bp)){
n = bp->ndata;
if(e->conns[i]->type == -2 && n > 64)
n = 64;
if(nwants-- == 1){
bp->ndata = n;
dbp = bp;
bp = nil;
}else{
dbp = allocbuf(e);
memmove(dbp->rp, bp->rp, n);
dbp->ndata = n;
dbp->type = bp->type;
}
if(nbsendp(e->conns[i]->rc, dbp) == 0){
deprint(2, "%s: (in) packet lost\n", argv0);
e->nierrs++;
freebuf(e, dbp);
}
}
freebuf(e, bp);
}
deprint(2, "%s: writeproc exiting\n", argv0);
etherexiting(e);
closedev(e->dev);
usbfsdel(&e->fs);
}
static void
setalt(Dev *d, int ifcid, int altid)
{
if(usbcmd(d, Rh2d|Rstd|Riface, Rsetiface, altid, ifcid, nil, 0) < 0)
dprint(2, "%s: setalt ifc %d alt %d: %r\n", argv0, ifcid, altid);
}
static int
ifaceinit(Ether *e, Iface *ifc, int *ei, int *eo)
{
Ep *ep;
int epin, epout, i;
if(ifc == nil)
return -1;
epin = epout = -1;
for(i = 0; (epin < 0 || epout < 0) && i < nelem(ifc->ep); i++)
if((ep = ifc->ep[i]) != nil && ep->type == Ebulk){
if(ep->dir == Eboth || ep->dir == Ein)
if(epin == -1)
epin = ep->id;
if(ep->dir == Eboth || ep->dir == Eout)
if(epout == -1)
epout = ep->id;
}
if(epin == -1 || epout == -1)
return -1;
dprint(2, "ether: ep ids: in %d out %d\n", epin, epout);
for(i = 0; i < nelem(ifc->altc); i++)
if(ifc->altc[i] != nil)
setalt(e->dev, ifc->id, i);
*ei = epin;
*eo = epout;
return 0;
}
static int
etherinit(Ether *e, int *ei, int *eo)
{
int ctlid, datid, i, j;
Conf *c;
Desc *desc;
Iface *ctlif, *datif;
Usbdev *ud;
*ei = *eo = -1;
ud = e->dev->usb;
/* look for union descriptor with ethernet ctrl interface */
for(i = 0; i < nelem(ud->ddesc); i++){
if((desc = ud->ddesc[i]) == nil)
continue;
if(desc->data.bLength < 5 || desc->data.bbytes[0] != Cdcunion)
continue;
ctlid = desc->data.bbytes[1];
datid = desc->data.bbytes[2];
if((c = desc->conf) == nil)
continue;
ctlif = datif = nil;
for(j = 0; j < nelem(c->iface); j++){
if(c->iface[j] == nil)
continue;
if(c->iface[j]->id == ctlid)
ctlif = c->iface[j];
if(c->iface[j]->id == datid)
datif = c->iface[j];
if(datif != nil && ctlif != nil){
if(Subclass(ctlif->csp) == Scether &&
ifaceinit(e, datif, ei, eo) != -1)
return 0;
break;
}
}
}
/* try any other one that seems to be ok */
for(i = 0; i < nelem(ud->conf); i++)
if((c = ud->conf[i]) != nil)
for(j = 0; j < nelem(c->iface); j++)
if(ifaceinit(e, c->iface[j], ei, eo) != -1)
return 0;
dprint(2, "%s: no valid endpoints", argv0);
return -1;
}
static int
kernelproxy(Ether *e)
{
int ctlfd, n;
char eaddr[13];
ctlfd = open("#l0/ether0/clone", ORDWR);
if(ctlfd < 0){
deprint(2, "%s: etherusb bind #l0: %r\n", argv0);
return -1;
}
close(e->epin->dfd);
close(e->epout->dfd);
seprintaddr(eaddr, eaddr+sizeof(eaddr), e->addr);
n = fprint(ctlfd, "bind %s #u/usb/ep%d.%d/data #u/usb/ep%d.%d/data %s %d %d",
e->name, e->dev->id, e->epin->id, e->dev->id, e->epout->id,
eaddr, e->bufsize, e->epout->maxpkt);
if(n < 0){
deprint(2, "%s: etherusb bind #l0: %r\n", argv0);
opendevdata(e->epin, OREAD);
opendevdata(e->epout, OWRITE);
close(ctlfd);
return -1;
}
close(ctlfd);
return 0;
}
int
ethermain(Dev *dev, int argc, char **argv)
{
int epin, epout, i, devid;
Ether *e;
uchar ea[Eaddrlen];
devid = dev->id;
memset(ea, 0, Eaddrlen);
ARGBEGIN{
case 'a':
if(parseaddr(ea, EARGF(usage())) < 0)
return usage();
break;
case 'd':
if(etherdebug == 0)
fprint(2, "ether debug on\n");
etherdebug++;
break;
case 'N':
devid = atoi(EARGF(usage()));
break;
default:
return usage();
}ARGEND
if(argc != 0) {
return usage();
}
e = dev->aux = emallocz(sizeof(Ether), 1);
e->dev = dev;
dev->free = etherdevfree;
memmove(e->addr, ea, Eaddrlen);
e->name = "cdc";
for(i = 0; i < nelem(ethers); i++)
if(ethers[i](e) == 0)
break;
if(i == nelem(ethers))
return -1;
if(e->init == nil)
e->init = etherinit;
if(e->init(e, &epin, &epout) < 0)
return -1;
if(e->bwrite == nil)
e->bwrite = etherbwrite;
if(e->bread == nil)
e->bread = etherbread;
if(e->bufsize == 0)
e->bufsize = Maxpkt;
if(openeps(e, epin, epout) < 0)
return -1;
if(kernelproxy(e) == 0)
return 0;
e->fs = etherfs;
snprint(e->fs.name, sizeof(e->fs.name), "etherU%d", devid);
e->fs.dev = dev;
e->fs.aux = e;
e->bc = chancreate(sizeof(Buf*), Nbufs);
e->rc = chancreate(sizeof(Buf*), Nconns/2);
e->wc = chancreate(sizeof(Buf*), Nconns*2);
incref(e->dev);
proccreate(etherwriteproc, e, 16*1024);
incref(e->dev);
proccreate(etherreadproc, e, 16*1024);
deprint(2, "%s: dev ref %ld\n", argv0, dev->ref);
incref(e->dev);
usbfsadd(&e->fs);
return 0;
}
|