ref: f75b4fddb3bc6d242f65ac478b53bb62c75c9760
dir: /sys/src/cmd/nusb/usbd/usbd.c/
#include <u.h> #include <libc.h> #include <thread.h> #include <fcall.h> #include <9p.h> #include "usb.h" #include "dat.h" #include "fns.h" enum { Qroot, Qusbevent, Qmax }; char *names[] = { "", "usbevent", }; static char Enonexist[] = "does not exist"; typedef struct Event Event; struct Event { Dev *dev; /* the device producing the event, dev->aux points to Fid processing the event */ char *data; int len; Event *link; int ref; /* number of readers which will read this one the next time they'll read */ int prev; /* number of events pointing to this one with their link pointers */ }; static Event *evlast; static Req *reqfirst, *reqlast; static QLock evlock; static void addreader(Req *req) { req->aux = nil; if(reqfirst == nil) reqfirst = req; else reqlast->aux = req; reqlast = req; } static void fulfill(Req *req, Event *e) { int n; n = e->len; if(n > req->ifcall.count) n = req->ifcall.count; memmove(req->ofcall.data, e->data, n); req->ofcall.count = n; } static void initevent(void) { evlast = emallocz(sizeof(Event), 1); } static Event* putevent(Event *e) { Event *ee; ee = e->link; if(e->ref || e->prev) return ee; ee->prev--; closedev(e->dev); free(e->data); free(e); return ee; } static void procreqs(void) { Req *r, *p, *x; Event *e; Fid *f; Loop: for(p = nil, r = reqfirst; r != nil; p = r, r = x){ x = r->aux; f = r->fid; e = f->aux; if(e == evlast) continue; if(e->dev->aux == f){ e->dev->aux = nil; /* release device */ e->ref--; e = putevent(e); e->ref++; f->aux = e; goto Loop; } if(e->dev->aux == nil){ e->dev->aux = f; /* claim device */ if(x == nil) reqlast = p; if(p == nil) reqfirst = x; else p->aux = x; r->aux = nil; fulfill(r, e); respond(r, nil); goto Loop; } } } static void pushevent(Dev *d, char *data) { Event *e; qlock(&evlock); e = evlast; evlast = emallocz(sizeof(Event), 1); incref(d); e->dev = d; e->data = data; e->len = strlen(data); e->link = evlast; evlast->prev++; procreqs(); putevent(e); qunlock(&evlock); } static int dirgen(int n, Dir *d, void *) { if(n >= Qmax - 1) return -1; d->qid.path = n + 1; d->qid.vers = 0; if(n >= 0){ d->qid.type = 0; d->mode = 0444; }else{ d->qid.type = QTDIR; d->mode = 0555 | DMDIR; } d->uid = estrdup9p(getuser()); d->gid = estrdup9p(d->uid); d->muid = estrdup9p(d->uid); d->name = estrdup9p(names[n+1]); d->atime = d->mtime = time(0); d->length = 0; return 0; } static void usbdattach(Req *req) { req->fid->qid = (Qid) {Qroot, 0, QTDIR}; req->ofcall.qid = req->fid->qid; respond(req, nil); } static char * usbdwalk(Fid *fid, char *name, Qid *qid) { int i; if(fid->qid.path != Qroot) return "not a directory"; if(strcmp(name, "..") == 0){ *qid = fid->qid; return nil; } for(i = Qroot+1; i < Qmax; i++) if(strcmp(name, names[i]) == 0){ fid->qid = (Qid) {i, 0, 0}; *qid = fid->qid; return nil; } return Enonexist; } static void usbdread(Req *req) { switch((long)req->fid->qid.path){ case Qroot: dirread9p(req, dirgen, nil); respond(req, nil); break; case Qusbevent: qlock(&evlock); addreader(req); procreqs(); qunlock(&evlock); break; default: respond(req, Enonexist); break; } } static void usbdstat(Req *req) { if(dirgen(req->fid->qid.path - 1, &req->d, nil) < 0) respond(req, Enonexist); else respond(req, nil); } static char * formatdev(Dev *d, int type) { Usbdev *u = d->usb; return smprint("%s %d %.4x %.4x %.6lx %s\n", type ? "detach" : "attach", d->id, u->vid, u->did, u->csp, d->hname != nil ? d->hname : ""); } static void enumerate(Event **l) { extern Hub *hubs; Event *e; Hub *h; Port *p; Dev *d; int i; for(h = hubs; h != nil; h = h->next){ for(i = 1; i <= h->nport; i++){ p = &h->port[i]; d = p->dev; if(d == nil || d->usb == nil || p->hub != nil) continue; e = emallocz(sizeof(Event), 1); incref(d); e->dev = d; e->data = formatdev(d, 0); e->len = strlen(e->data); e->prev = 1; *l = e; l = &e->link; } } *l = evlast; evlast->prev++; } static void usbdopen(Req *req) { extern QLock hublock; if(req->fid->qid.path == Qusbevent){ Event *e; qlock(&hublock); qlock(&evlock); enumerate(&e); e->prev--; e->ref++; req->fid->aux = e; qunlock(&evlock); qunlock(&hublock); } respond(req, nil); } static void usbddestroyfid(Fid *fid) { if(fid->qid.path == Qusbevent){ Event *e; qlock(&evlock); e = fid->aux; if(e != nil){ fid->aux = nil; if(e->dev != nil && e->dev->aux == fid){ e->dev->aux = nil; /* release device */ procreqs(); } e->ref--; while(e->ref == 0 && e->prev == 0 && e != evlast) e = putevent(e); } qunlock(&evlock); } } static void usbdflush(Req *req) { Req *r, *p, *x; qlock(&evlock); for(p = nil, r = reqfirst; r != nil; p = r, r = x){ x = r->aux; if(r == req->oldreq){ if(x == nil) reqlast = p; if(p == nil) reqfirst = x; else p->aux = x; r->aux = nil; respond(r, "interrupted"); break; } } qunlock(&evlock); respond(req, nil); } Srv usbdsrv = { .attach = usbdattach, .walk1 = usbdwalk, .read = usbdread, .stat = usbdstat, .open = usbdopen, .flush = usbdflush, .destroyfid = usbddestroyfid, }; static void assignhname(Dev *dev) { extern Hub *hubs; char buf[64]; Usbdev *ud; Hub *h; int i; ud = dev->usb; /* build string of device unique stuff */ snprint(buf, sizeof(buf), "%.4x%.4x%.4x%.6lx%s", ud->vid, ud->did, ud->dno, ud->csp, ud->serial); hname(buf); /* check for collisions */ for(h = hubs; h != nil; h = h->next){ for(i = 1; i <= h->nport; i++){ if(h->port[i].dev == nil) continue; if(h->port[i].dev->hname == nil || h->port[i].dev == dev) continue; if(strcmp(h->port[i].dev->hname, buf) == 0){ dev->hname = smprint("%s%d", buf, dev->id); return; } } } dev->hname = strdup(buf); } int attachdev(Port *p) { Dev *d = p->dev; int id; if(d->usb->class == Clhub){ /* * Hubs are handled directly by this process avoiding * concurrent operation so that at most one device * has the config address in use. * We cancel kernel debug for these eps. too chatty. */ if((p->hub = newhub(d->dir, d)) == nil) return -1; return 0; } /* create all endpoint files for default conf #1 */ for(id=1; id<nelem(d->usb->ep); id++){ Ep *ep = d->usb->ep[id]; if(ep != nil && ep->conf != nil && ep->conf->cval == 1){ Dev *epd = openep(d, id); if(epd != nil) closedev(epd); } } /* close control endpoint so driver can open it */ close(d->dfd); d->dfd = -1; /* assign stable name based on device descriptor */ assignhname(d); /* set device info for ctl file */ devctl(d, "info %s csp %#08lux vid %#.4ux did %#.4ux %q %q %s", classname(Class(d->usb->csp)), d->usb->csp, d->usb->vid, d->usb->did, d->usb->vendor, d->usb->product, d->hname); pushevent(d, formatdev(d, 0)); return 0; } void detachdev(Port *p) { Dev *d = p->dev; if(d->usb->class == Clhub) return; pushevent(d, formatdev(d, 1)); } /* * we create /env/usbbusy on startup and once all devices have been * enumerated and readers have consumed all the events, we remove the * file so nusbrc can continue. */ static int busyfd = -1; void checkidle(void) { if(busyfd < 0 || reqlast == nil || evlast == nil || evlast->prev > 0) return; close(busyfd); busyfd = -1; } void main(int argc, char **argv) { int fd, i, nd; Dir *d; ARGBEGIN { case 'D': chatty9p++; break; case 'd': usbdebug++; break; } ARGEND; busyfd = create("/env/usbbusy", ORCLOSE, 0600); quotefmtinstall(); initevent(); rfork(RFNOTEG); switch(rfork(RFPROC|RFMEM|RFNOWAIT)){ case -1: sysfatal("rfork: %r"); case 0: work(); exits(nil); } if(argc == 0){ if((fd = open("/dev/usb", OREAD)) < 0){ rendezvous(work, nil); sysfatal("/dev/usb: %r"); } nd = dirreadall(fd, &d); close(fd); if(nd < 2){ rendezvous(work, nil); sysfatal("/dev/usb: no hubs"); } for(i = 0; i < nd; i++) if(strcmp(d[i].name, "ctl") != 0) rendezvous(work, smprint("/dev/usb/%s", d[i].name)); free(d); }else for(i = 0; i < argc; i++) rendezvous(work, estrdup9p(argv[i])); rendezvous(work, nil); postsharesrv(&usbdsrv, nil, "usb", "usbd"); exits(nil); }