ref: c0cbe87b357a4464a7d10ab8f38a65c77ea3b629
dir: /os/pc/devusb.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "usb.h" static int debug = 0; #define Chatty 1 #define DPRINT if(Chatty)print #define XPRINT if(debug)iprint Usbhost* usbhost[MaxUsb]; static char *devstates[] = { [Disabled] "Disabled", [Attached] "Attached", [Enabled] "Enabled", [Assigned] "Assigned", [Configured] "Configured", }; static char Ebadusbmsg[] = "invalid parameters to USB ctl message"; enum { Qtopdir = 0, Q2nd, Qnew, Qport, Q3rd, Qctl, Qstatus, Qep0, /* other endpoint files */ }; /* * Qid path is: * 8 bits of file type (qids above) * 8 bits of slot number; default address 0 used for per-controller files * 4 bits of controller number */ enum { TYPEBITS = 8, SLOTBITS = 8, CTLRBITS = 4, SLOTSHIFT = TYPEBITS, CTLRSHIFT = SLOTSHIFT+SLOTBITS, TYPEMASK = (1<<TYPEBITS)-1, SLOTMASK = (1<<SLOTBITS)-1, CTLRMASK = (1<<CTLRBITS)-1, }; #define TYPE(q) (((ulong)(q).path)&TYPEMASK) #define SLOT(q) ((((ulong)(q).path)>>SLOTSHIFT)&SLOTMASK) #define CTLR(q) ((((ulong)(q).path)>>CTLRSHIFT)&CTLRMASK) #define PATH(t, s, c) ((t)|((s)<<SLOTSHIFT)|((c)<<CTLRSHIFT)) static Dirtab usbdir2[] = { "new", {Qnew}, 0, 0666, "port", {Qport}, 0, 0666, }; static Dirtab usbdir3[]={ "ctl", {Qctl}, 0, 0666, "status", {Qstatus}, 0, 0444, "setup", {Qep0}, 0, 0666, /* epNdata names are generated on demand */ }; enum { PMdisable, PMenable, PMreset, }; enum { CMclass, CMdata, CMdebug, CMep, CMmaxpkt, CMadjust, CMspeed, CMunstall, }; static Cmdtab usbportmsg[] = { PMdisable, "disable", 2, PMenable, "enable", 2, PMreset, "reset", 2, }; static Cmdtab usbctlmsg[] = { CMclass, "class", 0, CMdata, "data", 3, CMdebug, "debug", 3, CMep, "ep", 6, CMmaxpkt, "maxpkt", 3, CMadjust, "adjust", 3, CMspeed, "speed", 2, CMunstall, "unstall", 2, }; static struct { char* type; int (*reset)(Usbhost*); } usbtypes[MaxUsb+1]; void addusbtype(char* t, int (*r)(Usbhost*)) { static int ntype; if(ntype == MaxUsb) panic("too many USB host interface types"); usbtypes[ntype].type = t; usbtypes[ntype].reset = r; ntype++; } static Udev* usbdeviceofslot(Usbhost *uh, int s) { if(s < 0 || s > nelem(uh->dev)) return nil; return uh->dev[s]; } static Udev* usbdevice(Chan *c) { int bus; Udev *d; Usbhost *uh; bus = CTLR(c->qid); if(bus > nelem(usbhost) || (uh = usbhost[bus]) == nil) { error(Egreg); return nil; /* for compiler */ } d = usbdeviceofslot(uh, SLOT(c->qid)); if(d == nil || d->id != c->qid.vers || d->state == Disabled) error(Ehungup); return d; } static Endpt * devendpt(Udev *d, int id, int add) { Usbhost *uh; Endpt *e, **p; p = &d->ep[id&0xF]; lock(d); e = *p; if(e != nil){ incref(e); XPRINT("incref(0x%p) in devendpt, new value %ld\n", e, e->ref); unlock(d); return e; } unlock(d); if(!add) return nil; e = mallocz(sizeof(*e), 1); e->ref = 1; e->x = id&0xF; e->id = id; e->sched = -1; e->maxpkt = 8; e->nbuf = 1; e->dev = d; e->active = 0; uh = d->uh; uh->epalloc(uh, e); lock(d); if(*p != nil){ incref(*p); XPRINT("incref(0x%p) in devendpt, new value %ld\n", *p, (*p)->ref); unlock(d); uh->epfree(uh, e); free(e); return *p; } *p = e; unlock(d); e->rq = qopen(8*1024, 0, nil, e); e->wq = qopen(8*1024, 0, nil, e); return e; } static void freept(Endpt *e) { Usbhost *uh; if(e != nil && decref(e) == 0){ XPRINT("freept(%d,%d)\n", e->dev->x, e->x); uh = e->dev->uh; uh->epclose(uh, e); e->dev->ep[e->x] = nil; uh->epfree(uh, e); free(e); } } static Udev* usbnewdevice(Usbhost *uh) { int i; Udev *d; Endpt *e; d = nil; qlock(uh); if(waserror()){ qunlock(uh); nexterror(); } for(i=0; i<nelem(uh->dev); i++) if(uh->dev[i] == nil){ uh->idgen++; d = mallocz(sizeof(*d), 1); d->uh = uh; d->ref = 1; d->x = i; d->id = (uh->idgen << 8) | i; d->state = Enabled; XPRINT("calling devendpt in usbnewdevice\n"); e = devendpt(d, 0, 1); /* always provide control endpoint 0 */ e->mode = ORDWR; e->iso = 0; e->sched = -1; uh->dev[i] = d; break; } poperror(); qunlock(uh); return d; } static void freedev(Udev *d, int ept) { int i; Endpt *e; Usbhost *uh; uh = d->uh; if(decref(d) == 0){ XPRINT("freedev 0x%p, 0\n", d); for(i=0; i<nelem(d->ep); i++) freept(d->ep[i]); if(d->x >= 0) uh->dev[d->x] = nil; free(d); } else { if(ept >= 0 && ept < nelem(d->ep)){ e = d->ep[ept]; XPRINT("freedev, freept 0x%p\n", e); if(e != nil) uh->epclose(uh, e); } } } static int usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp) { Qid q; Udev *d; Endpt *e; Dirtab *tab; Usbhost *uh; int t, bus, slot, perm; /* * Top level directory contains the controller names. */ if(c->qid.path == Qtopdir){ if(s == DEVDOTDOT){ mkqid(&q, Qtopdir, 0, QTDIR); devdir(c, q, "#U", 0, eve, 0555, dp); return 1; } if(s >= nelem(usbhost) || usbhost[s] == nil) return -1; mkqid(&q, PATH(Q2nd, 0, s), 0, QTDIR); snprint(up->genbuf, sizeof up->genbuf, "usb%d", s); devdir(c, q, up->genbuf, 0, eve, 0555, dp); return 1; } bus = CTLR(c->qid); if(bus >= nelem(usbhost) || (uh = usbhost[bus]) == nil) return -1; /* * Second level contains "new", "port", and a numbered * directory for each enumerated device on the bus. */ t = TYPE(c->qid); if(t < Q3rd){ if(s == DEVDOTDOT){ mkqid(&q, Qtopdir, 0, QTDIR); devdir(c, q, "#U", 0, eve, 0555, dp); return 1; } if(s < nelem(usbdir2)){ d = uh->dev[0]; if(d == nil) return -1; tab = &usbdir2[s]; mkqid(&q, PATH(tab->qid.path, 0, bus), d->id, QTFILE); devdir(c, q, tab->name, tab->length, eve, tab->perm, dp); return 1; } s -= nelem(usbdir2); if(s >= 0 && s < nelem(uh->dev)) { d = uh->dev[s]; if(d == nil) return 0; sprint(up->genbuf, "%d", s); mkqid(&q, PATH(Q3rd, s, bus), d->id, QTDIR); devdir(c, q, up->genbuf, 0, eve, 0555, dp); return 1; } return -1; } /* * Third level. */ slot = SLOT(c->qid); if(s == DEVDOTDOT) { mkqid(&q, PATH(Q2nd, 0, bus), c->qid.vers, QTDIR); snprint(up->genbuf, sizeof up->genbuf, "usb%d", bus); devdir(c, q, up->genbuf, 0, eve, 0555, dp); return 1; } if(s < nelem(usbdir3)) { tab = &usbdir3[s]; mkqid(&q, PATH(tab->qid.path, slot, bus), c->qid.vers, QTFILE); devdir(c, q, tab->name, tab->length, eve, tab->perm, dp); return 1; } s -= nelem(usbdir3); /* active endpoints */ d = usbdeviceofslot(uh, slot); if(d == nil || s >= nelem(d->ep)) return -1; if(s == 0 || (e = d->ep[s]) == nil) /* ep0data is called "setup" */ return 0; sprint(up->genbuf, "ep%ddata", s); mkqid(&q, PATH(Qep0+s, slot, bus), c->qid.vers, QTFILE); switch(e->mode) { case OREAD: perm = 0444; break; case OWRITE: perm = 0222; break; default: perm = 0666; break; } devdir(c, q, up->genbuf, e->buffered, eve, perm, dp); return 1; } static Usbhost* usbprobe(int cardno, int ctlrno) { Usbhost *uh; char buf[128], *ebuf, name[64], *p, *type; uh = malloc(sizeof(Usbhost)); memset(uh, 0, sizeof(Usbhost)); uh->tbdf = BUSUNKNOWN; if(cardno < 0){ if(isaconfig("usb", ctlrno, uh) == 0){ free(uh); return nil; } for(cardno = 0; usbtypes[cardno].type; cardno++){ type = uh->type; if(type==nil || *type==0) type = "uhci"; if(cistrcmp(usbtypes[cardno].type, type)) continue; break; } } if(cardno >= MaxUsb || usbtypes[cardno].type == nil){ free(uh); return nil; } if(usbtypes[cardno].reset(uh) < 0){ free(uh); return nil; } /* * IRQ2 doesn't really exist, it's used to gang the interrupt * controllers together. A device set to IRQ2 will appear on * the second interrupt controller as IRQ9. */ if(uh->irq == 2) uh->irq = 9; snprint(name, sizeof(name), "usb%d", ctlrno); intrenable(uh->irq, uh->interrupt, uh, uh->tbdf, name); ebuf = buf + sizeof buf; p = seprint(buf, ebuf, "#U/usb%d: %s: port 0x%luX irq %d", ctlrno, usbtypes[cardno].type, uh->port, uh->irq); if(uh->mem) p = seprint(p, ebuf, " addr 0x%luX", PADDR(uh->mem)); if(uh->size) seprint(p, ebuf, " size 0x%luX", uh->size); print("%s\n", buf); return uh; } static void usbreset(void) { int cardno, ctlrno; Usbhost *uh; for(ctlrno = 0; ctlrno < MaxUsb; ctlrno++){ if((uh = usbprobe(-1, ctlrno)) == nil) continue; usbhost[ctlrno] = uh; } if(getconf("*nousbprobe")) return; cardno = ctlrno = 0; while(usbtypes[cardno].type != nil && ctlrno < MaxUsb){ if(usbhost[ctlrno] != nil){ ctlrno++; continue; } if((uh = usbprobe(cardno, ctlrno)) == nil){ cardno++; continue; } usbhost[ctlrno] = uh; ctlrno++; } } void usbinit(void) { Udev *d; int ctlrno; Usbhost *uh; for(ctlrno = 0; ctlrno < MaxUsb; ctlrno++){ uh = usbhost[ctlrno]; if(uh == nil) continue; if(uh->init != 0) uh->init(uh); /* reserve device for configuration */ d = usbnewdevice(uh); incref(d); d->state = Attached; } } Chan * usbattach(char *spec) { return devattach('U', spec); } static Walkqid* usbwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, nil, 0, usbgen); } static int usbstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, nil, 0, usbgen); } Chan* usbopen(Chan *c, int omode) { Udev *d; Endpt *e; int f, s, type; Usbhost *uh; if(c->qid.type == QTDIR) return devopen(c, omode, nil, 0, usbgen); f = 0; type = TYPE(c->qid); if(type == Qnew){ d = usbdevice(c); d = usbnewdevice(d->uh); XPRINT("usbopen, new dev 0x%p\n", d); if(d == nil) { XPRINT("usbopen failed (usbnewdevice)\n"); error(Enodev); } type = Qctl; mkqid(&c->qid, PATH(type, d->x, CTLR(c->qid)), d->id, QTFILE); f = 1; } if(type < Q3rd){ XPRINT("usbopen, devopen < Q3rd\n"); return devopen(c, omode, nil, 0, usbgen); } d = usbdevice(c); uh = d->uh; qlock(uh); if(waserror()){ qunlock(uh); nexterror(); } switch(type){ case Qctl: if(0&&d->busy) error(Einuse); d->busy = 1; if(!f) incref(d); XPRINT("usbopen, Qctl 0x%p\n", d); break; default: s = type - Qep0; XPRINT("usbopen, default 0x%p, %d\n", d, s); if(s >= 0 && s < nelem(d->ep)){ if((e = d->ep[s]) == nil) { XPRINT("usbopen failed (endpoint)\n"); error(Enodev); } XPRINT("usbopen: dev 0x%p, ept 0x%p\n", d, e); uh->epopen(uh, e); e->foffset = 0; e->toffset = 0; e->poffset = 0; e->buffered = 0; } incref(d); break; } poperror(); qunlock(uh); c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } void usbclose(Chan *c) { Udev *d; int ept, type; Usbhost *uh; type = TYPE(c->qid); if(c->qid.type == QTDIR || type < Q3rd) return; d = usbdevice(c); uh = d->uh; qlock(uh); if(waserror()){ qunlock(uh); nexterror(); } if(type == Qctl) d->busy = 0; XPRINT("usbclose: dev 0x%p\n", d); if(c->flag & COPEN){ ept = (type != Qctl) ? type - Qep0 : -1; XPRINT("usbclose: freedev 0x%p\n", d); freedev(d, ept); } poperror(); qunlock(uh); } static char * epstatus(char *s, char *se, Endpt *e, int i) { char *p; p = seprint(s, se, "%2d %#6.6lux %10lud bytes %10lud blocks\n", i, e->csp, e->nbytes, e->nblocks); if(e->iso){ p = seprint(p, se, "bufsize %6d buffered %6d", e->maxpkt, e->buffered); if(e->toffset) p = seprint(p, se, " offset %10lud time %19lld\n", e->toffset, e->time); p = seprint(p, se, "\n"); } return p; } long usbread(Chan *c, void *a, long n, vlong offset) { int t, i; Udev *d; Endpt *e; Usbhost *uh; char *s, *se, *p; if(c->qid.type == QTDIR) return devdirread(c, a, n, nil, 0, usbgen); d = usbdevice(c); uh = d->uh; t = TYPE(c->qid); if(t >= Qep0) { t -= Qep0; if(t >= nelem(d->ep)) error(Eio); e = d->ep[t]; if(e == nil || e->mode == OWRITE) error(Egreg); if(t == 0) { if(e->iso) error(Egreg); e->data01 = 1; n = uh->read(uh, e, a, n, 0LL); if(e->setin){ e->setin = 0; e->data01 = 1; uh->write(uh, e, "", 0, 0LL, TokOUT); } return n; } return uh->read(uh, e, a, n, offset); } s = smalloc(READSTR); se = s+READSTR; if(waserror()){ free(s); nexterror(); } switch(t){ case Qport: uh->portinfo(uh, s, se); break; case Qctl: seprint(s, se, "%11d %11d\n", d->x, d->id); break; case Qstatus: if (d->did || d->vid) p = seprint(s, se, "%s %#6.6lux %#4.4ux %#4.4ux\n", devstates[d->state], d->csp, d->vid, d->did); else p = seprint(s, se, "%s %#6.6lux\n", devstates[d->state], d->csp); for(i=0; i<nelem(d->ep); i++) { e = d->ep[i]; if(e == nil) continue; /* TO DO: freeze e */ p = epstatus(p, se, e, i); } } n = readstr(offset, a, n, s); poperror(); free(s); return n; } long usbwrite(Chan *c, void *a, long n, vlong offset) { Udev *d; Endpt *e; Cmdtab *ct; Cmdbuf *cb; Usbhost *uh; int id, nw, t, i; char cmd[50]; if(c->qid.type == QTDIR) error(Egreg); d = usbdevice(c); uh = d->uh; t = TYPE(c->qid); switch(t){ case Qport: cb = parsecmd(a, n); if(waserror()){ free(cb); nexterror(); } ct = lookupcmd(cb, usbportmsg, nelem(usbportmsg)); id = strtol(cb->f[1], nil, 0); switch(ct->index){ case PMdisable: uh->portenable(uh, id, 0); break; case PMenable: uh->portenable(uh, id, 1); break; case PMreset: uh->portreset(uh, id); break; } poperror(); free(cb); return n; case Qctl: cb = parsecmd(a, n); if(waserror()){ free(cb); nexterror(); } ct = lookupcmd(cb, usbctlmsg, nelem(usbctlmsg)); switch(ct->index){ case CMspeed: d->ls = strtoul(cb->f[1], nil, 0) == 0; break; case CMclass: if (cb->nf != 4 && cb->nf != 6) cmderror(cb, Ebadusbmsg); /* class #ifc ept csp ( == class subclass proto) [vendor product] */ d->npt = strtoul(cb->f[1], nil, 0); /* # of interfaces */ i = strtoul(cb->f[2], nil, 0); /* endpoint */ if (i < 0 || i >= nelem(d->ep) || d->npt > nelem(d->ep) || i >= d->npt) cmderror(cb, Ebadusbmsg); if (cb->nf == 6) { d->vid = strtoul(cb->f[4], nil, 0); d->did = strtoul(cb->f[5], nil, 0); } if (i == 0) d->csp = strtoul(cb->f[3], nil, 0); if(d->ep[i] == nil){ XPRINT("calling devendpt in usbwrite (CMclass)\n"); d->ep[i] = devendpt(d, i, 1); } d->ep[i]->csp = strtoul(cb->f[3], nil, 0); break; case CMdata: i = strtoul(cb->f[1], nil, 0); if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil) error(Ebadusbmsg); e = d->ep[i]; e->data01 = strtoul(cb->f[2], nil, 0) != 0; break; case CMmaxpkt: i = strtoul(cb->f[1], nil, 0); if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil) error(Ebadusbmsg); e = d->ep[i]; e->maxpkt = strtoul(cb->f[2], nil, 0); if(e->maxpkt > 1500) e->maxpkt = 1500; break; case CMadjust: i = strtoul(cb->f[1], nil, 0); if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil) error(Ebadusbmsg); e = d->ep[i]; if (e->iso == 0) error(Eperm); i = strtoul(cb->f[2], nil, 0); /* speed may not result in change of maxpkt */ if (i < (e->maxpkt-1)/e->samplesz * 1000/e->pollms || i > e->maxpkt/e->samplesz * 1000/e->pollms){ snprint(cmd, sizeof(cmd), "%d < %d < %d?", (e->maxpkt-1)/e->samplesz * 1000/e->pollms, i, e->maxpkt/e->samplesz * 1000/e->pollms); error(cmd); } e->hz = i; break; case CMdebug: i = strtoul(cb->f[1], nil, 0); if(i < -1 || i >= nelem(d->ep) || d->ep[i] == nil) error(Ebadusbmsg); if (i == -1) debug = 0; else { debug = 1; e = d->ep[i]; e->debug = strtoul(cb->f[2], nil, 0); } break; case CMunstall: i = strtoul(cb->f[1], nil, 0); if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil) error(Ebadusbmsg); e = d->ep[i]; e->err = nil; break; case CMep: /* ep n `bulk' mode maxpkt nbuf OR * ep n period mode samplesize Hz */ i = strtoul(cb->f[1], nil, 0); if(i < 0 || i >= nelem(d->ep)) { XPRINT("field 1: 0 <= %d < %d\n", i, nelem(d->ep)); error(Ebadarg); } if((e = d->ep[i]) == nil){ XPRINT("calling devendpt in usbwrite (CMep)\n"); e = devendpt(d, i, 1); } qlock(uh); if(waserror()){ freept(e); qunlock(uh); nexterror(); } if(e->active) error(Eperm); if(strcmp(cb->f[2], "bulk") == 0){ /* ep n `bulk' mode maxpkt nbuf */ e->iso = 0; i = strtoul(cb->f[4], nil, 0); if(i < 8 || i > 1023) i = 8; e->maxpkt = i; i = strtoul(cb->f[5], nil, 0); if(i >= 1 && i <= 32) e->nbuf = i; } else { /* ep n period mode samplesize Hz */ i = strtoul(cb->f[2], nil, 0); if(i > 0 && i <= 1000){ e->pollms = i; }else { XPRINT("field 4: 0 <= %d <= 1000\n", i); error(Ebadarg); } i = strtoul(cb->f[4], nil, 0); if(i >= 1 && i <= 8){ e->samplesz = i; }else { XPRINT("field 4: 0 < %d <= 8\n", i); error(Ebadarg); } i = strtoul(cb->f[5], nil, 0); if(i >= 1 && i*e->samplesz <= 12*1000*1000){ /* Hz */ e->hz = i; e->remain = 0; }else { XPRINT("field 5: 1 < %d <= 100000 Hz\n", i); error(Ebadarg); } e->maxpkt = (e->hz * e->pollms + 999)/1000 * e->samplesz; e->iso = 1; } e->mode = strcmp(cb->f[3],"r") == 0? OREAD : strcmp(cb->f[3],"w") == 0? OWRITE : ORDWR; uh->epmode(uh, e); poperror(); qunlock(uh); } poperror(); free(cb); return n; case Qep0: /* SETUP endpoint 0 */ /* should canqlock etc */ e = d->ep[0]; if(e == nil || e->iso) error(Egreg); if(n < 8) error(Eio); nw = *(uchar*)a & RD2H; e->data01 = 0; n = uh->write(uh, e, a, n, 0LL, TokSETUP); if(nw == 0) { /* host to device: use IN[DATA1] to ack */ e->data01 = 1; nw = uh->read(uh, e, cmd, 0LL, 8); if(nw != 0) error(Eio); /* could provide more status */ }else e->setin = 1; /* two-phase */ break; default: /* sends DATA[01] */ t -= Qep0; if(t < 0 || t >= nelem(d->ep)) error(Egreg); e = d->ep[t]; if(e == nil || e->mode == OREAD) error(Egreg); n = uh->write(uh, e, a, n, offset, TokOUT); break; } return n; } Dev usbdevtab = { 'U', "usb", usbreset, usbinit, devshutdown, usbattach, usbwalk, usbstat, usbopen, devcreate, usbclose, usbread, devbread, usbwrite, devbwrite, devremove, devwstat, };