nusb/usbd: create endpoint files for conf #1, usb3 preparation

This commit is contained in:
cinap_lenrek 2017-07-31 03:19:24 +02:00
parent d46099e3af
commit 215b67ff3d
6 changed files with 128 additions and 133 deletions

View file

@ -27,7 +27,12 @@ parsedev(Dev *xd, uchar *b, int n)
return -1; return -1;
} }
d->csp = CSP(dd->bDevClass, dd->bDevSubClass, dd->bDevProtocol); d->csp = CSP(dd->bDevClass, dd->bDevSubClass, dd->bDevProtocol);
d->ep[0]->maxpkt = xd->maxpkt = dd->bMaxPacketSize0; d->ver = GET2(dd->bcdUSB);
xd->isusb3 = (d->ver >= 0x0300);
if(xd->isusb3)
d->ep[0]->maxpkt = xd->maxpkt = 1<<dd->bMaxPacketSize0;
else
d->ep[0]->maxpkt = xd->maxpkt = dd->bMaxPacketSize0;
d->class = dd->bDevClass; d->class = dd->bDevClass;
d->nconf = dd->bNumConfigurations; d->nconf = dd->bNumConfigurations;
if(d->nconf == 0) if(d->nconf == 0)

View file

@ -36,7 +36,7 @@ enum {
Rep = 2, /* endpoint */ Rep = 2, /* endpoint */
Rother = 3, Rother = 3,
/* standard requests */ /* standard requests (USB2.0) */
Rgetstatus = 0, Rgetstatus = 0,
Rclearfeature = 1, Rclearfeature = 1,
Rsetfeature = 3, Rsetfeature = 3,
@ -49,6 +49,10 @@ enum {
Rsetiface = 11, Rsetiface = 11,
Rsynchframe = 12, Rsynchframe = 12,
/* standard requests (USB3.0) */
Rsethubdepth = 12,
Rgetporterrcnt = 13,
Rgetcur = 0x81, Rgetcur = 0x81,
Rgetmin = 0x82, Rgetmin = 0x82,
Rgetmax = 0x83, Rgetmax = 0x83,
@ -169,6 +173,7 @@ struct Dev
int id; /* usb id for device or ep. number */ int id; /* usb id for device or ep. number */
int dfd; /* descriptor for the data file */ int dfd; /* descriptor for the data file */
int cfd; /* descriptor for the control file */ int cfd; /* descriptor for the control file */
int isusb3; /* this is a usb3 device */
int maxpkt; /* cached from usb description */ int maxpkt; /* cached from usb description */
Ref nerrs; /* number of errors in requests */ Ref nerrs; /* number of errors in requests */
Usbdev* usb; /* USB description */ Usbdev* usb; /* USB description */
@ -182,6 +187,7 @@ struct Dev
*/ */
struct Usbdev struct Usbdev
{ {
int ver; /* usb version */
ulong csp; /* USB class/subclass/proto */ ulong csp; /* USB class/subclass/proto */
int vid; /* vendor id */ int vid; /* vendor id */
int did; /* product (device) id */ int did; /* product (device) id */

View file

@ -50,9 +50,7 @@ enum
Pconfiged, Pconfiged,
/* Delays, timeouts (ms) */ /* Delays, timeouts (ms) */
// Spawndelay = 1000, /* how often may we re-spawn a driver */
Spawndelay = 250, /* how often may we re-spawn a driver */ Spawndelay = 250, /* how often may we re-spawn a driver */
// Connectdelay = 1000, /* how much to wait after a connect */
Connectdelay = 500, /* how much to wait after a connect */ Connectdelay = 500, /* how much to wait after a connect */
Resetdelay = 20, /* how much to wait after a reset */ Resetdelay = 20, /* how much to wait after a reset */
Enabledelay = 20, /* how much to wait after an enable */ Enabledelay = 20, /* how much to wait after an enable */
@ -82,6 +80,7 @@ struct Hub
Port *port; Port *port;
int failed; /* I/O error while enumerating */ int failed; /* I/O error while enumerating */
int isroot; /* set if root hub */ int isroot; /* set if root hub */
int depth; /* hub depth */
Dev *dev; /* for this hub */ Dev *dev; /* for this hub */
Hub *next; /* in list of hubs */ Hub *next; /* in list of hubs */
}; };
@ -89,13 +88,11 @@ struct Hub
struct Port struct Port
{ {
int state; /* state of the device */ int state; /* state of the device */
int sts; /* old port status */ u32int sts; /* old port status */
uchar removable; uchar removable;
uchar pwrctl; uchar pwrctl;
Dev *dev; /* attached device (if non-nil) */ Dev *dev; /* attached device (if non-nil) */
Hub *hub; /* non-nil if hub attached */ Hub *hub; /* non-nil if hub attached */
int devnb; /* device number */
uvlong *devmaskp; /* ptr to dev mask */
}; };
/* USB HUB descriptor */ /* USB HUB descriptor */

View file

@ -1,6 +1,6 @@
int attachdev(Port*); int attachdev(Hub*, Port*);
void detachdev(Port*); void detachdev(Hub*, Port*);
void work(void); void work(void);
Hub* newhub(char *, Dev *); Hub* newhub(char *, Dev*, Hub*);
void hname(char *); void hname(char *);
void checkidle(void); void checkidle(void);

View file

@ -10,35 +10,9 @@ QLock hublock;
static int nhubs; static int nhubs;
static int mustdump; static int mustdump;
static int pollms = Pollms; static int pollms = Pollms;
static Lock masklck;
static char *dsname[] = { "disabled", "attached", "configed" }; static char *dsname[] = { "disabled", "attached", "configed" };
int
getdevnb(uvlong *maskp)
{
int i;
lock(&masklck);
for(i = 0; i < 8 * sizeof *maskp; i++)
if((*maskp & (1ULL<<i)) == 0){
*maskp |= 1ULL<<i;
unlock(&masklck);
return i;
}
unlock(&masklck);
return -1;
}
void
putdevnb(uvlong *maskp, int id)
{
lock(&masklck);
if(id >= 0)
*maskp &= ~(1ULL<<id);
unlock(&masklck);
}
static int static int
hubfeature(Hub *h, int port, int f, int on) hubfeature(Hub *h, int port, int f, int on)
{ {
@ -51,25 +25,6 @@ hubfeature(Hub *h, int port, int f, int on)
return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0); return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
} }
/*
* This may be used to detect overcurrent on the hub
*/
static void
checkhubstatus(Hub *h)
{
uchar buf[4];
int sts;
if(h->isroot) /* not for root hubs */
return;
if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){
dprint(2, "%s: get hub status: %r\n", h->dev->dir);
return;
}
sts = GET2(buf);
dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
}
static int static int
confighub(Hub *h) confighub(Hub *h)
{ {
@ -124,6 +79,10 @@ Config:
pp->removable = (dd->DeviceRemovable[offset] & mask) != 0; pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;
pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0; pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
} }
if(h->dev->isusb3){
type = Rh2d|Rclass|Rdev;
usbcmd(h->dev, type, Rsethubdepth, h->depth, 0, nil, 0);
}
return 0; return 0;
} }
@ -143,7 +102,7 @@ configroothub(Hub *h)
if(nr < 0) if(nr < 0)
goto Done; goto Done;
buf[nr] = 0; buf[nr] = 0;
d->isusb3 = strstr(buf, "speed super") != nil;
p = strstr(buf, "ports "); p = strstr(buf, "ports ");
if(p == nil) if(p == nil)
fprint(2, "%s: %s: no port information\n", argv0, d->dir); fprint(2, "%s: %s: no port information\n", argv0, d->dir);
@ -160,7 +119,7 @@ Done:
} }
Hub* Hub*
newhub(char *fn, Dev *d) newhub(char *fn, Dev *d, Hub *ph)
{ {
Hub *h; Hub *h;
int i; int i;
@ -169,27 +128,26 @@ newhub(char *fn, Dev *d)
h = emallocz(sizeof(Hub), 1); h = emallocz(sizeof(Hub), 1);
h->isroot = (d == nil); h->isroot = (d == nil);
if(h->isroot){ if(h->isroot){
h->depth = -1;
h->dev = opendev(fn); h->dev = opendev(fn);
if(h->dev == nil){ if(h->dev == nil){
fprint(2, "%s: opendev: %s: %r", argv0, fn); fprint(2, "%s: opendev: %s: %r", argv0, fn);
goto Fail; goto Fail;
} }
configroothub(h); /* never fails */
if(opendevdata(h->dev, ORDWR) < 0){ if(opendevdata(h->dev, ORDWR) < 0){
fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn); fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);
closedev(h->dev);
goto Fail; goto Fail;
} }
configroothub(h); /* never fails */
}else{ }else{
h->depth = ph->depth+1;
h->dev = d; h->dev = d;
if(confighub(h) < 0){ if(confighub(h) < 0){
fprint(2, "%s: %s: config: %r\n", argv0, fn); fprint(2, "%s: %s: config: %r\n", argv0, fn);
goto Fail; goto Fail;
} }
} }
if(h->dev == nil){
fprint(2, "%s: opendev: %s: %r\n", argv0, fn);
goto Fail;
}
devctl(h->dev, "hub"); devctl(h->dev, "hub");
ud = h->dev->usb; ud = h->dev->usb;
if(h->isroot) if(h->isroot)
@ -265,13 +223,13 @@ closehub(Hub *h)
free(h); free(h);
} }
static int static u32int
portstatus(Hub *h, int p) portstatus(Hub *h, int p)
{ {
Dev *d; Dev *d;
uchar buf[4]; uchar buf[4];
u32int sts;
int t; int t;
int sts;
int dbg; int dbg;
dbg = usbdebug; dbg = usbdebug;
@ -282,34 +240,38 @@ portstatus(Hub *h, int p)
if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0) if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)
sts = -1; sts = -1;
else else
sts = GET2(buf); sts = GET4(buf);
usbdebug = dbg; usbdebug = dbg;
return sts; return sts;
} }
static char* static char*
stsstr(int sts) stsstr(int sts, int isusb3)
{ {
static char s[80]; static char s[80];
char *e; char *e;
e = s; e = s;
if(sts&PSsuspend)
*e++ = 'z';
if(sts&PSreset)
*e++ = 'r';
if(sts&PSslow)
*e++ = 'l';
if(sts&PShigh)
*e++ = 'h';
if(sts&PSchange)
*e++ = 'c';
if(sts&PSenable)
*e++ = 'e';
if(sts&PSstatuschg)
*e++ = 's';
if(sts&PSpresent) if(sts&PSpresent)
*e++ = 'p'; *e++ = 'p';
if(sts&PSenable)
*e++ = 'e';
if(sts&PSovercurrent)
*e++ = 'o';
if(sts&PSreset)
*e++ = 'r';
if(!isusb3){
if(sts&PSslow)
*e++ = 'l';
if(sts&PShigh)
*e++ = 'h';
if(sts&PSchange)
*e++ = 'c';
if(sts&PSstatuschg)
*e++ = 's';
if(sts&PSsuspend)
*e++ = 'z';
}
if(e == s) if(e == s)
*e++ = '-'; *e++ = '-';
*e = 0; *e = 0;
@ -322,6 +284,8 @@ getmaxpkt(Dev *d, int islow)
uchar buf[64]; /* More room to try to get device-specific descriptors */ uchar buf[64]; /* More room to try to get device-specific descriptors */
DDev *dd; DDev *dd;
if(d->isusb3)
return 512;
dd = (DDev*)buf; dd = (DDev*)buf;
if(islow) if(islow)
dd->bMaxPacketSize0 = 8; dd->bMaxPacketSize0 = 8;
@ -336,7 +300,7 @@ getmaxpkt(Dev *d, int islow)
* BUG: does not consider max. power avail. * BUG: does not consider max. power avail.
*/ */
static Dev* static Dev*
portattach(Hub *h, int p, int sts) portattach(Hub *h, int p, u32int sts)
{ {
Dev *d; Dev *d;
Port *pp; Port *pp;
@ -352,31 +316,43 @@ portattach(Hub *h, int p, int sts)
nd = nil; nd = nil;
pp->state = Pattached; pp->state = Pattached;
dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts); dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);
sleep(Connectdelay); if(h->dev->isusb3){
if(hubfeature(h, p, Fportenable, 1) < 0) sleep(Connectdelay);
dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
sleep(Enabledelay);
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); sts = portstatus(h, p);
if((sts & PSenable) == 0) if(sts == -1)
goto Fail; goto Fail;
if((sts & PSenable) == 0){
dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
goto Fail;
}
sp = "super";
} else {
sleep(Connectdelay);
if(hubfeature(h, p, Fportenable, 1) < 0)
dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
sleep(Enabledelay);
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 == -1)
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;
}
sp = "full";
if(sts & PSslow)
sp = "low";
if(sts & PShigh)
sp = "high";
} }
sp = "full"; dprint(2, "%s: %s: port %d: attached status %#ux, speed %s\n", argv0, d->dir, p, sts, sp);
if(sts & PSslow)
sp = "low";
if(sts & PShigh)
sp = "high";
dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts);
if(devctl(d, "newdev %s %d", sp, p) < 0){ if(devctl(d, "newdev %s %d", sp, p) < 0){
fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p); fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
@ -413,7 +389,7 @@ portattach(Hub *h, int p, int sts)
dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p); dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
goto Fail; goto Fail;
} }
nd->isusb3 = h->dev->isusb3;
mp=getmaxpkt(nd, strcmp(sp, "low") == 0); mp=getmaxpkt(nd, strcmp(sp, "low") == 0);
if(mp < 0){ if(mp < 0){
dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p); dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);
@ -422,9 +398,6 @@ portattach(Hub *h, int p, int sts)
dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp); dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);
devctl(nd, "maxpkt %d", mp); devctl(nd, "maxpkt %d", mp);
} }
if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)
dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",
argv0, d->dir, p, nd->dir);
if(configdev(nd) < 0){ if(configdev(nd) < 0){
dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p); dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);
goto Fail; goto Fail;
@ -446,7 +419,8 @@ Fail:
pp->sts = 0; pp->sts = 0;
if(pp->hub != nil) if(pp->hub != nil)
pp->hub = nil; /* hub closed by enumhub */ pp->hub = nil; /* hub closed by enumhub */
hubfeature(h, p, Fportenable, 0); if(!h->dev->isusb3)
hubfeature(h, p, Fportenable, 0);
if(nd != nil) if(nd != nil)
devctl(nd, "detach"); devctl(nd, "detach");
closedev(nd); closedev(nd);
@ -475,11 +449,8 @@ portdetach(Hub *h, int p)
closehub(pp->hub); closehub(pp->hub);
pp->hub = nil; pp->hub = nil;
} }
if(pp->devmaskp != nil)
putdevnb(pp->devmaskp, pp->devnb);
pp->devmaskp = nil;
if(pp->dev != nil){ if(pp->dev != nil){
detachdev(pp); detachdev(h, pp);
devctl(pp->dev, "detach"); devctl(pp->dev, "detach");
closedev(pp->dev); closedev(pp->dev);
@ -523,7 +494,7 @@ portresetwanted(Hub *h, int p)
static void static void
portreset(Hub *h, int p) portreset(Hub *h, int p)
{ {
int sts; u32int sts;
Dev *d, *nd; Dev *d, *nd;
Port *pp; Port *pp;
@ -537,10 +508,12 @@ portreset(Hub *h, int p)
} }
sleep(Resetdelay); sleep(Resetdelay);
sts = portstatus(h, p); sts = portstatus(h, p);
if(sts < 0) if(sts == -1)
goto Fail; goto Fail;
if((sts & PSenable) == 0){ if((sts & PSenable) == 0){
dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p); dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
if(h->dev->isusb3)
goto Fail;
hubfeature(h, p, Fportenable, 1); hubfeature(h, p, Fportenable, 1);
sts = portstatus(h, p); sts = portstatus(h, p);
if((sts & PSenable) == 0) if((sts & PSenable) == 0)
@ -572,16 +545,17 @@ Fail:
pp->sts = 0; pp->sts = 0;
if(pp->hub != nil) if(pp->hub != nil)
pp->hub = nil; /* hub closed by enumhub */ pp->hub = nil; /* hub closed by enumhub */
hubfeature(h, p, Fportenable, 0); if(!h->dev->isusb3)
hubfeature(h, p, Fportenable, 0);
if(nd != nil) if(nd != nil)
devctl(nd, "detach"); devctl(nd, "detach");
closedev(nd); closedev(nd);
} }
static int static int
portgone(Port *pp, int sts) portgone(Port *pp, u32int sts)
{ {
if(sts < 0) if(sts == -1)
return 1; return 1;
/* /*
* If it was enabled and it's not now then it may be reconnect. * If it was enabled and it's not now then it may be reconnect.
@ -595,7 +569,7 @@ portgone(Port *pp, int sts)
static int static int
enumhub(Hub *h, int p) enumhub(Hub *h, int p)
{ {
int sts; u32int sts;
Dev *d; Dev *d;
Port *pp; Port *pp;
int onhubs; int onhubs;
@ -607,31 +581,33 @@ enumhub(Hub *h, int p)
fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p); fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);
sts = portstatus(h, p); sts = portstatus(h, p);
if(sts < 0){ if(sts == -1){
hubfail(h); /* avoid delays on detachment */ hubfail(h); /* avoid delays on detachment */
return -1; return -1;
} }
pp = &h->port[p]; pp = &h->port[p];
onhubs = nhubs; onhubs = nhubs;
if((sts & PSsuspend) != 0){ if(!h->dev->isusb3){
if(hubfeature(h, p, Fportenable, 1) < 0) if((sts & PSsuspend) != 0){
dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p); if(hubfeature(h, p, Fportenable, 1) < 0)
sleep(Enabledelay); dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
sts = portstatus(h, p); sleep(Enabledelay);
fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts); sts = portstatus(h, p);
fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
}
} }
if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){ if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
if(portattach(h, p, sts) != nil) if(portattach(h, p, sts) != nil)
if(attachdev(pp) < 0) if(attachdev(h, pp) < 0)
portdetach(h, p); portdetach(h, p);
}else if(portgone(pp, sts)){ }else if(portgone(pp, sts)){
portdetach(h, p); portdetach(h, p);
}else if(portresetwanted(h, p)) }else if(portresetwanted(h, p))
portreset(h, p); portreset(h, p);
else if(pp->sts != sts){ else if(pp->sts != sts){
dprint(2, "%s: %s port %d: sts %s %#x ->", dprint(2, "%s: %s port %d: sts %s %#ux ->",
argv0, d->dir, p, stsstr(pp->sts), pp->sts); argv0, d->dir, p, stsstr(pp->sts, h->dev->isusb3), pp->sts);
dprint(2, " %s %#x\n",stsstr(sts), sts); dprint(2, " %s %#ux\n",stsstr(sts, h->dev->isusb3), sts);
} }
pp->sts = sts; pp->sts = sts;
if(onhubs != nhubs) if(onhubs != nhubs)
@ -663,7 +639,7 @@ work(void)
hubs = nil; hubs = nil;
while((fn = rendezvous(work, nil)) != nil){ while((fn = rendezvous(work, nil)) != nil){
dprint(2, "%s: %s starting\n", argv0, fn); dprint(2, "%s: %s starting\n", argv0, fn);
h = newhub(fn, nil); h = newhub(fn, nil, nil);
if(h == nil) if(h == nil)
fprint(2, "%s: %s: newhub failed: %r\n", argv0, fn); fprint(2, "%s: %s: newhub failed: %r\n", argv0, fn);
free(fn); free(fn);

View file

@ -373,9 +373,10 @@ assignhname(Dev *dev)
} }
int int
attachdev(Port *p) attachdev(Hub *h, Port *p)
{ {
Dev *d = p->dev; Dev *d = p->dev;
int id;
if(d->usb->class == Clhub){ if(d->usb->class == Clhub){
/* /*
@ -384,11 +385,21 @@ attachdev(Port *p)
* has the config address in use. * has the config address in use.
* We cancel kernel debug for these eps. too chatty. * We cancel kernel debug for these eps. too chatty.
*/ */
if((p->hub = newhub(d->dir, d)) == nil) if((p->hub = newhub(d->dir, d, h)) == nil)
return -1; return -1;
return 0; 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 control endpoint so driver can open it */
close(d->dfd); close(d->dfd);
d->dfd = -1; d->dfd = -1;
@ -406,7 +417,7 @@ attachdev(Port *p)
} }
void void
detachdev(Port *p) detachdev(Hub *, Port *p)
{ {
Dev *d = p->dev; Dev *d = p->dev;