Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * 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;
}