devusb: handle root-port reset delays outside of hci driver, rootport power control

We want to avoid long delays with interrupts disabled,
so handle the delay from devusb/usbd.

Do not expect SetFeatrue/PortEnable request, this is
invalid by the usb standard. But some HCI's require
setting enable bit in port status/ctrl register of
rootports, so handle this internally.

xhci, dwc and ohci have a power-power control bit,
so implement standard Set/ClearFeature/PortPower in
roothub, which allows us to control port on some
rootports.
This commit is contained in:
cinap_lenrek 2024-12-06 16:07:44 +00:00
parent ff65b74935
commit 2f12ad38ca
7 changed files with 227 additions and 259 deletions

View file

@ -27,9 +27,6 @@
enum
{
USBREGS = VIRTIO + 0x980000,
Enabledelay = 50,
Resetdelay = 10,
ResetdelayHS = 50,
Read = 0,
Write = 1,
@ -936,61 +933,52 @@ seprintep(char *s, char*, Ep*)
{
return s;
}
enum {
RW1C = Prtconndet | Prtena | Prtenchng | Prtovrcurrchng,
};
static int
portenable(Hci *hp, int port, int on)
static void
portenable(Hci *hp, int, int on)
{
Ctlr *ctlr;
Dwcregs *r;
Ctlr *ctlr = hp->aux;
Dwcregs *r = ctlr->regs;
assert(port == 1);
ctlr = hp->aux;
r = ctlr->regs;
dprint("usbdwc enable=%d; sts %#x\n", on, r->hport0);
if(!on)
r->hport0 = Prtpwr | Prtena;
tsleep(&up->sleep, return0, 0, Enabledelay);
dprint("usbdwc enable=%d; sts %#x\n", on, r->hport0);
return 0;
r->hport0 = (r->hport0 & ~RW1C) | Prtena;
}
static void
portreset(Hci *hp, int, int on)
{
Ctlr *ctlr = hp->aux;
Dwcregs *r = ctlr->regs;
if(on)
r->hport0 = (r->hport0 & ~RW1C) | Prtrst;
else
r->hport0 = (r->hport0 & ~RW1C) & ~Prtrst;
}
static void
portpower(Hci *hp, int, int on)
{
Ctlr *ctlr = hp->aux;
Dwcregs *r = ctlr->regs;
if(on)
r->hport0 = (r->hport0 & ~RW1C) | Prtpwr;
else
r->hport0 = (r->hport0 & ~RW1C) & ~Prtpwr;
}
static int
portreset(Hci *hp, int port, int on)
portstatus(Hci *hp, int)
{
Ctlr *ctlr;
Dwcregs *r;
Ctlr *ctlr = hp->aux;
Dwcregs *r = ctlr->regs;
int b, s;
assert(port == 1);
ctlr = hp->aux;
r = ctlr->regs;
dprint("usbdwc reset=%d; sts %#x\n", on, r->hport0);
if(!on)
return 0;
r->hport0 = Prtpwr | Prtrst;
tsleep(&up->sleep, return0, 0, ResetdelayHS);
r->hport0 = Prtpwr;
tsleep(&up->sleep, return0, 0, Enabledelay);
s = r->hport0;
b = s & (Prtconndet|Prtenchng|Prtovrcurrchng);
if(b != 0)
r->hport0 = Prtpwr | b;
dprint("usbdwc reset=%d; sts %#x\n", on, s);
if((s & Prtena) == 0)
print("usbdwc: host port not enabled after reset");
return 0;
}
static int
portstatus(Hci *hp, int port)
{
Ctlr *ctlr;
Dwcregs *r;
int b, s;
assert(port == 1);
ctlr = hp->aux;
r = ctlr->regs;
s = r->hport0;
b = s & (Prtconndet|Prtenchng|Prtovrcurrchng);
if(b != 0)
@ -1067,6 +1055,7 @@ reset(Hci *hp)
hp->seprintep = seprintep;
hp->portenable = portenable;
hp->portreset = portreset;
hp->portpower = portpower;
hp->portstatus = portstatus;
hp->shutdown = shutdown;
hp->debug = setdebug;

View file

@ -43,7 +43,6 @@ enum
Abortdelay = 1, /* delay after cancelling Tds (ms) */
Tdatomic = 8, /* max nb. of Tds per bulk I/O op. */
Enabledelay = 100, /* waiting for a port to enable */
/* Queue states (software) */
@ -339,7 +338,6 @@ struct Edpool
struct Ctlr
{
Lock; /* for ilock; lists and basic ctlr I/O */
QLock resetl; /* lock controller during USB reset */
int active;
Ctlr* next;
int nports;
@ -950,7 +948,6 @@ seprintep(char* s, char* e, Ep *ep)
case Tctl:
cio = ep->aux;
s = seprintio(s, e, cio, "c");
s = seprint(s, e, "\trepl %llux ndata %d\n", ep->rhrepl, cio->ndata);
break;
case Tbulk:
case Tintr:
@ -2211,63 +2208,46 @@ epclose(Ep *ep)
ep->aux = nil;
}
static int
static void
portreset(Hci *hp, int port, int on)
{
Ctlr *ctlr;
Ohci *ohci;
if(on == 0)
return 0;
ctlr = hp->aux;
eqlock(&ctlr->resetl);
if(waserror()){
qunlock(&ctlr->resetl);
nexterror();
}
ilock(ctlr);
ohci = ctlr->ohci;
ohci->rhportsts[port - 1] = Spp;
if((ohci->rhportsts[port - 1] & Ccs) == 0){
iunlock(ctlr);
error("port not connected");
}
ohci->rhportsts[port - 1] = Spr;
while((ohci->rhportsts[port - 1] & Prsc) == 0){
iunlock(ctlr);
dprint("ohci: portreset, wait for reset complete\n");
ilock(ctlr);
}
ohci->rhportsts[port - 1] = Prsc;
if(on)
ctlr->ohci->rhportsts[port - 1] = Spr;
else if(ctlr->ohci->rhportsts[port - 1] & Prsc)
ctlr->ohci->rhportsts[port - 1] = Prsc;
iunlock(ctlr);
poperror();
qunlock(&ctlr->resetl);
return 0;
}
static int
static void
portpower(Hci *hp, int port, int on)
{
Ctlr *ctlr;
ctlr = hp->aux;
ilock(ctlr);
if(on)
ctlr->ohci->rhportsts[port - 1] = Spp;
else
ctlr->ohci->rhportsts[port - 1] = Cpp;
iunlock(ctlr);
}
static void
portenable(Hci *hp, int port, int on)
{
Ctlr *ctlr;
ctlr = hp->aux;
dprint("ohci: %#p port %d enable=%d\n", ctlr->ohci, port, on);
eqlock(&ctlr->resetl);
if(waserror()){
qunlock(&ctlr->resetl);
nexterror();
}
ilock(ctlr);
if(on)
ctlr->ohci->rhportsts[port - 1] = Spe | Spp;
ctlr->ohci->rhportsts[port - 1] = Spe;
else
ctlr->ohci->rhportsts[port - 1] = Cpe;
iunlock(ctlr);
tsleep(&up->sleep, return0, 0, Enabledelay);
poperror();
qunlock(&ctlr->resetl);
return 0;
}
static int
@ -2626,6 +2606,7 @@ reset(Hci *hp)
hp->seprintep = seprintep;
hp->portenable = portenable;
hp->portreset = portreset;
hp->portpower = portpower;
hp->portstatus = portstatus;
hp->shutdown = shutdown;
hp->debug = usbdebug;

View file

@ -30,7 +30,6 @@ typedef struct Tdpool Tdpool;
enum
{
Resetdelay = 100, /* delay after a controller reset (ms) */
Enabledelay = 100, /* waiting for a port to enable */
Abortdelay = 10, /* delay after cancelling Tds (ms) */
Incr = 64, /* for Td and Qh pools */
@ -2023,79 +2022,52 @@ seprintep(char *s, char *e, Ep *ep)
return s;
}
static int
static void
portenable(Hci *hp, int port, int on)
{
int s;
int ioport;
Ctlr *ctlr;
int ioport, s;
ctlr = hp->aux;
dprint("uhci: %#x port %d enable=%d\n", ctlr->port, port, on);
ioport = PORT(port-1);
eqlock(&ctlr->portlck);
if(waserror()){
qunlock(&ctlr->portlck);
nexterror();
}
ilock(ctlr);
s = INS(ioport);
if(on)
OUTS(ioport, s | PSenable);
else
OUTS(ioport, s & ~PSenable);
microdelay(64);
iunlock(ctlr);
tsleep(&up->sleep, return0, 0, Enabledelay);
dprint("uhci %#ux port %d enable=%d: sts %#x\n",
ctlr->port, port, on, INS(ioport));
qunlock(&ctlr->portlck);
poperror();
return 0;
}
static int
static void
portreset(Hci *hp, int port, int on)
{
int i, p;
Ctlr *ctlr;
int ioport;
if(on == 0)
return 0;
ctlr = hp->aux;
dprint("uhci: %#ux port %d reset\n", ctlr->port, port);
p = PORT(port-1);
ioport = PORT(port-1);
eqlock(&ctlr->portlck);
ilock(ctlr);
OUTS(p, PSreset);
delay(50);
OUTS(p, INS(p) & ~PSreset);
OUTS(p, INS(p) | PSenable);
microdelay(64);
for(i=0; i<1000 && (INS(p) & PSenable) == 0; i++)
;
OUTS(p, (INS(p) & ~PSreset)|PSenable);
if(on)
OUTS(ioport, PSreset);
else
OUTS(ioport, INS(ioport) & ~PSreset);
iunlock(ctlr);
dprint("uhci %#ux after port %d reset: sts %#x\n",
ctlr->port, port, INS(p));
return 0;
qunlock(&ctlr->portlck);
}
static int
portstatus(Hci *hp, int port)
{
int s;
int r;
int ioport;
Ctlr *ctlr;
int ioport, s, r;
ctlr = hp->aux;
ioport = PORT(port-1);
eqlock(&ctlr->portlck);
if(waserror()){
iunlock(ctlr);
qunlock(&ctlr->portlck);
nexterror();
}
ilock(ctlr);
s = INS(ioport);
if(s & (PSstatuschg | PSchange)){
@ -2104,7 +2076,6 @@ portstatus(Hci *hp, int port)
}
iunlock(ctlr);
qunlock(&ctlr->portlck);
poperror();
/*
* We must return status bits as a

View file

@ -93,6 +93,8 @@ enum
/* Hub feature selectors */
Rportenable = 1,
Rportreset = 4,
Rportpower = 8,
Rbhportreset = 28,
};
@ -505,6 +507,9 @@ newdev(Hci *hp, Ep *hub, int port, int speed)
d->ttport = d->ttt = d->mtt = 0;
d->tthub = nil;
d->rhrepl = -1;
d->rhresetport = 0;
if(hub != nil){
d->hub = hub->dev;
d->depth = d->hub->depth+1;
@ -522,7 +527,7 @@ newdev(Hci *hp, Ep *hub, int port, int speed)
}
}
d->rootport = rootport(hub, port);
if(d->rootport == 0)
if(d->rootport <= 0)
error(Ebadport);
} else {
d->hub = nil;
@ -1014,7 +1019,8 @@ usbopen(Chan *c, int omode)
if(ep->dev->state == Ddetach)
error(Edetach);
ep->clrhalt = 0;
ep->rhrepl = -1;
if(ep->ttype == Tctl)
ep->dev->rhrepl = -1;
if(ep->ttype == Tiso)
isotiming(ep);
if(ep->load == 0 && ep->dev->speed != Superspeed)
@ -1127,20 +1133,23 @@ static long
rhubread(Ep *ep, void *a, long n)
{
uchar b[8];
Udev *dev;
if(ep->dev->depth >= 0 || ep->nb != 0 || n < 2 || ep->rhrepl == -1)
dev = ep->dev;
if(dev->depth >= 0 || ep->nb != 0 || n < 2 || dev->rhrepl == -1)
return -1;
b[0] = ep->rhrepl;
b[1] = ep->rhrepl>>8;
b[2] = ep->rhrepl>>16;
b[3] = ep->rhrepl>>24;
b[4] = ep->rhrepl>>32;
b[5] = ep->rhrepl>>40;
b[6] = ep->rhrepl>>48;
b[7] = ep->rhrepl>>56;
b[0] = dev->rhrepl;
b[1] = dev->rhrepl>>8;
b[2] = dev->rhrepl>>16;
b[3] = dev->rhrepl>>24;
b[4] = dev->rhrepl>>32;
b[5] = dev->rhrepl>>40;
b[6] = dev->rhrepl>>48;
b[7] = dev->rhrepl>>56;
ep->rhrepl = -1;
dev->rhrepl = -1;
if(n > sizeof(b))
n = sizeof(b);
@ -1157,33 +1166,70 @@ rhubwrite(Ep *ep, void *a, long n)
int feature;
int port;
Hci *hp;
Udev *dev;
if(ep->dev->depth >= 0 || ep->nb != 0)
hp = ep->hp;
dev = ep->dev;
if(dev->depth >= 0 || ep->nb != 0)
return -1;
if(n != Rsetuplen)
error("root hub is a toy hub");
ep->rhrepl = -1;
s = a;
if(s[Rtype] != (Rh2d|Rclass|Rother) && s[Rtype] != (Rd2h|Rclass|Rother))
error("root hub is a toy hub");
hp = ep->hp;
/* terminate previous port reset */
port = dev->rhresetport;
if(port > 0){
dev->rhresetport = 0;
/* some controllers have to clear reset and set enable manually */
if(hp->portreset != nil){
(*hp->portreset)(hp, port, 0);
tsleep(&up->sleep, return0, nil, 50);
}
if(hp->portenable != nil){
(*hp->portenable)(hp, port, 1);
tsleep(&up->sleep, return0, nil, 50);
}
}
cmd = s[Rreq];
feature = GET2(s+Rvalue);
port = rootport(ep, GET2(s+Rindex));
if(port == 0)
if(port <= 0)
error(Ebadport);
dev->rhrepl = 0;
switch(feature){
case Rportpower:
if(hp->portpower == nil)
break;
(*hp->portpower)(hp, port, cmd == Rsetfeature);
break;
case Rportenable:
ep->rhrepl = (*hp->portenable)(hp, port, cmd == Rsetfeature);
if(cmd != Rclearfeature || hp->portenable == nil)
break;
(*hp->portenable)(hp, port, 0);
break;
case Rbhportreset:
if(cmd != Rsetfeature || hp->bhportreset == nil)
break;
(*hp->bhportreset)(hp, port, 1);
break;
case Rportreset:
ep->rhrepl = (*hp->portreset)(hp, port, cmd == Rsetfeature);
if(cmd != Rsetfeature || hp->portreset == nil)
break;
(*hp->portreset)(hp, port, 1);
/* port reset in progress */
dev->rhresetport = port;
break;
case Rgetstatus:
ep->rhrepl = (*hp->portstatus)(hp, port);
if(hp->portstatus == nil)
break;
dev->rhrepl = (*hp->portstatus)(hp, port);
break;
default:
ep->rhrepl = 0;
}
return n;
}

View file

@ -117,9 +117,11 @@ struct Hciimpl
long (*epwrite)(Ep*,void*,long); /* receive data for ep */
char* (*seprintep)(char*,char*,Ep*); /* debug */
void (*devclose)(Udev*); /* release the device */
int (*portenable)(Hci*, int, int); /* enable/disable port */
int (*portreset)(Hci*, int, int); /* set/clear port reset */
int (*portstatus)(Hci*, int); /* get port status */
void (*portenable)(Hci*, int, int); /* enable/disable root port */
void (*portreset)(Hci*, int, int); /* set/clear root port reset */
void (*bhportreset)(Hci*, int, int); /* set/clear warm root port reset (usb3) */
void (*portpower)(Hci*, int, int); /* set/clear root port power state */
int (*portstatus)(Hci*, int); /* get root port status */
void (*shutdown)(Hci*); /* shutdown for reboot */
void (*debug)(Hci*, int); /* set/clear debug flag */
};
@ -167,7 +169,6 @@ struct Ep
int ttype; /* tranfer type */
ulong load; /* in µs, for a transfer of maxpkt bytes */
void* aux; /* for controller specific info */
u64int rhrepl; /* fake root hub replies */
int toggle[2]; /* saved toggles (while ep is not in use) */
long pollival; /* poll interval ([µ]frames; intr/iso) */
long hz; /* poll frequency (iso) */
@ -204,6 +205,9 @@ struct Udev
void *aux;
Ep *eps[Ndeveps]; /* end points for this device (cached) */
u64int rhrepl; /* fake root hub replies */
int rhresetport; /* root port being reset */
};
void addhcitype(char *type, int (*reset)(Hci*));

View file

@ -51,7 +51,6 @@ enum
Qclose,
Qfree,
Enabledelay = 100, /* waiting for a port to enable */
Abortdelay = 10, /* delay after cancelling Tds (ms) */
Incr = 64, /* for pools of Tds, Qhs, etc. */
@ -1628,41 +1627,6 @@ interrupt(Ureg*, void* a)
ehciintr(a);
}
static int
portenable(Hci *hp, int port, int on)
{
Ctlr *ctlr;
Eopio *opio;
int s;
ctlr = hp->aux;
opio = ctlr->opio;
s = opio->portsc[port-1];
eqlock(&ctlr->portlck);
if(waserror()){
qunlock(&ctlr->portlck);
nexterror();
}
dprint("ehci %#p port %d enable=%d; sts %#x\n",
ctlr->capio, port, on, s);
ilock(ctlr);
if(s & (Psstatuschg | Pschange))
opio->portsc[port-1] = s;
if(on)
opio->portsc[port-1] |= Psenable;
else
opio->portsc[port-1] &= ~Psenable;
coherence();
microdelay(64);
iunlock(ctlr);
tsleep(&up->sleep, return0, 0, Enabledelay);
dprint("ehci %#p port %d enable=%d: sts %#lux\n",
ctlr->capio, port, on, opio->portsc[port-1]);
qunlock(&ctlr->portlck);
poperror();
return 0;
}
/*
* If we detect during status that the port is low-speed or
* during reset that it's full-speed, the device is not for
@ -1678,67 +1642,53 @@ portlend(Ctlr *ctlr, int port, char *ss)
ulong s;
opio = ctlr->opio;
dprint("ehci %#p port %d: %s speed device: no longer owned\n",
ctlr->capio, port, ss);
s = opio->portsc[port-1] & ~(Pschange|Psstatuschg);
opio->portsc[port-1] = s | Psowner;
coherence();
}
static int
portreset(Hci *hp, int port, int on)
static void
portenable(Hci *hp, int port, int on)
{
ulong *portscp;
Eopio *opio;
Ctlr *ctlr;
int i;
if(on == 0)
return 0;
Eopio *opio;
int s;
ctlr = hp->aux;
opio = ctlr->opio;
eqlock(&ctlr->portlck);
if(waserror()){
iunlock(ctlr);
qunlock(&ctlr->portlck);
nexterror();
}
portscp = &opio->portsc[port-1];
dprint("ehci %#p port %d reset; sts %#lux\n", ctlr->capio, port, *portscp);
ilock(ctlr);
/* Shalted must be zero, else Psreset will stay set */
if (opio->sts & Shalted)
iprint("ehci %#p: halted yet trying to reset port\n",
ctlr->capio);
*portscp = (*portscp & ~Psenable) | Psreset; /* initiate reset */
/*
* usb 2 spec: reset must finish within 20 ms.
* linux says spec says it can take 50 ms. for hubs.
*/
delay(50);
*portscp &= ~Psreset; /* terminate reset */
delay(10);
for(i = 0; *portscp & Psreset && i < 10; i++)
delay(10);
if (*portscp & Psreset)
iprint("ehci %#p: port %d didn't reset; sts %#lux\n",
ctlr->capio, port, *portscp);
delay(10); /* ehci spec: enable within 2 ms. */
if((*portscp & Psenable) == 0)
portlend(ctlr, port, "full");
s = opio->portsc[port-1];
if(s & (Psstatuschg | Pschange))
opio->portsc[port-1] = s;
if(on){
/* not enabled after reset? */
if((s & Psenable) == 0)
portlend(ctlr, port, "full");
} else {
opio->portsc[port-1] &= ~Psenable;
}
iunlock(ctlr);
qunlock(&ctlr->portlck);
}
static void
portreset(Hci *hp, int port, int on)
{
Eopio *opio;
Ctlr *ctlr;
ctlr = hp->aux;
opio = ctlr->opio;
eqlock(&ctlr->portlck);
ilock(ctlr);
if(on)
opio->portsc[port-1] = (opio->portsc[port-1] & ~Psenable) | Psreset; /* initiate reset */
else
opio->portsc[port-1] &= ~Psreset; /* terminate reset */
iunlock(ctlr);
dprint("ehci %#p after port %d reset; sts %#lux\n",
ctlr->capio, port, *portscp);
qunlock(&ctlr->portlck);
poperror();
return 0;
}
static int
@ -1751,18 +1701,10 @@ portstatus(Hci *hp, int port)
ctlr = hp->aux;
opio = ctlr->opio;
eqlock(&ctlr->portlck);
if(waserror()){
iunlock(ctlr);
qunlock(&ctlr->portlck);
nexterror();
}
ilock(ctlr);
s = opio->portsc[port-1];
if(s & (Psstatuschg | Pschange)){
if(s & (Psstatuschg | Pschange))
opio->portsc[port-1] = s;
coherence();
ddprint("ehci %#p port %d status %#x\n", ctlr->capio, port, s);
}
/*
* If the port is a low speed port we yield ownership now
* to the [uo]hci companion controller and pretend it's not here.
@ -1773,7 +1715,6 @@ portstatus(Hci *hp, int port)
}
iunlock(ctlr);
qunlock(&ctlr->portlck);
poperror();
/*
* We must return status bits as a
@ -1822,7 +1763,6 @@ seprintep(char *s, char *e, Ep *ep)
case Tctl:
cio = ep->aux;
s = seprintio(s, e, cio, "c");
s = seprint(s, e, "\trepl %llux ndata %d\n", ep->rhrepl, cio->ndata);
break;
case Tbulk:
case Tintr:

View file

@ -1785,26 +1785,61 @@ portstatus(Hci *hp, int port)
return ps;
}
enum {
RW1S = PR | WPR,
RW1CS = PED | CSC | PEC | WRC | OCC | PRC | PLC | CEC,
};
static int
portenable(Hci*, int, int)
static void
portenable(Hci *hp, int port, int on)
{
return 0;
Ctlr *ctlr = hp->aux;
u32int *portsc;
if(on || ctlr->port == nil || needrecover(ctlr))
return;
portsc = &ctlr->port[port-1].reg[PORTSC];
*portsc = (*portsc & ~(RW1CS|RW1S)) | PED;
}
static int
static void
portreset(Hci *hp, int port, int on)
{
Ctlr *ctlr = hp->aux;
u32int *portsc;
if(!on || ctlr->port == nil || needrecover(ctlr))
return;
portsc = &ctlr->port[port-1].reg[PORTSC];
*portsc = (*portsc & ~(RW1CS|RW1S)) | PR;
}
static void
bhportreset(Hci *hp, int port, int on)
{
Ctlr *ctlr = hp->aux;
u32int *portsc;
if(!on || ctlr->port == nil || needrecover(ctlr))
return;
portsc = &ctlr->port[port-1].reg[PORTSC];
*portsc = (*portsc & ~(RW1CS|RW1S)) | WPR;
}
static void
portpower(Hci *hp, int port, int on)
{
Ctlr *ctlr = hp->aux;
u32int *portsc;
if(ctlr->port == nil || needrecover(ctlr))
return 0;
if(on){
ctlr->port[port-1].reg[PORTSC] |= PR;
tsleep(&up->sleep, return0, nil, 200);
}
return 0;
return;
portsc = &ctlr->port[port-1].reg[PORTSC];
if(on)
*portsc = (*portsc & ~(RW1CS|RW1S|PP)) | PP;
else
*portsc = (*portsc & ~(RW1CS|RW1S|PP));
}
static u64int
@ -1851,6 +1886,8 @@ xhcilinkage(Hci *hp, Xhci *ctlr)
hp->devclose = devclose;
hp->portenable = portenable;
hp->portreset = portreset;
hp->bhportreset = bhportreset;
hp->portpower = portpower;
hp->portstatus = portstatus;
hp->debug = setdebug;