@@ 197,9 197,9 @@ enum {
static char *devstates[] = {
[Disabled] "Disabled",
- [Attached] "Attached",
+ [Attached] "Attached",
[Enabled] "Enabled",
- [Assigned] "Assigned",
+ [Assigned] "Assigned",
[Configured] "Configured",
};
@@ 213,14 213,14 @@ struct Endpt {
int id; /* hardware endpoint address */
int maxpkt; /* maximum packet size (from endpoint descriptor) */
int data01; /* 0=DATA0, 1=DATA1 */
- byte eof;
+ byte eof;
ulong csp;
- byte mode; /* OREAD, OWRITE, ORDWR */
- byte nbuf; /* number of buffers allowed */
- byte periodic;
- byte iso;
- byte debug;
- byte active; /* listed for examination by interrupts */
+ byte mode; /* OREAD, OWRITE, ORDWR */
+ byte nbuf; /* number of buffers allowed */
+ byte periodic;
+ byte iso;
+ byte debug;
+ byte active; /* listed for examination by interrupts */
int sched; /* schedule index; -1 if undefined or aperiodic */
int setin;
ulong bw; /* bandwidth requirement */
@@ 369,7 369,8 @@ dumptd(TD *t, int follow)
XPRINT("td %8.8lux: ", t);
XPRINT("l=%8.8lux s=%8.8lux d=%8.8lux b=%8.8lux %8.8lux f=%8.8lux\n",
t->link, t->status, t->dev, t->buffer, t->bp?(ulong)t->bp->rp:0, t->flags);
- XPRINT("\ts=%s,ep=%ld,d=%ld,D=%ld\n", buf, (t->dev>>15)&0xF, (t->dev>>8)&0xFF, (t->dev>>19)&1);
+ XPRINT("\ts=%s,ep=%ld,d=%ld,D=%ld\n",
+ buf, (t->dev>>15)&0xF, (t->dev>>8)&0xFF, (t->dev>>19)&1);
if(debug && t->bp && (t->flags & CancelTD) == 0)
dumpdata(t->bp, n);
if(!follow || t->link & Terminate || t->link & IsQH)
@@ 479,7 480,8 @@ cleantd(TD *t, int discard)
Block *b;
int n, err;
- XPRINT("cleanTD: %8.8lux %8.8lux %8.8lux %8.8lux\n", t->link, t->status, t->dev, t->buffer);
+ XPRINT("cleanTD: %8.8lux %8.8lux %8.8lux %8.8lux\n",
+ t->link, t->status, t->dev, t->buffer);
if(t->ep != nil && t->ep->debug)
dumptd(t, 0);
if(t->status & Active)
@@ 487,7 489,8 @@ cleantd(TD *t, int discard)
err = t->status & (AnyError&~NAKed);
/* TO DO: on t->status&AnyError, q->entries will not have advanced */
if (err)
- XPRINT("cleanTD: Error %8.8lux %8.8lux %8.8lux %8.8lux\n", t->link, t->status, t->dev, t->buffer);
+ XPRINT("cleanTD: Error %8.8lux %8.8lux %8.8lux %8.8lux\n",
+ t->link, t->status, t->dev, t->buffer);
switch(t->dev&0xFF){
case TokIN:
if(discard || (t->flags & CancelTD) || t->ep == nil || t->ep->x!=0&&err){
@@ 550,7 553,8 @@ cleanq(QH *q, int discard, int vf)
ilock(ub);
tp = nil;
for(t = q->first; t != nil;){
- XPRINT("cleanq: %8.8lux %8.8lux %8.8lux %8.8lux %8.8lux %8.8lux\n", t->link, t->status, t->dev, t->buffer, t->flags, t->next);
+ XPRINT("cleanq: %8.8lux %8.8lux %8.8lux %8.8lux %8.8lux %8.8lux\n",
+ t->link, t->status, t->dev, t->buffer, t->flags, t->next);
if(t->status & Active){
if(t->status & NAKed){
t->status = (t->status & ~NAKed) | IOC; /* ensure interrupt next frame */
@@ 565,9 569,9 @@ cleanq(QH *q, int discard, int vf)
t = t->next;
continue;
}
-tp = t;
-t = t->next;
-continue;
+ tp = t;
+ t = t->next;
+ continue;
break;
}
t->status &= ~IOC;
@@ 879,4 883,1082 @@ schedendpt(Endpt *e)
QH *qh;
int q;
- if(!e->periodic || e->sched
+ if(!e->periodic || e->sched >= 0)
+ return 0;
+ ub = &ubus;
+ if(e->epq == nil){
+ e->epq = allocqh(ub);
+ if(e->epq == nil)
+ return -1;
+ }
+ qlock(ub->tree);
+ q = pickschedq(ub->tree, e->pollms, e->bw, ~0); /* TO DO: bus bandwidth limit */
+ if(q < 0)
+ return -1;
+ ub->tree->bw[q] += e->bw;
+ qh = &ub->tree->root[q];
+ e->sched = q;
+ e->epq->head = qh->entries;
+ e->epq->entries = Terminate;
+ qh->entries = PADDR(e->epq) | IsQH;
+ qunlock(ub->tree);
+ return 0;
+}
+
+static void
+unschedendpt(Endpt *e)
+{
+ Ctlr *ub;
+ ulong p;
+ QH *qh;
+ int q;
+
+ ub = &ubus;
+ if(!e->periodic || (q = e->sched) < 0)
+ return;
+ p = PADDR(e->epq) | IsQH;
+ qlock(ub->tree);
+ ub->tree->bw[q] -= e->bw;
+ qh = &ub->tree->root[q];
+ for(; qh->entries != p; qh = QFOL(qh->entries))
+ if(qh->entries & Terminate || (qh->entries & IsQH) == 0){
+ qunlock(ub->tree);
+ panic("usb: unschedendpt");
+ }
+ qh->entries = e->epq->head;
+ qunlock(ub->tree);
+ e->epq->head = Terminate;
+}
+
+static Endpt *
+devendpt(Udev *d, int id, int add)
+{
+ Endpt *e, **p;
+ Ctlr *ub;
+
+ ub = &ubus;
+ p = &d->ep[id&0xF];
+ lock(d);
+ if((e = *p) != nil){
+ incref(e);
+ 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->periodic = 0;
+ e->sched = -1;
+ e->maxpkt = 8;
+ e->pollms = 0;
+ e->bw = 0;
+ e->nbuf = 1;
+ e->dev = d;
+ e->active = 0;
+ e->epq = allocqh(ub);
+ if(e->epq == nil)
+ panic("devendpt");
+
+ lock(d);
+ if(*p != nil){
+ incref(*p);
+ unlock(d);
+ 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)
+{
+ if(e != nil && decref(e) == 0){
+ XPRINT("freept(%d,%d)\n", e->dev->x, e->x);
+ unschedendpt(e);
+ e->dev->ep[e->x] = nil;
+ eptdeactivate(e);
+ if(e->epq != nil)
+ freeqh(&ubus, e->epq);
+ free(e);
+ }
+}
+
+static void
+usbdevreset(Udev *d)
+{
+ d->state = Disabled;
+ if(Class(d->csp) == Hubclass)
+ for(d = d->ports; d != nil; d = d->next)
+ usbdevreset(d);
+}
+
+static void
+freedev(Udev *d)
+{
+ int i;
+
+ if(d != nil && decref(d) == 0){
+ for(i=0; i<nelem(d->ep); i++)
+ freept(d->ep[i]);
+ if(d->x >= 0)
+ usbdev[d->x] = nil;
+ free(d);
+ }
+}
+
+static void
+hubportreset(Udev *h, int p)
+{
+ USED(h, p);
+ /* reset state of each attached device? */
+}
+
+static int ioports[] = {-1, Portsc0, Portsc1};
+
+static void
+portreset(int port)
+{
+ Ctlr *ub;
+ int i, p;
+
+ /* should check that device not being configured on other port? */
+ p = ioports[port];
+ ub = &ubus;
+ qlock(&ub->resetl);
+ if(waserror()){
+ qunlock(&ub->resetl);
+ nexterror();
+ }
+ XPRINT("r: %x\n", IN(p));
+ ilock(ub);
+ OUT(p, PortReset);
+ delay(12); /* BUG */
+ XPRINT("r2: %x\n", IN(p));
+ OUT(p, IN(p) & ~PortReset);
+ XPRINT("r3: %x\n", IN(p));
+ OUT(p, IN(p) | PortEnable);
+ microdelay(64);
+ for(i=0; i<1000 && (IN(p) & PortEnable) == 0; i++)
+ ;
+ XPRINT("r': %x %d\n", IN(p), i);
+ OUT(p, (IN(p) & ~PortReset)|PortEnable);
+ iunlock(ub);
+ hubportreset(nil, port);
+ poperror();
+ qunlock(&ub->resetl);
+}
+
+static void
+portenable(int port, int on)
+{
+ Ctlr *ub;
+ int w, p;
+
+ /* should check that device not being configured on other port? */
+ p = ioports[port];
+ ub = &ubus;
+ qlock(&ub->resetl);
+ if(waserror()){
+ qunlock(&ub->resetl);
+ nexterror();
+ }
+ ilock(ub);
+ w = IN(p);
+ if(on)
+ w |= PortEnable;
+ else
+ w &= ~PortEnable;
+ OUT(p, w);
+ microdelay(64);
+ iunlock(ub);
+ XPRINT("e: %x\n", IN(p));
+ if(!on)
+ hubportreset(nil, port);
+ poperror();
+ qunlock(&ub->resetl);
+}
+
+static int
+portinfo(Ctlr *ub, int *p0, int *p1)
+{
+ int m, v;
+
+ ilock(ub);
+ m = 0;
+ if((v = IN(Portsc0)) & PortChange){
+ OUT(Portsc0, v);
+ m |= 1<<0;
+ }
+ *p0 = v;
+ if((v = IN(Portsc1)) & PortChange){
+ OUT(Portsc1, v);
+ m |= 1<<1;
+ }
+ *p1 = v;
+ iunlock(ub);
+ return m;
+}
+
+static void
+interrupt(Ureg*, void *a)
+{
+ Ctlr *ub;
+ Endpt *e;
+ int s;
+ QH *q;
+
+ ub = a;
+ s = IN(Status);
+ OUT(Status, s);
+ if ((s & 0x1f) == 0)
+ return;
+ XPRINT("usbint: #%x f%d\n", s, IN(Frnum));
+ if (s & 0x1a) {
+ XPRINT("cmd #%x sofmod #%x\n", IN(Cmd), inb(ub->io+SOFMod));
+ XPRINT("sc0 #%x sc1 #%x\n", IN(Portsc0), IN(Portsc1));
+ }
+
+ XPRINT("cleanq(ub->ctlq, 0, 0)\n");
+ cleanq(ub->ctlq, 0, 0);
+ XPRINT("cleanq(ub->bulkq, 0, Vf)\n");
+ cleanq(ub->bulkq, 0, Vf);
+ XPRINT("clean recvq\n");
+ for (q = ub->recvq->next; q; q = q->hlink) {
+ XPRINT("cleanq(q, 0, Vf)\n");
+ cleanq(q, 0, Vf);
+ }
+ ilock(&activends);
+ for(e = activends.f; e != nil; e = e->activef)
+ if(e->epq != nil) {
+ XPRINT("cleanq(e->epq, 0, 0)\n");
+ cleanq(e->epq, 0, 0);
+ }
+ iunlock(&activends);
+}
+
+enum
+{
+ Qtopdir = 0,
+ Q2nd,
+ Qbusctl,
+ Qnew,
+ Qport,
+ Q3rd,
+ Qctl,
+ Qsetup,
+ Qdebug,
+ Qstatus,
+ Qep0,
+ /* other endpoint files */
+};
+
+/*
+ * Qid path is:
+ * 8 bits of file type (qids above)
+ * 10 bits of slot number +1; 0 means not attached to device
+ */
+#define QSHIFT 8 /* location in qid of device # */
+#define QMASK ((1<<QSHIFT)-1)
+
+#define QID(q) ((ulong)(q).path&QMASK)
+#define DEVPATH(p) ((p)>>QSHIFT)
+
+static Dirtab usbdir2[] = {
+ "new", {Qnew}, 0, 0666,
+ "ctl", {Qbusctl}, 0, 0666,
+ "port", {Qport}, 0, 0444,
+};
+
+static Dirtab usbdir3[]={
+ "ctl", {Qctl}, 0, 0666,
+ "setup", {Qsetup}, 0, 0666,
+ "status", {Qstatus}, 0, 0444,
+ "debug", {Qdebug}, 0, 0666,
+ /* epNdata names are generated on demand */
+};
+
+static Udev *
+usbdeviceofpath(ulong path)
+{
+ int s;
+
+ s = DEVPATH(path);
+ if(s == 0)
+ return nil;
+ return usbdev[s-1];
+}
+
+static Udev *
+usbdevice(Chan *c)
+{
+ Udev *d;
+
+ d = usbdeviceofpath(c->qid.path);
+ if(d == nil || d->id != c->qid.vers || d->state == Disabled)
+ error(Ehungup);
+ return d;
+}
+
+static Udev *
+usbnewdevice(void)
+{
+ Udev *d;
+ Endpt *e;
+ int i;
+
+ d = nil;
+ qlock(&usbstate);
+ if(waserror()){
+ qunlock(&usbstate);
+ nexterror();
+ }
+ for(i=0; i<nelem(usbdev); i++)
+ if(usbdev[i] == nil){
+ ubus.idgen++;
+ d = mallocz(sizeof(*d), 1);
+ d->ref = 1;
+ d->x = i;
+ d->id = (ubus.idgen << 8) | i;
+ d->state = Enabled;
+ e = devendpt(d, 0, 1); /* always provide control endpoint 0 */
+ e->mode = ORDWR;
+ e->periodic = 0;
+ e->sched = -1;
+ usbdev[i] = d;
+ break;
+ }
+ poperror();
+ qunlock(&usbstate);
+ return d;
+}
+
+static void
+usbreset(void)
+{
+ Pcidev *cfg;
+ int i;
+ ulong port;
+ QTree *qt;
+ TD *t;
+ Ctlr *ub;
+ ISAConf isa;
+
+ if(isaconfig("usb", 0, &isa) == 0) {
+ XPRINT("usb not in plan9.ini\n");
+// return;
+ }
+ ub = &ubus;
+ memset(&cfg, 0, sizeof(cfg));
+ cfg = pcimatch(0, 0x8086, 0x7112); /* Intel chipset PIIX 4*/
+ if(cfg == nil) {
+ cfg = pcimatch(0, 0x1106, 0x0586); /* Via chipset */
+ if(cfg == nil) {
+ DPRINT("No USB device found\n");
+ return;
+ }
+ }
+ port = cfg->mem[4].bar & ~0x0F;
+ if (port == 0) {
+ print("usb: failed to map registers\n");
+ return;
+ }
+
+ DPRINT("USB: %x/%x port 0x%lux size 0x%x irq %d\n",
+ cfg->vid, cfg->did, port, cfg->mem[4].size, cfg->intl);
+
+ i = inb(port+SOFMod);
+ if(1){
+ OUT(Cmd, 4); /* global reset */
+ delay(15);
+ OUT(Cmd, 0); /* end reset */
+ delay(4);
+ }
+ outb(port+SOFMod, i);
+ /*
+ * Interrupt handler.
+ * Bail out if no IRQ assigned by the BIOS.
+ */
+ if(cfg->intl == 0xFF || cfg->intl == 0)
+ return;
+ intrenable(cfg->intl, interrupt, ub, cfg->tbdf, "usb");
+
+ ub->io = port;
+ ub->tdpool = xspanalloc(128*sizeof(TD), 16, 0);
+ for(i=128; --i>=0;){
+ ub->tdpool[i].next = ub->freetd;
+ ub->freetd = &ub->tdpool[i];
+ }
+ ub->qhpool = xspanalloc(32*sizeof(QH), 16, 0);
+ for(i=32; --i>=0;){
+ ub->qhpool[i].next = ub->freeqh;
+ ub->freeqh = &ub->qhpool[i];
+ }
+
+ /*
+ * the root of the periodic (interrupt & isochronous) scheduling tree
+ * points to the control queue and the bandwidth sop for bulk traffic.
+ * this is looped following the instructions in PIIX4 errata 29773804.pdf:
+ * a QH links to a looped but inactive TD as its sole entry,
+ * with its head entry leading on to the bulk traffic, the last QH of which
+ * links back to the empty QH.
+ */
+ ub->ctlq = allocqh(ub);
+ ub->bwsop = allocqh(ub);
+ ub->bulkq = allocqh(ub);
+ ub->recvq = allocqh(ub);
+ t = alloctd(ub); /* inactive TD, looped */
+ t->link = PADDR(t);
+ ub->bwsop->entries = PADDR(t);
+ ub->ctlq->head = PADDR(ub->bwsop) | IsQH;
+ ub->bwsop->head = PADDR(ub->bulkq) | IsQH;
+ ub->bulkq->head = PADDR(ub->recvq) | IsQH;
+ ub->recvq->head = PADDR(ub->bwsop) | IsQH; /* loop back */
+ XPRINT("usbcmd\t0x%.4x\nusbsts\t0x%.4x\nusbintr\t0x%.4x\nfrnum\t0x%.2x\n",
+ IN(Cmd), IN(Status), IN(Usbintr), inb(port+Frnum));
+ XPRINT("frbaseadd\t0x%.4x\nsofmod\t0x%x\nportsc1\t0x%.4x\nportsc2\t0x%.4x\n",
+ IN(Flbaseadd), inb(port+SOFMod), IN(Portsc0), IN(Portsc1));
+ OUT(Cmd, 0); /* stop */
+ ub->frames = xspanalloc(FRAMESIZE, FRAMESIZE, 0);
+ qt = mkqhtree(ub->frames, NFRAME, 32);
+ if(qt == nil){
+ print("usb: can't allocate scheduling tree\n");
+ ub->io = 0;
+ return;
+ }
+ qt->root->head = PADDR(ub->ctlq) | IsQH;
+ ub->tree = qt;
+ XPRINT("usb tree: nel=%d depth=%d\n", qt->nel, qt->depth);
+
+ outl(port+Flbaseadd, PADDR(ub->frames));
+ OUT(Frnum, 0);
+ OUT(Usbintr, 0xF); /* enable all interrupts */
+ XPRINT("cmd 0x%x sofmod 0x%x\n", IN(Cmd), inb(port+SOFMod));
+ XPRINT("sc0 0x%x sc1 0x%x\n", IN(Portsc0), IN(Portsc1));
+}
+
+void
+usbinit(void)
+{
+ Udev *d;
+
+ if(ubus.io != 0 && usbdev[0] == nil){
+ d = usbnewdevice(); /* reserve device 0 for configuration */
+ incref(d);
+ d->state = Attached;
+ }
+}
+
+Chan *
+usbattach(char *spec)
+{
+ Ctlr *ub;
+
+ ub = &ubus;
+ if(ub->io == 0) {
+ XPRINT("usbattach failed\n");
+ error(Enodev);
+ }
+ if((IN(Cmd)&1)==0 || *spec)
+ OUT(Cmd, 1); /* run */
+// pprint("at: c=%x s=%x c0=%x\n", IN(Cmd), IN(Status), IN(Portsc0));
+ return devattach('U', spec);
+}
+
+static int
+usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+ int t;
+ Qid q;
+ ulong path;
+ Udev *d;
+ Dirtab *tab;
+ Endpt *e;
+
+ /*
+ * Top level directory contains the name of the device.
+ */
+ 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 == 0){
+ mkqid(&q, Q2nd, 0, QTDIR);
+ devdir(c, q, "usb", 0, eve, 0555, dp);
+ return 1;
+ }
+ return -1;
+ }
+
+ /*
+ * Second level contains "new" plus all the clients.
+ */
+ t = QID(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)){
+ tab = &usbdir2[s];
+ devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+ }
+ s -= nelem(usbdir2);
+ if(s >= 0 && s < nelem(usbdev)){
+ d = usbdev[s];
+ if(d == nil)
+ return -1;
+ sprint(up->genbuf, "%d", s);
+ mkqid(&q, ((s+1)<<QSHIFT)|Q3rd, d->id, QTDIR);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+ return -1;
+ }
+
+ /*
+ * Third level.
+ */
+ path = c->qid.path & ~QMASK; /* slot component */
+ if(s == DEVDOTDOT){
+ mkqid(&q, Q2nd, c->qid.vers, QTDIR);
+ devdir(c, q, "usb", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s < nelem(usbdir3)){
+ Dirtab *tab = &usbdir3[s];
+ mkqid(&q, path | tab->qid.path, c->qid.vers, QTFILE);
+ devdir(c, q, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+ }
+
+ /* active endpoints */
+ d = usbdeviceofpath(path);
+ if(d == nil)
+ return -1;
+ s -= nelem(usbdir3);
+ if(s < 0 || 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), c->qid.vers, QTFILE);
+ devdir(c, q, up->genbuf, 0, eve, e->mode==OREAD? 0444: e->mode==OWRITE? 0222: 0666, dp);
+ return 1;
+}
+
+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;
+ int f, s;
+
+ if(c->qid.type == QTDIR)
+ return devopen(c, omode, nil, 0, usbgen);
+
+ f = 0;
+ if(QID(c->qid) == Qnew){
+ d = usbnewdevice();
+ if(d == nil) {
+ XPRINT("usbopen failed (usbnewdevice)\n");
+ error(Enodev);
+ }
+ c->qid.path = Qctl|((d->x+1)<<QSHIFT);
+ c->qid.vers = d->id;
+ f = 1;
+ }
+
+ if(c->qid.path < Q3rd)
+ return devopen(c, omode, nil, 0, usbgen);
+
+ qlock(&usbstate);
+ if(waserror()){
+ qunlock(&usbstate);
+ nexterror();
+ }
+
+ switch(QID(c->qid)){
+ case Qctl:
+ d = usbdevice(c);
+ if(0&&d->busy)
+ error(Einuse);
+ d->busy = 1;
+ if(!f)
+ incref(d);
+ break;
+
+ default:
+ d = usbdevice(c);
+ s = QID(c->qid) - Qep0;
+ if(s >= 0 && s < nelem(d->ep)){
+ Endpt *e;
+ if((e = d->ep[s]) == nil) {
+ XPRINT("usbopen failed (endpoint)\n");
+ error(Enodev);
+ }
+ if(schedendpt(e) < 0)
+ error("can't schedule USB endpoint");
+ eptactivate(e);
+ }
+ incref(d);
+ break;
+ }
+ poperror();
+ qunlock(&usbstate);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+void
+usbcreate(Chan *c, char *name, int omode, ulong perm)
+{
+ USED(c, name, omode, perm);
+ error(Eperm);
+}
+
+void
+usbremove(Chan*)
+{
+ error(Eperm);
+}
+
+void
+usbwstat(Chan *c, char *dp)
+{
+ USED(c, dp);
+ error(Eperm);
+}
+
+void
+usbclose(Chan *c)
+{
+ Udev *d;
+
+ if(c->qid.type == QTDIR || c->qid.path < Q3rd)
+ return;
+ qlock(&usbstate);
+ if(waserror()){
+ qunlock(&usbstate);
+ nexterror();
+ }
+ d = usbdevice(c);
+ if(QID(c->qid) == Qctl)
+ d->busy = 0;
+ if(c->flag & COPEN)
+ freedev(d);
+ poperror();
+ qunlock(&usbstate);
+}
+
+static int
+eptinput(void *arg)
+{
+ Endpt *e;
+
+ e = arg;
+ return e->eof || e->err || qcanread(e->rq);
+}
+
+static long
+readusb(Endpt *e, void *a, long n)
+{
+ Block *b;
+ uchar *p;
+ long l, i;
+
+ XPRINT("qlock(%p)\n", &e->rlock);
+ qlock(&e->rlock);
+ XPRINT("got qlock(%p)\n", &e->rlock);
+ if(waserror()){
+ qunlock(&e->rlock);
+ eptcancel(e);
+ nexterror();
+ }
+ p = a;
+ do {
+ if(e->eof) {
+ XPRINT("e->eof\n");
+ break;
+ }
+ if(e->err)
+ error(e->err);
+ qrcv(e);
+ if(!e->iso)
+ e->data01 ^= 1;
+ sleep(&e->rr, eptinput, e);
+ if(e->err)
+ error(e->err);
+ b = qget(e->rq); /* TO DO */
+ if(b == nil) {
+ XPRINT("b == nil\n");
+ break;
+ }
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ l = BLEN(b);
+ if((i = l) > n)
+ i = n;
+ if(i > 0){
+ memmove(p, b->rp, i);
+ p += i;
+ }
+ poperror();
+ freeb(b);
+ n -= i;
+ } while (l == e->maxpkt && n > 0);
+ poperror();
+ qunlock(&e->rlock);
+ return p-(uchar*)a;
+}
+
+long
+usbread(Chan *c, void *a, long n, vlong offset)
+{
+ Endpt *e;
+ Udev *d;
+ char buf[48], *s;
+ int t, w0, w1, ps, l, i;
+
+ if(c->qid.type == QTDIR)
+ return devdirread(c, a, n, nil, 0, usbgen);
+
+ t = QID(c->qid);
+ switch(t){
+ case Qbusctl:
+ snprint(buf, sizeof(buf), "%11d %11d ", 0, 0);
+ return readstr(offset, a, n, buf);
+
+ case Qport:
+ ps = portinfo(&ubus, &w0, &w1);
+ snprint(buf, sizeof(buf), "0x%ux 0x%ux 0x%ux ", ps, w0, w1);
+ return readstr(offset, a, n, buf);
+
+ case Qctl:
+ d = usbdevice(c);
+ sprint(buf, "%11d %11d ", d->x, d->id);
+ return readstr(offset, a, n, buf);
+
+ case Qsetup: /* endpoint 0 */
+ d = usbdevice(c);
+ if((e = d->ep[0]) == nil)
+ error(Eio); /* can't happen */
+ e->data01 = 1;
+ n = readusb(e, a, n);
+ if(e->setin){
+ e->setin = 0;
+ e->data01 = 1;
+ writeusb(e, "", 0, TokOUT);
+ }
+ break;
+
+ case Qdebug:
+ n=0;
+ break;
+
+ case Qstatus:
+ d = usbdevice(c);
+ s = smalloc(READSTR);
+ if(waserror()){
+ free(s);
+ nexterror();
+ }
+ l = snprint(s, READSTR, "%s %#6.6lux\n", devstates[d->state], d->csp);
+ for(i=0; i<nelem(d->ep); i++)
+ if((e = d->ep[i]) != nil) /* TO DO: freeze e */
+ l += snprint(s+l, READSTR-l, "%2d %#6.6lux %10lud bytes %10lud blocks\n", i, e->csp, e->nbytes, e->nblocks);
+ n = readstr(offset, a, n, s);
+ poperror();
+ free(s);
+ break;
+
+ default:
+ d = usbdevice(c);
+ if((t -= Qep0) < 0 || t >= nelem(d->ep))
+ error(Eio);
+ if((e = d->ep[t]) == nil || e->mode == OWRITE)
+ error(Eio); /* can't happen */
+ n=readusb(e, a, n);
+ break;
+ }
+ return n;
+}
+
+static int
+qisempty(void *arg)
+{
+ return ((QH*)arg)->entries & Terminate;
+}
+
+static long
+writeusb(Endpt *e, void *a, long n, int tok)
+{
+ long i;
+ Block *b;
+ uchar *p;
+ QH *qh;
+
+ p = a;
+ qlock(&e->wlock);
+ if(waserror()){
+ qunlock(&e->wlock);
+ eptcancel(e);
+ nexterror();
+ }
+ do {
+ int j;
+
+ if(e->err)
+ error(e->err);
+ if((i = n) >= e->maxpkt)
+ i = e->maxpkt;
+ b = allocb(i);
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ XPRINT("out [%ld]", i);
+ for (j = 0; j < i; j++) XPRINT(" %.2x", p[j]);
+ XPRINT("\n");
+ memmove(b->wp, p, i);
+ b->wp += i;
+ p += i;
+ n -= i;
+ poperror();
+ qh = qxmit(e, b, tok);
+ tok = TokOUT;
+ if(!e->iso)
+ e->data01 ^= 1;
+ if(e->ntd >= e->nbuf) {
+ XPRINT("writeusb: sleep %lux\n", &e->wr);
+ sleep(&e->wr, qisempty, qh);
+ XPRINT("writeusb: awake\n");
+ }
+ } while(n > 0);
+ poperror();
+ qunlock(&e->wlock);
+ return p-(uchar*)a;
+}
+
+long
+usbwrite(Chan *c, void *a, long n, vlong)
+{
+ Udev *d;
+ Endpt *e;
+ int id, nw, nf, t, i;
+ char cmd[50], *fields[10];
+
+ if(c->qid.type == QTDIR)
+ error(Egreg);
+ t = QID(c->qid);
+ if(t == Qbusctl){
+ if(n >= sizeof(cmd)-1)
+ n = sizeof(cmd)-1;
+ memmove(cmd, a, n);
+ cmd[n] = 0;
+ nf = getfields(cmd, fields, nelem(fields), 0, " \t\n");
+ if(nf==1 && strcmp(fields[0], "dump")==0){
+ dumpframe(-1, -1);
+ return n;
+ }
+ if(nf < 2)
+ error(Ebadarg);
+ id = strtol(fields[1], nil, 0);
+ if(id != 1 && id != 2)
+ error(Ebadarg); /* there are two ports on the root hub */
+ if(strcmp(fields[0], "reset") == 0)
+ portreset(id);
+ else if(strcmp(fields[0], "enable") == 0)
+ portenable(id, 1);
+ else if(strcmp(fields[0], "disable") == 0)
+ portenable(id, 0);
+ else
+ error(Ebadarg);
+ return n;
+ }
+ d = usbdevice(c);
+ t = QID(c->qid);
+ switch(t){
+ case Qctl:
+ if(n >= sizeof(cmd)-1)
+ n = sizeof(cmd)-1;
+ memmove(cmd, a, n);
+ cmd[n] = 0;
+ nf = getfields(cmd, fields, nelem(fields), 0, " \t\n");
+ if(nf > 1 && strcmp(fields[0], "speed") == 0){
+ d->ls = strtoul(fields[1], nil, 0) == 0;
+ } else if(nf > 3 && strcmp(fields[0], "class") == 0){
+ i = strtoul(fields[2], nil, 0);
+ d->npt = strtoul(fields[1], nil, 0);
+ /* class config# csp ( == class subclass proto) */
+ if (i < 0 || i >= nelem(d->ep)
+ || d->npt > nelem(d->ep) || i >= d->npt)
+ error(Ebadarg);
+ if (i == 0) {
+ d->csp = strtoul(fields[3], nil, 0);
+ }
+ if(d->ep[i] == nil)
+ d->ep[i] = devendpt(d, i, 1);
+ d->ep[i]->csp = strtoul(fields[3], nil, 0);
+ }else if(nf > 2 && strcmp(fields[0], "data") == 0){
+ i = strtoul(fields[1], nil, 0);
+ if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
+ error(Ebadarg);
+ e = d->ep[i];
+ e->data01 = strtoul(fields[2], nil, 0) != 0;
+ }else if(nf > 2 && strcmp(fields[0], "maxpkt") == 0){
+ i = strtoul(fields[1], nil, 0);
+ if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
+ error(Ebadarg);
+ e = d->ep[i];
+ e->maxpkt = strtoul(fields[2], nil, 0);
+ if(e->maxpkt > 1500)
+ e->maxpkt = 1500;
+ }else if(nf > 2 && strcmp(fields[0], "debug") == 0){
+ i = strtoul(fields[1], nil, 0);
+ if(i < -1 || i >= nelem(d->ep) || d->ep[i] == nil)
+ error(Ebadarg);
+ if (i == -1)
+ debug = 0;
+ else {
+ debug = 1;
+ e = d->ep[i];
+ e->debug = strtoul(fields[2], nil, 0);
+ }
+ }else if(nf > 1 && strcmp(fields[0], "unstall") == 0){
+ i = strtoul(fields[1], nil, 0);
+ if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
+ error(Ebadarg);
+ e = d->ep[i];
+ e->err = nil;
+ }else if(nf == 6 && strcmp(fields[0], "ep") == 0){
+ /* ep n maxpkt mode poll nbuf */
+ i = strtoul(fields[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)
+ e = devendpt(d, i, 1);
+ if(waserror()){
+ freept(e);
+ nexterror();
+ }
+ i = strtoul(fields[2], nil, 0);
+ if(i < 8 || i > 1023)
+ i = 8;
+ e->maxpkt = i;
+ e->mode = strcmp(fields[3],"r") == 0? OREAD :
+ strcmp(fields[3],"w") == 0? OWRITE : ORDWR;
+ e->periodic = 0;
+ e->sched = -1;
+ if(strcmp(fields[4], "bulk") == 0){
+ Ctlr *ub;
+
+ ub = &ubus;
+ /* Each bulk device gets a queue head hanging off the
+ * bulk queue head
+ */
+ if (e->epq == nil) {
+ e->epq = allocqh(ub);
+ if(e->epq == nil)
+ panic("usbwrite: allocqh");
+ }
+ queueqh(e->epq);
+ } else {
+ e->periodic = 1;
+ i = strtoul(fields[4], nil, 0);
+ if(i > 0 && i <= 1000)
+ e->pollms = i;
+ else {
+ XPRINT("field 4: 0 <= %d <= 1000\n", i);
+ error(Ebadarg);
+ }
+ }
+ i = strtoul(fields[5], nil, 0);
+ if(i >= 1 && i <= 32)
+ e->nbuf = i;
+ poperror();
+ }else {
+ XPRINT("command %s, fields %d\n", fields[0], nf);
+ error(Ebadarg);
+ }
+ return n;
+
+ case Qsetup: /* SETUP endpoint 0 */
+ /* should canqlock etc */
+ if((e = d->ep[0]) == nil)
+ error(Eio); /* can't happen */
+ if(n < 8 || n > 1023)
+ error(Eio);
+ nw = *(uchar*)a & RD2H;
+ e->data01 = 0;
+ n = writeusb(e, a, n, TokSETUP);
+ if(nw == 0){ /* host to device: use IN[DATA1] to ack */
+ e->data01 = 1;
+ nw = readusb(e, cmd, 8);
+ if(nw != 0)
+ error(Eio); /* could provide more status */
+ }else
+ e->setin = 1; /* two-phase */
+ break;
+
+ default: /* sends DATA[01] */
+ if((t -= Qep0) < 0 || t >= nelem(d->ep)) {
+ print("t = %d\n", t);
+ error(Eio);
+ }
+ if((e = d->ep[t]) == nil || e->mode == OREAD) {
+ error(Eio); /* can't happen */
+ }
+ n = writeusb(e, a, n, TokOUT);
+ break;
+ }
+ return n;
+}
+
+Dev usbdevtab = {
+ 'U',
+ "usb",
+
+ usbreset,
+ usbinit,
+ usbattach,
+ usbwalk,
+ usbstat,
+ usbopen,
+ devcreate,
+ usbclose,
+ usbread,
+ devbread,
+ usbwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};