/*
* USB device driver framework.
*
* This is in charge of providing access to actual HCIs
* and providing I/O to the various endpoints of devices.
* A separate user program (usbd) is in charge of
* enumerating the bus, setting up endpoints and
* starting devices (also user programs).
*
* The interface provided is a violation of the standard:
* you're welcome.
*
* The interface consists of a root directory with several files
* plus a directory (epN.M) with two files per endpoint.
* A device is represented by its first endpoint, which
* is a control endpoint automatically allocated for each device.
* Device control endpoints may be used to create new endpoints.
* Devices corresponding to hubs may also allocate new devices,
* perhaps also hubs. Initially, a hub device is allocated for
* each controller present, to represent its root hub. Those can
* never be removed.
*
* All endpoints refer to the first endpoint (epN.0) of the device,
* which keeps per-device information, and also to the HCI used
* to reach them. Although all endpoints cache that information.
*
* epN.M/data files permit I/O and are considered DMEXCL.
* epN.M/ctl files provide status info and accept control requests.
*
* Endpoints may be given file names to be listed also at #u,
* for those drivers that have nothing to do after configuring the
* device and its endpoints.
*
* Drivers for different controllers are kept at usb[oue]hci.c
* It's likely we could factor out much from controllers into
* a generic controller driver, the problem is that details
* regarding how to handle toggles, tokens, Tds, etc. will
* get in the way. Thus, code is probably easier the way it is.
*/
Qep0dir, /* #u/usb/ep0.0 - endpoint 0 dir */
Qep0io, /* #u/usb/ep0.0/data - endpoint 0 I/O */
Qep0ctl, /* #u/usb/ep0.0/ctl - endpoint 0 ctl. */
Qep0dummy, /* give 4 qids to each endpoint */
Qepdir = 0, /* (qid-qep0dir)&3 is one of these */
Qepio, /* to identify which file for the endpoint */
Qepctl,
/* ... */
/* Usb ctls. */
CMdebug = 0, /* debug on|off */
CMdump, /* dump (data structures for debug) */
/* Ep. ctls */
CMnew = 0, /* new nb ctl|bulk|intr|iso r|w|rw (endpoint) */
CMnewdev, /* newdev full|low|high|super portnb (allocate new devices) */
CMhub, /* hub (set the device as a hub) */
CMspeed, /* speed full|low|high|no */
CMmaxpkt, /* maxpkt size */
CMntds, /* ntds nb (max nb. of tds per µframe) */
CMclrhalt, /* clrhalt (halt was cleared on endpoint) */
CMpollival, /* pollival interval (interrupt/iso) */
CMhz, /* hz n (samples/sec; iso) */
CMsamplesz, /* samplesz n (sample size; iso) */
CMinfo, /* info infostr (ke.ep info for humans) */
CMdetach, /* detach (abort I/O forever on this ep). */
CMaddress, /* address (address is assigned) */
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) */
CMsampledelay, /* maximum delay introduced by buffering (iso) */
CMpreset, /* reset the port */
CMuframes, /* set uframe mode (iso) */
static int debug;
static Hcitype hcitypes[Nhcis];
static Hci* hcis[Nhcis];
static QLock epslck; /* add, del, lookup endpoints */
static Ep* eps[Neps]; /* all endpoints known */
static int epmax; /* 1 + last endpoint index used */
static int usbidgen; /* device address generator */
/*
* Is there something like this in a library? should it be?
*/
char*
seprintdata(char *s, char *se, uchar *d, int n)
{
int i, l;
s = seprint(s, se, " %#p[%d]: ", d, n);
l = n;
if(l > 10)
l = 10;
for(i=0; i<l; i++)
s = seprint(s, se, " %2.2ux", d[i]);
if(l < n)
s = seprint(s, se, "...");
return s;
}
for(i = 0; i < nelem(ttname); i++)
if(strcmp(name, ttname[i]) == 0)
return i;
/* may be a std. USB ep. type */
i = strtol(name, nil, 0);
switch(i+1){
case Tctl:
case Tiso:
case Tbulk:
case Tintr:
return i+1;
default:
return Tnone;
}
}
static int
isqtype(int q, int type)
{
if(q < Qep0dir)
return 0;
q -= Qep0dir;
return (q & 3) == type;
}
void
addhcitype(char* t, int (*r)(Hci*))
{
static int ntype;
if(ntype == Nhcis)
panic("too many USB host interface types");
hcitypes[ntype].type = t;
hcitypes[ntype].reset = r;
ntype++;
}
static char*
seprintep(char *s, char *se, Ep *ep, int all)
{
static char* dsnames[] = { "config", "enabled", "detached", "reset" };
Udev *d;
int i;
int di;
d = ep->dev;
qlock(ep);
if(waserror()){
qunlock(ep);
nexterror();
}
di = ep->dev->nb;
if(all)
s = seprint(s, se, "dev %d ep %d ", di, ep->nb);
s = seprint(s, se, "%s", dsnames[ep->dev->state]);
s = seprint(s, se, " %s", ttname[ep->ttype]);
assert(ep->mode == OREAD || ep->mode == OWRITE || ep->mode == ORDWR);
s = seprint(s, se, " %s", usbmodename[ep->mode]);
s = seprint(s, se, " speed %s", spname[d->speed]);
s = seprint(s, se, " maxpkt %ld", ep->maxpkt);
s = seprint(s, se, " ntds %d", ep->ntds);
s = seprint(s, se, " pollival %ld", ep->pollival);
s = seprint(s, se, " samplesz %ld", ep->samplesz);
s = seprint(s, se, " hz %ld", ep->hz);
s = seprint(s, se, " uframes %d", ep->uframes);
s = seprint(s, se, " hub %d", ep->dev->hub);
s = seprint(s, se, " port %d", ep->dev->port);
s = seprint(s, se, " rootport %d", ep->dev->rootport);
s = seprint(s, se, " addr %d", ep->dev->addr);
if(ep->inuse)
s = seprint(s, se, " busy");
else
s = seprint(s, se, " idle");
if(all){
s = seprint(s, se, " load %uld", ep->load);
s = seprint(s, se, " ref %ld addr %#p", ep->ref, ep);
s = seprint(s, se, " idx %d", ep->idx);
if(ep->name != nil)
s = seprint(s, se, " name '%s'", ep->name);
if(ep->tmout != 0)
s = seprint(s, se, " tmout");
if(ep == ep->ep0){
s = seprint(s, se, " ctlrno %#x", ep->hp->ctlrno);
s = seprint(s, se, " eps:");
for(i = 0; i < nelem(d->eps); i++)
if(d->eps[i] != nil)
s = seprint(s, se, " ep%d.%d", di, i);
}
}
if(ep->info != nil)
s = seprint(s, se, "\n%s %s\n", ep->info, ep->hp->type);
else
s = seprint(s, se, "\n");
qunlock(ep);
poperror();
return s;
}
/*
* Create a new endpoint for the device
* accessed via the given endpoint 0.
*/
static Ep*
newdevep(Ep *ep, int i, int tt, int mode)
{
Ep *nep;
Udev *d;
/*
* modern machines have too many usb controllers to list on
* the console.
*/
dprint("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n",
epnb, hcitypes[cardno].type, hp->port, hp->irq);
epnb++;
return hp;
}
static void
usbreset(void)
{
int cardno, ctlrno;
Hci *hp;
static int
usbstat(Chan *c, uchar *db, int n)
{
return devstat(c, db, n, nil, 0, usbgen);
}
/*
* µs for the given transfer, for bandwidth allocation.
* This is a very rough worst case for what 5.11.3
* of the usb 2.0 spec says.
* Also, we are using maxpkt and not actual transfer sizes.
* Only when we are sure we
* are not exceeding b/w might we consider adjusting it.
*/
static ulong
usbload(int speed, int maxpkt)
{
enum{ Hostns = 1000, Hubns = 333 };
ulong l;
ulong bs;
l = 0;
bs = 10UL * maxpkt;
switch(speed){
case Highspeed:
l = 55*8*2 + 2 * (3 + bs) + Hostns;
break;
case Fullspeed:
l = 9107 + 84 * (4 + bs) + Hostns;
break;
case Lowspeed:
l = 64107 + 2 * Hubns + 667 * (3 + bs) + Hostns;
break;
default:
print("usbload: bad speed %d\n", speed);
/* let it run */
}
return l / 1000UL; /* in µs */
}
static Chan*
usbopen(Chan *c, int omode)
{
int q;
Ep *ep;
int mode;
static long
ctlread(Chan *c, void *a, long n, vlong offset)
{
int q;
char *s;
char *us;
char *se;
Ep *ep;
int i;
q = QID(c->qid);
us = s = smalloc(READSTR);
se = s + READSTR;
if(waserror()){
free(us);
nexterror();
}
if(q == Qctl)
for(i = 0; i < epmax; i++){
ep = getep(i);
if(ep != nil){
if(waserror()){
putep(ep);
nexterror();
}
s = seprint(s, se, "ep%d.%d ", ep->dev->nb, ep->nb);
s = seprintep(s, se, ep, 0);
poperror();
}
putep(ep);
}
else{
ep = getep(qid2epidx(q));
if(ep == nil)
error(Eio);
if(waserror()){
putep(ep);
nexterror();
}
if(c->aux != nil){
/* After a new endpoint request we read
* the new endpoint name back.
*/
strecpy(s, se, c->aux);
free(c->aux);
c->aux = nil;
}else
seprintep(s, se, ep, 0);
poperror();
putep(ep);
}
n = readstr(offset, a, n, us);
poperror();
free(us);
return n;
}
/*
* Fake root hub emulation.
*/
static long
rhubread(Ep *ep, void *a, long n)
{
uchar b[8];
/*
* Many endpoint ctls. simply update the portable representation
* of the endpoint. The actual controller driver will look
* at them to setup the endpoints as dictated.
*/
static long
epctl(Ep *ep, Chan *c, void *a, long n)
{
int i, l, mode, nb, tt;
char *b, *s;
Cmdbuf *cb;
Cmdtab *ct;
Ep *nep;
Udev *d;
static char *Info = "info ";
d = ep->dev;
cb = parsecmd(a, n);
if(waserror()){
free(cb);
nexterror();
}
ct = lookupcmd(cb, epctls, nelem(epctls));
i = ct->index;
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)
if(ep != ep->ep0 && ep->inuse != 0)
error("must configure before using");
switch(i){
case CMnew:
deprint("usb epctl %s\n", cb->f[0]);
nb = strtol(cb->f[1], nil, 0);
if(nb < 0 || nb >= Ndeveps)
error("bad endpoint number");
tt = name2ttype(cb->f[2]);
if(tt == Tnone)
error("unknown endpoint type");
mode = name2mode(cb->f[3]);
if(mode < 0)
error("unknown i/o mode");
newdevep(ep, nb, tt, mode);
break;
case CMnewdev:
deprint("usb epctl %s\n", cb->f[0]);
if(ep != ep->ep0 || d->ishub == 0)
error("not a hub setup endpoint");
l = name2speed(cb->f[1]);
if(l == Nospeed)
error("speed must be full|low|high|super");
if(l != d->speed && (l == Superspeed || d->speed == Superspeed))
error("wrong speed for superspeed hub/device");
nep = newdev(ep->hp, 0, 0);
nep->dev->speed = l;
if(l == Superspeed)
nep->maxpkt = 512;
else if(l != Lowspeed)
nep->maxpkt = 64; /* assume full speed */
nep->dev->hub = d->addr;
nep->dev->port = atoi(cb->f[2]);
nep->dev->depth = d->depth+1;
nep->dev->rootport = rootport(ep, nep->dev->port);
nep->dev->routestr = d->routestr | (((nep->dev->port&15) << 4*nep->dev->depth) >> 4);
/* next read request will read
* the name for the new endpoint
*/
l = sizeof(up->genbuf);
snprint(up->genbuf, l, "ep%d.%d", nep->dev->nb, nep->nb);
kstrdup(&c->aux, up->genbuf);
break;
case CMhub:
deprint("usb epctl %s\n", cb->f[0]);
d->ishub = 1;
break;
case CMspeed:
l = name2speed(cb->f[1]);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(l == Nospeed)
error("speed must be full|low|high|super");
if(l != d->speed && (l == Superspeed || d->speed == Superspeed))
error("cannot change speed on superspeed device");
qlock(ep->ep0);
d->speed = l;
qunlock(ep->ep0);
break;
case CMmaxpkt:
l = strtoul(cb->f[1], nil, 0);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(l < 1 || l > 1024)
error("maxpkt not in [1:1024]");
qlock(ep);
ep->maxpkt = l;
qunlock(ep);
break;
case CMntds:
l = strtoul(cb->f[1], nil, 0);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(l < 1 || l > 3)
error("ntds not in [1:3]");
qlock(ep);
ep->ntds = l;
qunlock(ep);
break;
case CMpollival:
if(ep->ttype != Tintr && ep->ttype != Tiso)
error("not an intr or iso endpoint");
l = strtoul(cb->f[1], nil, 0);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(ep->dev->speed == Fullspeed || ep->dev->speed == Lowspeed){
if(l < 1 || l > 255)
error("pollival not in [1:255]");
} else {
if(l < 1 || l > 16)
error("pollival power not in [1:16]");
l = 1 << l-1;
}
qlock(ep);
ep->pollival = l;
if(ep->ttype == Tiso)
setmaxpkt(ep, "pollival");
qunlock(ep);
break;
case CMsamplesz:
if(ep->ttype != Tiso)
error("not an iso endpoint");
l = strtoul(cb->f[1], nil, 0);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(l <= 0 || l > 8)
error("samplesz not in [1:8]");
qlock(ep);
ep->samplesz = l;
setmaxpkt(ep, "samplesz");
qunlock(ep);
break;
case CMhz:
if(ep->ttype != Tiso)
error("not an iso endpoint");
l = strtoul(cb->f[1], nil, 0);
deprint("usb epctl %s %d\n", cb->f[0], l);
if(l <= 0 || l > 100000)
error("hz not in [1:100000]");
qlock(ep);
ep->hz = l;
setmaxpkt(ep, "hz");
qunlock(ep);
break;
case CMuframes:
if(ep->ttype != Tiso)
error("not an iso endpoint");
l = strtoul(cb->f[1], nil, 0);
deprint("usb uframes %s %d\n", cb->f[0], l);
if(l != 0 && l != 1)
error("uframes not in [0:1]");
qlock(ep);
ep->uframes = l;
qunlock(ep);
break;
case CMclrhalt:
qlock(ep);
deprint("usb epctl %s\n", cb->f[0]);
ep->clrhalt = 1;
qunlock(ep);
break;
case CMinfo:
deprint("usb epctl %s\n", cb->f[0]);
l = strlen(Info);
s = a;
if(n < l+2 || strncmp(Info, s, l) != 0)
error(Ebadctl);
if(n > 1024)
n = 1024;
b = smalloc(n);
memmove(b, s+l, n-l);
b[n-l] = 0;
if(b[n-l-1] == '\n')
b[n-l-1] = 0;
qlock(ep);
free(ep->info);
ep->info = b;
qunlock(ep);
break;
case CMaddress:
deprint("usb epctl %s\n", cb->f[0]);
if(ep->dev->addr == 0)
ep->dev->addr = ep->dev->nb;
ep->dev->state = Denabled;
break;
case CMdetach:
if(ep->dev->isroot != 0)
error("can't detach a root hub");
deprint("usb epctl %s ep%d.%d\n",
cb->f[0], ep->dev->nb, ep->nb);
ep->dev->state = Ddetach;
/* Release file system ref. for its endpoints */
for(i = 0; i < nelem(ep->dev->eps); i++)
putep(ep->dev->eps[i]);
break;
case CMdebugep:
if(strcmp(cb->f[1], "on") == 0)
ep->debug = 1;
else if(strcmp(cb->f[1], "off") == 0)
ep->debug = 0;
else
ep->debug = strtoul(cb->f[1], nil, 0);
print("usb: ep%d.%d debug %d\n",
ep->dev->nb, ep->nb, ep->debug);
break;
case CMname:
deprint("usb epctl %s %s\n", cb->f[0], cb->f[1]);
validname(cb->f[1], 0);
kstrdup(&ep->name, cb->f[1]);
break;
case CMtmout:
deprint("usb epctl %s\n", cb->f[0]);
if(ep->ttype == Tiso || ep->ttype == Tctl)
error("ctl ignored for this endpoint type");
ep->tmout = strtoul(cb->f[1], nil, 0);
if(ep->tmout != 0 && ep->tmout < Xfertmout)
ep->tmout = Xfertmout;
break;
case CMsampledelay:
if(ep->ttype != Tiso)
error("ctl ignored for this endpoint type");
ep->sampledelay = strtoul(cb->f[1], nil, 0);
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);
}
free(cb);
poperror();
return n;
}
static long
usbctl(void *a, long n)
{
Cmdtab *ct;
Cmdbuf *cb;
Ep *ep;
int i;
switch(ep->ttype){
case Tnone:
error(Enotconf);
case Tctl:
nr = rhubwrite(ep, a, n);
if(nr >= 0){
n = nr;
break;
}
/* else fall */
default:
ddeprint("\nusbwrite q %#x fid %d cnt %ld off %lld\n",q, c->fid, n, off);
ep->hp->epwrite(ep, a, n);
}
putep(ep);
poperror();
return n;
}
void
usbshutdown(void)
{
Hci *hp;
int i;
for(i = 0; i < Nhcis; i++){
hp = hcis[i];
if(hp == nil)
continue;
if(hp->shutdown == nil)
print("#u: no shutdown function for %s\n", hp->type);
else
hp->shutdown(hp);
}
}