Add a new "reset" control request. Used by drivers to ask for a usb port reset to recover from errors. This also requires a change to usbd, included. Remove the temporary placeholder for the global usb reset request (I'm confident now that it should not be added). Also, change a commented out print to become a dprint. Notes: Thu Dec 10 17:45:45 EST 2009 geoff we've apparently already picked up this change in newer usb code. Reference: /n/sources/patch/sorry/usbportreset Date: Tue Sep 8 16:20:31 CES 2009 Signed-off-by: nemo@lsub.org Reviewed-by: geoff --- /sys/src/9/pc/usbehci.c Tue Sep 8 16:16:52 2009 +++ /sys/src/9/pc/usbehci.c Tue Sep 8 16:16:45 2009 @@ -2478,10 +2478,11 @@ } /* set the address if unset and out of configuration state */ - if(ep->dev->state != Dconfig && cio->usbid == 0){ - cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax); - qhsetaddr(cio->qh, cio->usbid); - } + if(ep->dev->state != Dconfig && ep->dev->state != Dreset) + if(cio->usbid == 0){ + cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax); + qhsetaddr(cio->qh, cio->usbid); + } /* adjust maxpkt if the user has learned a different one */ if(qhmaxpkt(cio->qh) != ep->maxpkt) qhsetmaxpkt(cio->qh, ep->maxpkt); --- /sys/src/9/pc/usbohci.c Tue Sep 8 16:17:01 2009 +++ /sys/src/9/pc/usbohci.c Tue Sep 8 16:16:55 2009 @@ -1684,10 +1684,11 @@ } /* set the address if unset and out of configuration state */ - if(ep->dev->state != Dconfig && cio->usbid == 0){ - cio->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax); - edsetaddr(cio->ed, cio->usbid); - } + if(ep->dev->state != Dconfig && ep->dev->state != Dreset) + if(cio->usbid == 0){ + cio->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax); + edsetaddr(cio->ed, cio->usbid); + } /* adjust maxpkt if the user has learned a different one */ if(edmaxpkt(cio->ed) != ep->maxpkt) edsetmaxpkt(cio->ed, ep->maxpkt); --- /sys/src/9/pc/usbuhci.c Tue Sep 8 16:17:10 2009 +++ /sys/src/9/pc/usbuhci.c Tue Sep 8 16:17:04 2009 @@ -964,7 +964,6 @@ else if(qh->state == Qclose) qhlinktd(qh, nil); iunlock(ctlr); - } /* @@ -1502,8 +1501,9 @@ } /* set the address if unset and out of configuration state */ - if(ep->dev->state != Dconfig && cio->usbid == 0) - cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax); + if(ep->dev->state != Dconfig && ep->dev->state != Dreset) + if(cio->usbid == 0) + cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax); c = a; cio->tok = Tdtoksetup; cio->toggle = Tddata0; @@ -1869,7 +1869,6 @@ } free(iso->data); iso->data = nil; - } static void --- /sys/src/9/pc/devusb.c Tue Sep 8 16:17:16 2009 +++ /sys/src/9/pc/devusb.c Tue Sep 8 16:17:12 2009 @@ -71,7 +71,6 @@ /* Usb ctls. */ CMdebug = 0, /* debug on|off */ CMdump, /* dump (data structures for debug) */ - CMreset, /* reset the bus; start over */ /* Ep. ctls */ CMnew = 0, /* new nb ctl|bulk|intr|iso r|w|rw (endpoint) */ @@ -90,6 +89,7 @@ CMdebugep, /* debug n (set/clear debug for this ep) */ CMname, /* name str (show up as #u/name as well) */ CMtmout, /* timeout n (activate timeouts for ep) */ + CMpreset, /* reset the port */ /* Hub feature selectors */ Rportenable = 1, @@ -113,7 +113,6 @@ { {CMdebug, "debug", 2}, {CMdump, "dump", 1}, - {CMreset, "reset", 1}, }; static Cmdtab epctls[] = @@ -134,6 +133,7 @@ {CMclrhalt, "clrhalt", 1}, {CMname, "name", 2}, {CMtmout, "timeout", 2}, + {CMpreset, "reset", 1}, }; static Dirtab usbdir[] = @@ -269,7 +269,7 @@ static char* seprintep(char *s, char *se, Ep *ep, int all) { - static char* dsnames[] = { "config", "enabled", "detached" }; + static char* dsnames[] = { "config", "enabled", "detached", "reset" }; Udev *d; int i; int di; @@ -657,7 +657,6 @@ Fail: if(0)ddprint("fail\n"); return -1; - } static Hci* @@ -709,8 +708,8 @@ * modern machines have too many usb controllers to list on * the console. */ -// print("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n", -// epnb, hcitypes[cardno].type, hp->port, hp->irq); + dprint("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n", + epnb, hcitypes[cardno].type, hp->port, hp->irq); epnb++; return hp; } @@ -1142,7 +1141,7 @@ if(ct == nil) error(Ebadctl); i = ct->index; - if(i == CMnew || i == CMspeed || i == CMhub) + if(i == CMnew || i == CMspeed || i == CMhub || i == CMpreset) if(ep != ep->ep0) error("allowed only on a setup endpoint"); if(i != CMclrhalt && i != CMdetach && i != CMdebugep && i != CMname) @@ -1317,6 +1316,14 @@ if(ep->tmout != 0 && ep->tmout < Xfertmout) ep->tmout = Xfertmout; break; + case CMpreset: + deprint("usb epctl %s\n", cb->f[0]); + if(ep->ttype != Tctl) + error("not a control endpoint"); + if(ep->dev->state != Denabled) + error("forbidden on devices not enabled"); + ep->dev->state = Dreset; + break; default: panic("usb: unknown epctl %d", ct->index); } @@ -1354,18 +1361,6 @@ ep->hp->debug(ep->hp, debug); putep(ep); } - break; - case CMreset: - print("devusb: CMreset not implemented\n"); - error("not implemented"); -#ifdef TODO - XXX: I'm not sure this is a good idea. - Usbd should not be restarted at all. - for(all eps) - closeep(ep); - do a global reset once more - recreate root hub devices in place. -#endif break; case CMdump: dumpeps(); --- /sys/src/9/pc/usb.h Tue Sep 8 16:17:20 2009 +++ /sys/src/9/pc/usb.h Tue Sep 8 16:17:18 2009 @@ -62,6 +62,7 @@ Dconfig = 0, /* configuration in progress */ Denabled, /* address assigned */ Ddetach, /* device is detached */ + Dreset, /* its port is being reset */ /* (root) Hub reply to port status (reported to usbd) */ HPpresent = 0x1, --- /sys/man/3/usb Tue Sep 8 16:17:25 2009 +++ /sys/man/3/usb Tue Sep 8 16:17:23 2009 @@ -186,7 +186,6 @@ timeout performed by the kernel and it is not necessary for applications to place their own timers. For other transfer types, the kernel will not time out any operation -by default (but see the .L timeout control request). @@ -222,8 +221,9 @@ One of .BR config , .BR enabled , +.BR detached , and -.BR detached . +.BR reset . An endpoint starts in the .B config state, and accepts control commands written to its @@ -242,6 +242,9 @@ detachment from the bus, the endpoint enters the .B detached state and no further I/O is accepted on it. +The +.B reset +state reflects that a port reset is being performed to recover from errors. Files for an endpoint (including its directory) vanish when the device is detached and its files are no longer open. Root hubs may not be detached. @@ -482,6 +485,13 @@ .TP .B hub Tell this driver that the endpoint corresponds to a hub device. +.TP +.B reset +Ask for a port reset on the device, to recover from errors. +The caller +must close the data file before issuing the request. +The request is finished when the caller can open the +data file again (and the device state changes). .PD .PP Setup endpoints for hub devices also accept his request: --- /sys/src/cmd/usb/usbd/usbd.c Tue Sep 8 16:17:31 2009 +++ /sys/src/cmd/usb/usbd/usbd.c Tue Sep 8 16:17:28 2009 @@ -465,6 +465,96 @@ } } +/* + * The next two functions are included to + * perform a port reset asked for by someone (usually a driver). + * This must be done while no other device is in using the + * configuration address and with care to keep the old address. + * To keep drivers decoupled from usbd they write the reset request + * to the #u/usb/epN.0/ctl file and then exit. + * This is unfortunate because usbd must now poll twice as much. + * + * An alternative to this reset process would be for the driver to detach + * the device. The next function could see that, issue a port reset, and + * then restart the driver once to see if it's a temporary error. + * + * The real fix would be to use interrupt endpoints for non-root hubs + * (would probably make some hubs fail) and add an events file to + * the kernel to report events to usbd. This is a severe change not + * yet implemented. + */ +static int +portresetwanted(Hub *h, int p) +{ + Port *pp; + Dev *nd; + char buf[5]; + + pp = &h->port[p]; + nd = pp->dev; + if(nd != nil && nd->cfd >= 0 && pread(nd->cfd, buf, 5, 0LL) == 5) + return strncmp(buf, "reset", 5) == 0; + else + return 0; +} + +static void +portreset(Hub *h, int p) +{ + Dev *d; + Dev *nd; + Port *pp; + int sts; + + d = h->dev; + pp = &h->port[p]; + nd = pp->dev; + dprint(2, "%s: %s: port %d: resetting\n", argv0, d->dir, p); + if(hubfeature(h, p, Fportreset, 1) < 0){ + dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p); + goto Fail; + } + sleep(Resetdelay); + sts = portstatus(h, p); + if(sts < 0) + goto Fail; + if((sts & PSenable) == 0){ + dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p); + hubfeature(h, p, Fportenable, 1); + sts = portstatus(h, p); + if((sts & PSenable) == 0) + goto Fail; + } + nd = pp->dev; + opendevdata(nd, ORDWR); + if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){ + dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p); + goto Fail; + } + if(devctl(nd, "address") < 0){ + dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p); + goto Fail; + } + if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){ + dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p); + unstall(nd, nd, Eout); + if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0) + goto Fail; + } + if(nd->dfd >= 0) + close(nd->dfd); + return; +Fail: + pp->state = Pdisabled; + pp->sts = 0; + if(pp->hub != nil) + pp->hub = nil; /* hub closed by enumhub */ + hubfeature(h, p, Fportenable, 0); + if(nd != nil) + devctl(nd, "detach"); + closedev(nd); +} + static int portgone(Port *pp, int sts) { @@ -513,6 +603,8 @@ portdetach(h, p); }else if(portgone(pp, sts)) portdetach(h, p); + else if(portresetwanted(h, p)) + portreset(h, p); else if(pp->sts != sts){ dprint(2, "%s: %s port %d: sts %s %#x ->", argv0, d->dir, p, stsstr(pp->sts), pp->sts); @@ -617,7 +709,6 @@ if(strstr(dt->name, name) != nil) dt->args = estrdup(args); } - static long cfswrite(Usbfs*, Fid *, void *data, long cnt, vlong )