#include #include #include #include #include #include #include <9p.h> #include "usb.h" typedef struct Tab Tab; typedef struct Qbuf Qbuf; typedef struct Dq Dq; typedef struct Conn Conn; typedef struct Ehdr Ehdr; typedef struct Stats Stats; enum { SC_ACM = 2, SC_ETHER = 6, FUNCTION = 0x24, FN_HEADER = 0, FN_UNION = 6, FN_ETHER = 15, }; enum { Qroot, Qiface, Qclone, Qstats, Qaddr, Qndir, Qctl, Qdata, Qtype, Qmax, }; struct Tab { char *name; ulong mode; }; Tab tab[] = { "/", DMDIR|0555, "etherU", DMDIR|0555, /* calling it *ether* makes snoopy(8) happy */ "clone", 0666, "stats", 0666, "addr", 0444, "%ud", DMDIR|0555, "ctl", 0666, "data", 0666, "type", 0444, }; struct Qbuf { Qbuf *next; int ndata; uchar data[]; }; struct Dq { QLock l; Dq *next; Req *r; Req **rt; Qbuf *q; Qbuf **qt; int nb; }; struct Conn { QLock l; int used; int type; int prom; Dq *dq; }; struct Ehdr { uchar d[6]; uchar s[6]; uchar type[2]; }; struct Stats { int in; int out; }; Conn conn[32]; int nconn = 0; int debug; ulong time0; int usbfdin = -1; int usbfdout = -1; Stats stats; uchar macaddr[6]; int iunion[8][2]; int niunion; int maxpacket = 64; char *uname; #define PATH(type, n) ((type)|((n)<<8)) #define TYPE(path) (((uint)(path) & 0x000000FF)>>0) #define NUM(path) (((uint)(path) & 0xFFFFFF00)>>8) #define NUMCONN(c) (((long)(c)-(long)&conn[0])/sizeof(conn[0])) static int receivepacket(void *buf, int len); static void fillstat(Dir *d, uvlong path) { Tab *t; memset(d, 0, sizeof(*d)); d->uid = estrdup9p(uname); d->gid = estrdup9p(uname); d->qid.path = path; d->atime = d->mtime = time0; t = &tab[TYPE(path)]; d->name = smprint(t->name, NUM(path)); d->qid.type = t->mode>>24; d->mode = t->mode; } static void fsattach(Req *r) { if(r->ifcall.aname && r->ifcall.aname[0]){ respond(r, "invalid attach specifier"); return; } if(uname == nil) uname = estrdup9p(r->ifcall.uname); r->fid->qid.path = PATH(Qroot, 0); r->fid->qid.type = QTDIR; r->fid->qid.vers = 0; r->ofcall.qid = r->fid->qid; respond(r, nil); } static void fsstat(Req *r) { fillstat(&r->d, r->fid->qid.path); respond(r, nil); } static int rootgen(int i, Dir *d, void*) { i += Qroot+1; if(i == Qiface){ fillstat(d, i); return 0; } return -1; } static int ifacegen(int i, Dir *d, void*) { i += Qiface+1; if(i < Qndir){ fillstat(d, i); return 0; } i -= Qndir; if(i < nconn){ fillstat(d, PATH(Qndir, i)); return 0; } return -1; } static int ndirgen(int i, Dir *d, void *aux) { i += Qndir+1; if(i < Qmax){ fillstat(d, PATH(i, NUMCONN(aux))); return 0; } return -1; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { int i, n; char buf[32]; ulong path; path = fid->qid.path; if(!(fid->qid.type&QTDIR)) return "walk in non-directory"; if(strcmp(name, "..") == 0){ switch(TYPE(path)){ case Qroot: return nil; case Qiface: qid->path = PATH(Qroot, 0); qid->type = tab[Qroot].mode>>24; return nil; case Qndir: qid->path = PATH(Qiface, 0); qid->type = tab[Qiface].mode>>24; return nil; default: return "bug in fswalk1"; } } for(i = TYPE(path)+1; ipath = PATH(i, n); qid->type = tab[i].mode>>24; return nil; } break; } if(strcmp(name, tab[i].name) == 0){ qid->path = PATH(i, NUM(path)); qid->type = tab[i].mode>>24; return nil; } if(tab[i].mode&DMDIR) break; } return "directory entry not found"; } static void matchrq(Dq *d) { Req *r; Qbuf *b; while(r = d->r){ int n; if((b = d->q) == nil) break; if((d->q = b->next) == nil) d->qt = &d->q; if((d->r = (Req*)r->aux) == nil) d->rt = &d->r; n = r->ifcall.count; if(n > b->ndata) n = b->ndata; memmove(r->ofcall.data, b->data, n); free(b); r->ofcall.count = n; respond(r, nil); } } static void readconndata(Req *r) { Dq *d; d = r->fid->aux; qlock(&d->l); if(d->q==nil && d->nb){ qunlock(&d->l); r->ofcall.count = 0; respond(r, nil); return; } // enqueue request r->aux = nil; *d->rt = r; d->rt = (Req**)&r->aux; matchrq(d); qunlock(&d->l); } static void writeconndata(Req *r) { char e[ERRMAX]; Dq *d; void *p; int n; d = r->fid->aux; p = r->ifcall.data; n = r->ifcall.count; if((n == 11) && memcmp(p, "nonblocking", n)==0){ d->nb = 1; goto out; } n = write(usbfdout, p, n); if(n < 0){ rerrstr(e, sizeof(e)); respond(r, e); return; } /* * this may not work with all CDC devices. the * linux driver sends one more random byte * instead of a zero byte transaction. maybe we * should do the same? */ if(n % maxpacket == 0) write(usbfdout, "", 0); if(receivepacket(p, n) == 0) stats.out++; out: r->ofcall.count = n; respond(r, nil); } static char* mac2str(uchar *m) { int i; char *t = "0123456789abcdef"; static char buf[13]; buf[13] = 0; for(i=0; i<6; i++){ buf[i*2] = t[m[i]>>4]; buf[i*2+1] = t[m[i]&0xF]; } return buf; } static int str2mac(uchar *m, char *s) { int i; if(strlen(s) != 12) return -1; for(i=0; i<12; i++){ uchar v; 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 { v = 0; } if(i&1){ m[i/2] |= v; } else { m[i/2] = v<<4; } } return 0; } static void fsread(Req *r) { char buf[200]; char e[ERRMAX]; ulong path; path = r->fid->qid.path; switch(TYPE(path)){ default: snprint(e, sizeof e, "bug in fsread path=%lux", path); respond(r, e); break; case Qroot: dirread9p(r, rootgen, nil); respond(r, nil); break; case Qiface: dirread9p(r, ifacegen, nil); respond(r, nil); break; case Qstats: snprint(buf, sizeof(buf), "in: %d\n" "out: %d\n" "mbps: %d\n" "addr: %s\n", stats.in, stats.out, 10, mac2str(macaddr)); readstr(r, buf); respond(r, nil); break; case Qaddr: readstr(r, mac2str(macaddr)); respond(r, nil); break; case Qndir: dirread9p(r, ndirgen, &conn[NUM(path)]); respond(r, nil); break; case Qctl: snprint(buf, sizeof(buf), "%11d ", NUM(path)); readstr(r, buf); respond(r, nil); break; case Qtype: snprint(buf, sizeof(buf), "%11d ", conn[NUM(path)].type); readstr(r, buf); respond(r, nil); break; case Qdata: readconndata(r); break; } } static void fswrite(Req *r) { char e[ERRMAX]; ulong path; char *p; int n; path = r->fid->qid.path; switch(TYPE(path)){ case Qctl: n = r->ifcall.count; p = (char*)r->ifcall.data; if((n == 11) && memcmp(p, "promiscuous", 11)==0) conn[NUM(path)].prom = 1; if((n > 8) && memcmp(p, "connect ", 8)==0){ char x[12]; if(n - 8 >= sizeof(x)){ respond(r, "invalid control msg"); return; } p += 8; memcpy(x, p, n-8); x[n-8] = 0; conn[NUM(path)].type = atoi(p); } r->ofcall.count = n; respond(r, nil); break; case Qdata: writeconndata(r); break; default: snprint(e, sizeof e, "bug in fswrite path=%lux", path); respond(r, e); } } static void fsopen(Req *r) { static int need[4] = { 4, 2, 6, 1 }; ulong path; int i, n; Tab *t; Dq *d; Conn *c; /* * lib9p already handles the blatantly obvious. * we just have to enforce the permissions we have set. */ path = r->fid->qid.path; t = &tab[TYPE(path)]; n = need[r->ifcall.mode&3]; if((n&t->mode) != n){ respond(r, "permission denied"); return; } d = nil; r->fid->aux = nil; switch(TYPE(path)){ case Qclone: for(i=0; i= nconn) nconn = i+1; path = PATH(Qctl, i); goto CaseConn; } respond(r, "out of connections"); return; case Qdata: d = emalloc9p(sizeof(*d)); memset(d, 0, sizeof(*d)); d->qt = &d->q; d->rt = &d->r; r->fid->aux = d; case Qndir: case Qctl: case Qtype: CaseConn: c = &conn[NUM(path)]; qlock(&c->l); if(c->used++ == 0){ c->type = 0; c->prom = 0; } if(d != nil){ d->next = c->dq; c->dq = d; } qunlock(&c->l); break; } r->fid->qid.path = path; r->ofcall.qid.path = path; respond(r, nil); } static void fsflush(Req *r) { Req *o, **p; Fid *f; Dq *d; o = r->oldreq; f = o->fid; if(TYPE(f->qid.path) == Qdata){ d = f->aux; qlock(&d->l); for(p=&d->r; *p; p=(Req**)&((*p)->aux)){ if(*p == o){ if((*p = (Req*)o->aux) == nil) d->rt = p; r->oldreq = nil; respond(o, "interrupted"); break; } } qunlock(&d->l); } respond(r, nil); } static void fsdestroyfid(Fid *fid) { Conn *c; Qbuf *b; Dq **x, *d; if(TYPE(fid->qid.path) >= Qndir){ c = &conn[NUM(fid->qid.path)]; qlock(&c->l); if(d = fid->aux){ fid->aux = nil; for(x=&c->dq; *x; x=&((*x)->next)){ if(*x == d){ *x = d->next; break; } } qlock(&d->l); while(b = d->q){ d->q = b->next; free(b); } qunlock(&d->l); } if(TYPE(fid->qid.path) == Qctl) c->prom = 0; c->used--; qunlock(&c->l); } } static char* finddevice(int *ctrlno, int *id) { static char buf[80]; int fd; int i, j; for(i=0; i<16; i++){ for(j=0; j<128; j++){ int csp; char line[80]; char *p; int n; snprint(buf, sizeof(buf), "/dev/usb%d/%d/status", i, j); fd = open(buf, OREAD); buf[strlen(buf)-7] = 0; if(fd < 0) break; n = read(fd, line, sizeof(line)-1); close(fd); if(n <= 0) continue; line[n] = 0; p = line; if(strncmp(p, "Enabled ", 8) == 0) p += 8; csp = atol(p); if(Class(csp) != CL_COMMS) continue; switch(Subclass(csp)){ default: continue; case SC_ACM: case SC_ETHER: break; } if(debug) fprint(2, "found matching device %s\n", buf); if(ctrlno) *ctrlno = i; if(id) *id = j; return buf; } } return nil; } static char* usbgetstr(Device *d, int i, int lang) { byte b[200]; byte *rb; char *s; Rune r; int l; int n; setupreq(d->ep[0], RD2H|Rdevice, GET_DESCRIPTOR, STRING<<8|i, lang, sizeof(b)); if((n = setupreply(d->ep[0], b, sizeof(b))) < 0) return nil; if(n <= 2) return nil; if(n & 1) return nil; s = malloc(n*UTFmax+1); n = (n - 2)/2; rb = (byte*)b + 2; for(l=0; --n >= 0; rb += 2){ r = GET2(rb); l += runetochar(s+l, &r); } s[l] = 0; return s; } static void etherfunc(Device *d, int, ulong csp, void *bb, int nb) { int class, subclass; uchar *b = bb; char *s; class = Class(csp); subclass = Subclass(csp); if(class != CL_COMMS || subclass != SC_ETHER) return; switch(b[2]){ case FN_HEADER: pcs_raw("header: ", bb, nb); break; case FN_ETHER: pcs_raw("ether: ", bb, nb); if(s = usbgetstr(d, b[3], 0)){ str2mac(macaddr, s); free(s); } break; case FN_UNION: pcs_raw("union: ", bb, nb); if(niunion < nelem(iunion)){ iunion[niunion][0] = b[3]; iunion[niunion][1] = b[4]; niunion++; } break; default: pcs_raw("unknown: ", bb, nb); } } int findendpoints(Device *d, int *epin, int *epout) { int i, j, k; *epin = *epout = -1; niunion = 0; memset(macaddr, 0, 6); for(i=0; inconf; i++){ if (d->config[i] == nil) d->config[i] = mallocz(sizeof(*d->config[0]), 1); loadconfig(d, i); } if(niunion <= 0) return -1; for(i=0; iep); i++){ Endpt *ep; if((ep = d->ep[i]) == nil) continue; if(ep->type != Ebulk) continue; if(ep->iface == nil) continue; if(Class(ep->iface->csp) != CL_DATA) continue; for(j=0; jiface->interface) continue; if(ep->conf == nil) continue; for(k=0; kconf->nif; k++){ if(iunion[j][0] != ep->conf->iface[k]->interface) continue; if(Class(ep->conf->iface[k]->csp) != CL_COMMS) continue; if(Subclass(ep->conf->iface[k]->csp) != SC_ETHER) continue; if(ep->addr & 0x80){ if(*epin == -1) *epin = ep->addr&0xF; } else { if(*epout == -1) *epout = ep->addr&0xF; } if (*epin != -1 && *epout != -1){ maxpacket = ep->maxpkt; for(i=0; i<2; i++){ if(ep->iface->dalt[i] == nil) continue; setupreq(d->ep[0], RH2D|Rinterface, SET_INTERFACE, i, ep->iface->interface, 0); break; } return 0; } break; } break; } } return -1; } static int inote(void *, char *msg) { if(strstr(msg, "interrupt")) return 1; return 0; } static int receivepacket(void *buf, int len) { int i; int t; Ehdr *h; if(len < sizeof(*h)) return -1; h = (Ehdr*)buf; t = (h->type[0]<<8)|h->type[1]; for(i=0; il); if(!c->used) goto next; if(c->type > 0) if(c->type != t) goto next; if(!c->prom && !(h->d[0]&1)) if(memcmp(h->d, macaddr, 6)) goto next; for(d=c->dq; d; d=d->next){ int n; n = len; if(c->type == -2 && n > 64) n = 64; b = emalloc9p(sizeof(*b) + n); b->ndata = n; memcpy(b->data, buf, n); qlock(&d->l); // enqueue buffer b->next = nil; *d->qt = b; d->qt = &b->next; matchrq(d); qunlock(&d->l); } next: qunlock(&c->l); } return 0; } static void usbreadproc(void *) { char err[ERRMAX]; uchar buf[4*1024]; atnotify(inote, 1); threadsetname("usbreadproc"); for(;;){ int n; n = read(usbfdin, buf, sizeof(buf)); if(n < 0){ rerrstr(err, sizeof(err)); if(strstr(err, "interrupted")) continue; fprint(2, "usbreadproc: %s\n", err); threadexitsall(err); } if(n == 0) continue; if(receivepacket(buf, n) == 0) stats.in++; } } void (*dprinter[])(Device *, int, ulong, void *b, int n) = { [STRING] pstring, [DEVICE] pdevice, [FUNCTION] etherfunc, }; Srv fs = { .attach= fsattach, .destroyfid= fsdestroyfid, .walk1= fswalk1, .open= fsopen, .read= fsread, .write= fswrite, .stat= fsstat, .flush= fsflush, }; static void usage(void) { fprint(2, "usage: %s [-dD] [-m mtpt] [-s srv] [ctrlno n]\n", argv0); exits("usage"); } void threadmain(int argc, char **argv) { char *srv, *mtpt, *r; char s[64]; int ctrlno, id, epin, epout; Device *d; srv = nil; mtpt = "/net"; ARGBEGIN { case 'd': debug = 1; break; case 'D': chatty9p++; break; case 'm': mtpt = EARGF(usage()); break; case 's': srv = EARGF(usage()); break; default: usage(); } ARGEND; if(argc != 0 && argc != 2) usage(); if(argc == 2){ ctrlno = atoi(argv[0]); id = atoi(argv[1]); r = smprint("/dev/usb%d/%d", ctrlno, id); } else { r = finddevice(&ctrlno, &id); if(r == nil){ fprint(2, "no device found\n"); return; } } if(debug) fprint(2, "using %d %d %s\n", ctrlno, id, r); if((d = opendev(ctrlno, id)) == nil){ fprint(2, "opendev failed: %r\n"); exits("opendev"); } if(describedevice(d) < 0){ fprint(2, "describedevice failed: %r\n"); exits("describedevice"); } if(findendpoints(d, &epin, &epout) < 0){ fprint(2, "no endpoints found!\n"); exits("findendpoints"); } if(debug) fprint(2, "endpoints in %d, out %d, maxpacket %d\n", epin, epout, maxpacket); fprint(d->ctl, "ep %d bulk r %d 24", epin, maxpacket); fprint(d->ctl, "ep %d bulk w %d 24", epout, maxpacket); closedev(d); sprint(s, "%s/ep%ddata", r, epin); if((usbfdin = open(s, OREAD)) < 0){ fprint(2, "cant open receiving endpoint: %r\n"); exits("open"); } sprint(s, "%s/ep%ddata", r, epout); if((usbfdout = open(s, OWRITE)) < 0){ fprint(2, "cant open transmitting endpoint: %r\n"); exits("open"); } proccreate(usbreadproc, nil, 8*1024); atnotify(inote, 1); time0 = time(0); threadpostmountsrv(&fs, srv, mtpt, MBEFORE); }