1591 lines
32 KiB
C
1591 lines
32 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "../port/error.h"
|
|
#include "../port/usb.h"
|
|
|
|
typedef struct Hcitype Hcitype;
|
|
|
|
enum
|
|
{
|
|
/* Qid numbers */
|
|
Qdir = 0, /* #u */
|
|
Qusbdir, /* #u/usb */
|
|
Qctl, /* #u/usb/ctl - control requests */
|
|
|
|
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) */
|
|
|
|
/* Hub feature selectors */
|
|
Rportenable = 1,
|
|
Rportreset = 4,
|
|
|
|
};
|
|
|
|
struct Hcitype
|
|
{
|
|
char* type;
|
|
int (*reset)(Hci*);
|
|
};
|
|
|
|
#define QID(q) ((int)(q).path)
|
|
|
|
static char Edetach[] = "device is detached";
|
|
static char Enotconf[] = "endpoint not configured";
|
|
char Estalled[] = "endpoint stalled";
|
|
|
|
static Cmdtab usbctls[] =
|
|
{
|
|
{CMdebug, "debug", 2},
|
|
{CMdump, "dump", 1},
|
|
};
|
|
|
|
static Cmdtab epctls[] =
|
|
{
|
|
{CMnew, "new", 4},
|
|
{CMnewdev, "newdev", 3},
|
|
{CMhub, "hub", 1},
|
|
{CMspeed, "speed", 2},
|
|
{CMmaxpkt, "maxpkt", 2},
|
|
{CMntds, "ntds", 2},
|
|
{CMpollival, "pollival", 2},
|
|
{CMsamplesz, "samplesz", 2},
|
|
{CMhz, "hz", 2},
|
|
{CMinfo, "info", 0},
|
|
{CMdetach, "detach", 1},
|
|
{CMaddress, "address", 1},
|
|
{CMdebugep, "debug", 2},
|
|
{CMclrhalt, "clrhalt", 1},
|
|
{CMname, "name", 2},
|
|
{CMtmout, "timeout", 2},
|
|
{CMsampledelay, "sampledelay", 2},
|
|
{CMpreset, "reset", 1},
|
|
{CMuframes, "uframes", 2},
|
|
};
|
|
|
|
static Dirtab usbdir[] =
|
|
{
|
|
"ctl", {Qctl}, 0, 0666,
|
|
};
|
|
|
|
char *usbmodename[] =
|
|
{
|
|
[OREAD] "r",
|
|
[OWRITE] "w",
|
|
[ORDWR] "rw",
|
|
};
|
|
|
|
static char *ttname[] =
|
|
{
|
|
[Tnone] "none",
|
|
[Tctl] "control",
|
|
[Tiso] "iso",
|
|
[Tintr] "interrupt",
|
|
[Tbulk] "bulk",
|
|
};
|
|
|
|
static char *spname[] =
|
|
{
|
|
[Superspeed] "super",
|
|
[Fullspeed] "full",
|
|
[Lowspeed] "low",
|
|
[Highspeed] "high",
|
|
[Nospeed] "no",
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
static int
|
|
name2speed(char *name)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(spname); i++)
|
|
if(strcmp(name, spname[i]) == 0)
|
|
return i;
|
|
return Nospeed;
|
|
}
|
|
|
|
static int
|
|
name2ttype(char *name)
|
|
{
|
|
int i;
|
|
|
|
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
|
|
name2mode(char *mode)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(usbmodename); i++)
|
|
if(strcmp(mode, usbmodename[i]) == 0)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
qid2epidx(int q)
|
|
{
|
|
q = (q-Qep0dir)/4;
|
|
if(q < 0 || q >= epmax || eps[q] == nil)
|
|
return -1;
|
|
return q;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static Ep*
|
|
epalloc(Hci *hp)
|
|
{
|
|
Ep *ep;
|
|
int i;
|
|
|
|
ep = smalloc(sizeof(Ep));
|
|
ep->ref = 1;
|
|
qlock(&epslck);
|
|
for(i = 0; i < Neps; i++)
|
|
if(eps[i] == nil)
|
|
break;
|
|
if(i == Neps){
|
|
qunlock(&epslck);
|
|
free(ep);
|
|
print("usb: bug: too few endpoints.\n");
|
|
return nil;
|
|
}
|
|
ep->idx = i;
|
|
if(epmax <= i)
|
|
epmax = i+1;
|
|
eps[i] = ep;
|
|
ep->hp = hp;
|
|
ep->maxpkt = 8;
|
|
ep->ntds = 1;
|
|
ep->uframes = ep->samplesz = ep->pollival = ep->hz = 0; /* make them void */
|
|
qunlock(&epslck);
|
|
return ep;
|
|
}
|
|
|
|
static Ep*
|
|
getep(int i)
|
|
{
|
|
Ep *ep;
|
|
|
|
if(i < 0 || i >= epmax || eps[i] == nil)
|
|
return nil;
|
|
qlock(&epslck);
|
|
ep = eps[i];
|
|
if(ep != nil)
|
|
incref(ep);
|
|
qunlock(&epslck);
|
|
return ep;
|
|
}
|
|
|
|
static void
|
|
putep(Ep *ep)
|
|
{
|
|
Udev *d;
|
|
|
|
if(ep == nil || decref(ep) > 0)
|
|
return;
|
|
d = ep->dev;
|
|
deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
|
|
qlock(&epslck);
|
|
eps[ep->idx] = nil;
|
|
if(ep->idx == epmax-1)
|
|
epmax--;
|
|
if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
|
|
usbidgen--;
|
|
qunlock(&epslck);
|
|
if(d != nil){
|
|
qlock(ep->ep0);
|
|
d->eps[ep->nb] = nil;
|
|
qunlock(ep->ep0);
|
|
}
|
|
if(ep->ep0 != ep){
|
|
putep(ep->ep0);
|
|
ep->ep0 = nil;
|
|
} else if(d != nil){
|
|
if(d->free != nil)
|
|
(*d->free)(d->aux);
|
|
free(d);
|
|
}
|
|
free(ep->info);
|
|
free(ep->name);
|
|
free(ep);
|
|
}
|
|
|
|
static void
|
|
dumpeps(void)
|
|
{
|
|
int i;
|
|
static char buf[512];
|
|
char *s;
|
|
char *e;
|
|
Ep *ep;
|
|
|
|
print("usb dump eps: epmax %d Neps %d (ref=1+ for dump):\n", epmax, Neps);
|
|
for(i = 0; i < epmax; i++){
|
|
s = buf;
|
|
e = buf+sizeof(buf);
|
|
ep = getep(i);
|
|
if(ep != nil){
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
s = seprint(s, e, "ep%d.%d ", ep->dev->nb, ep->nb);
|
|
seprintep(s, e, ep, 1);
|
|
print("%s", buf);
|
|
if(ep->hp->seprintep != nil){
|
|
ep->hp->seprintep(buf, e, ep);
|
|
print("%s", buf);
|
|
}
|
|
poperror();
|
|
putep(ep);
|
|
}
|
|
}
|
|
print("usb dump hcis:\n");
|
|
for(i = 0; i < Nhcis; i++)
|
|
if(hcis[i] != nil && hcis[i]->dump != nil)
|
|
hcis[i]->dump(hcis[i]);
|
|
}
|
|
|
|
static int
|
|
newusbid(Hci *)
|
|
{
|
|
int id;
|
|
|
|
qlock(&epslck);
|
|
id = ++usbidgen;
|
|
if(id >= 0x7F)
|
|
print("#u: too many device addresses; reuse them more\n");
|
|
qunlock(&epslck);
|
|
return id;
|
|
}
|
|
|
|
/*
|
|
* Create endpoint 0 for a new device
|
|
*/
|
|
static Ep*
|
|
newdev(Hci *hp, int ishub, int isroot)
|
|
{
|
|
Ep *ep;
|
|
Udev *d;
|
|
|
|
ep = epalloc(hp);
|
|
d = ep->dev = smalloc(sizeof(Udev));
|
|
d->nb = newusbid(hp);
|
|
d->addr = 0;
|
|
d->eps[0] = ep;
|
|
ep->nb = 0;
|
|
ep->toggle[0] = ep->toggle[1] = 0;
|
|
d->ishub = ishub;
|
|
d->isroot = isroot;
|
|
d->rootport = 0;
|
|
d->routestr = 0;
|
|
d->depth = -1;
|
|
d->speed = Fullspeed;
|
|
d->state = Dconfig; /* address not yet set */
|
|
ep->dev = d;
|
|
ep->ep0 = ep; /* no ref counted here */
|
|
ep->ttype = Tctl;
|
|
ep->tmout = Xfertmout;
|
|
ep->mode = ORDWR;
|
|
dprint("newdev %#p ep%d.%d %#p\n", d, d->nb, ep->nb, ep);
|
|
return ep;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
d = ep->dev;
|
|
if(d->eps[i] != nil)
|
|
error("endpoint already in use");
|
|
nep = epalloc(ep->hp);
|
|
incref(ep);
|
|
d->eps[i] = nep;
|
|
nep->nb = i;
|
|
nep->toggle[0] = nep->toggle[1] = 0;
|
|
nep->ep0 = ep;
|
|
nep->dev = ep->dev;
|
|
nep->mode = mode;
|
|
nep->ttype = tt;
|
|
nep->debug = ep->debug;
|
|
/* set defaults */
|
|
switch(tt){
|
|
case Tctl:
|
|
nep->tmout = Xfertmout;
|
|
break;
|
|
case Tintr:
|
|
nep->pollival = 10;
|
|
break;
|
|
case Tiso:
|
|
nep->tmout = Xfertmout;
|
|
nep->pollival = 10;
|
|
nep->samplesz = 4;
|
|
nep->hz = 44100;
|
|
nep->uframes = 0;
|
|
break;
|
|
}
|
|
deprint("newdevep ep%d.%d %#p\n", d->nb, nep->nb, nep);
|
|
return ep;
|
|
}
|
|
|
|
static int
|
|
epdataperm(int mode)
|
|
{
|
|
|
|
switch(mode){
|
|
case OREAD:
|
|
return 0440|DMEXCL;
|
|
break;
|
|
case OWRITE:
|
|
return 0220|DMEXCL;
|
|
break;
|
|
default:
|
|
return 0660|DMEXCL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
|
|
{
|
|
Qid q;
|
|
Dirtab *dir;
|
|
int perm;
|
|
char *se;
|
|
Ep *ep;
|
|
int nb;
|
|
int mode;
|
|
|
|
if(0)ddprint("usbgen q %#x s %d...", QID(c->qid), s);
|
|
if(s == DEVDOTDOT){
|
|
if(QID(c->qid) <= Qusbdir){
|
|
mkqid(&q, Qdir, 0, QTDIR);
|
|
devdir(c, q, "#u", 0, eve, 0555, dp);
|
|
}else{
|
|
mkqid(&q, Qusbdir, 0, QTDIR);
|
|
devdir(c, q, "usb", 0, eve, 0555, dp);
|
|
}
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
}
|
|
|
|
switch(QID(c->qid)){
|
|
case Qdir: /* list #u */
|
|
if(s == 0){
|
|
mkqid(&q, Qusbdir, 0, QTDIR);
|
|
devdir(c, q, "usb", 0, eve, 0555, dp);
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
}
|
|
s--;
|
|
if(s < 0 || s >= epmax)
|
|
goto Fail;
|
|
ep = getep(s);
|
|
if(ep == nil || ep->name == nil){
|
|
if(ep != nil)
|
|
putep(ep);
|
|
if(0)ddprint("skip\n");
|
|
return 0;
|
|
}
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
mkqid(&q, Qep0io+s*4, 0, QTFILE);
|
|
devdir(c, q, ep->name, 0, eve, epdataperm(ep->mode), dp);
|
|
putep(ep);
|
|
poperror();
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
|
|
case Qusbdir: /* list #u/usb */
|
|
Usbdir:
|
|
if(s < nelem(usbdir)){
|
|
dir = &usbdir[s];
|
|
mkqid(&q, dir->qid.path, 0, QTFILE);
|
|
devdir(c, q, dir->name, dir->length, eve, dir->perm, dp);
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
}
|
|
s -= nelem(usbdir);
|
|
if(s < 0 || s >= epmax)
|
|
goto Fail;
|
|
ep = getep(s);
|
|
if(ep == nil){
|
|
if(0)ddprint("skip\n");
|
|
return 0;
|
|
}
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
se = up->genbuf+sizeof(up->genbuf);
|
|
seprint(up->genbuf, se, "ep%d.%d", ep->dev->nb, ep->nb);
|
|
mkqid(&q, Qep0dir+4*s, 0, QTDIR);
|
|
putep(ep);
|
|
poperror();
|
|
devdir(c, q, up->genbuf, 0, eve, 0775, dp);
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
|
|
case Qctl:
|
|
s = 0;
|
|
goto Usbdir;
|
|
|
|
default: /* list #u/usb/epN.M */
|
|
nb = qid2epidx(QID(c->qid));
|
|
ep = getep(nb);
|
|
if(ep == nil)
|
|
goto Fail;
|
|
mode = ep->mode;
|
|
putep(ep);
|
|
if(isqtype(QID(c->qid), Qepdir)){
|
|
Epdir:
|
|
switch(s){
|
|
case 0:
|
|
mkqid(&q, Qep0io+nb*4, 0, QTFILE);
|
|
perm = epdataperm(mode);
|
|
devdir(c, q, "data", 0, eve, perm, dp);
|
|
break;
|
|
case 1:
|
|
mkqid(&q, Qep0ctl+nb*4, 0, QTFILE);
|
|
devdir(c, q, "ctl", 0, eve, 0664, dp);
|
|
break;
|
|
default:
|
|
goto Fail;
|
|
}
|
|
}else if(isqtype(QID(c->qid), Qepctl)){
|
|
s = 1;
|
|
goto Epdir;
|
|
}else{
|
|
s = 0;
|
|
goto Epdir;
|
|
}
|
|
if(0)ddprint("ok\n");
|
|
return 1;
|
|
}
|
|
Fail:
|
|
if(0)ddprint("fail\n");
|
|
return -1;
|
|
}
|
|
|
|
static Hci*
|
|
hciprobe(int cardno, int ctlrno)
|
|
{
|
|
Hci *hp;
|
|
char *type;
|
|
static int epnb = 1; /* guess the endpoint nb. for the controller */
|
|
|
|
ddprint("hciprobe %d %d\n", cardno, ctlrno);
|
|
hp = smalloc(sizeof(Hci));
|
|
hp->ctlrno = ctlrno;
|
|
hp->tbdf = BUSUNKNOWN;
|
|
|
|
if(cardno < 0){
|
|
if(isaconfig("usb", ctlrno, hp) == 0){
|
|
free(hp);
|
|
return nil;
|
|
}
|
|
for(cardno = 0; cardno < Nhcis; cardno++){
|
|
if(hcitypes[cardno].type == nil)
|
|
break;
|
|
type = hp->type;
|
|
if(type==nil || *type==0)
|
|
type = "uhci";
|
|
if(cistrcmp(hcitypes[cardno].type, type) == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(cardno >= Nhcis || hcitypes[cardno].type == nil){
|
|
free(hp);
|
|
return nil;
|
|
}
|
|
dprint("%s...", hcitypes[cardno].type);
|
|
if(hcitypes[cardno].reset(hp) < 0){
|
|
free(hp);
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if(getconf("*nousbprobe"))
|
|
return;
|
|
dprint("usbreset\n");
|
|
|
|
for(ctlrno = 0; ctlrno < Nhcis; ctlrno++)
|
|
if((hp = hciprobe(-1, ctlrno)) != nil)
|
|
hcis[ctlrno] = hp;
|
|
cardno = ctlrno = 0;
|
|
while(cardno < Nhcis && ctlrno < Nhcis && hcitypes[cardno].type != nil)
|
|
if(hcis[ctlrno] != nil)
|
|
ctlrno++;
|
|
else{
|
|
hp = hciprobe(cardno, ctlrno);
|
|
if(hp == nil)
|
|
cardno++;
|
|
hcis[ctlrno++] = hp;
|
|
}
|
|
if(hcis[Nhcis-1] != nil)
|
|
print("usbreset: bug: Nhcis (%d) too small\n", Nhcis);
|
|
}
|
|
|
|
static int
|
|
numbits(uint n)
|
|
{
|
|
int c = 0;
|
|
while(n != 0){
|
|
c++;
|
|
n = (n-1) & n;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static void
|
|
usbinit(void)
|
|
{
|
|
Hci *hp;
|
|
int ctlrno;
|
|
Ep *d;
|
|
char info[40];
|
|
|
|
dprint("usbinit\n");
|
|
for(ctlrno = 0; ctlrno < Nhcis; ctlrno++){
|
|
hp = hcis[ctlrno];
|
|
if(hp != nil){
|
|
int n;
|
|
|
|
if(hp->init != nil){
|
|
if(waserror()){
|
|
print("usbinit: %s: %s\n", hp->type, up->errstr);
|
|
continue;
|
|
}
|
|
hp->init(hp);
|
|
poperror();
|
|
}
|
|
|
|
hp->superspeed &= (1<<hp->nports)-1;
|
|
n = hp->nports - numbits(hp->superspeed);
|
|
if(n > 0){
|
|
d = newdev(hp, 1, 1); /* new LS/FS/HS root hub */
|
|
d->maxpkt = 64;
|
|
if(hp->highspeed != 0)
|
|
d->dev->speed = Highspeed;
|
|
d->dev->state = Denabled; /* although addr == 0 */
|
|
snprint(info, sizeof(info), "roothub ports %d", n);
|
|
kstrdup(&d->info, info);
|
|
}
|
|
n = numbits(hp->superspeed);
|
|
if(n > 0){
|
|
d = newdev(hp, 1, 1); /* new SS root hub */
|
|
d->maxpkt = 512;
|
|
d->dev->speed = Superspeed;
|
|
d->dev->state = Denabled; /* although addr == 0 */
|
|
snprint(info, sizeof(info), "roothub ports %d", n);
|
|
kstrdup(&d->info, info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static Chan*
|
|
usbattach(char *spec)
|
|
{
|
|
return devattach(L'u', spec);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* µ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;
|
|
|
|
mode = openmode(omode);
|
|
q = QID(c->qid);
|
|
|
|
if(q >= Qep0dir && qid2epidx(q) < 0)
|
|
error(Eio);
|
|
if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
|
|
return devopen(c, omode, nil, 0, usbgen);
|
|
|
|
ep = getep(qid2epidx(q));
|
|
if(ep == nil)
|
|
error(Eio);
|
|
deprint("usbopen q %#x fid %d omode %d\n", q, c->fid, mode);
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
qlock(ep);
|
|
if(ep->inuse){
|
|
qunlock(ep);
|
|
error(Einuse);
|
|
}
|
|
ep->inuse = 1;
|
|
qunlock(ep);
|
|
if(waserror()){
|
|
ep->inuse = 0;
|
|
nexterror();
|
|
}
|
|
if(mode != OREAD && ep->mode == OREAD)
|
|
error(Eperm);
|
|
if(mode != OWRITE && ep->mode == OWRITE)
|
|
error(Eperm);
|
|
if(ep->ttype == Tnone)
|
|
error(Enotconf);
|
|
ep->clrhalt = 0;
|
|
ep->rhrepl = -1;
|
|
if(ep->load == 0 && ep->dev->speed != Superspeed)
|
|
ep->load = usbload(ep->dev->speed, ep->maxpkt);
|
|
ep->hp->epopen(ep);
|
|
|
|
poperror(); /* ep->inuse */
|
|
poperror(); /* don't putep(): ref kept for fid using the ep. */
|
|
|
|
c->mode = mode;
|
|
c->flag |= COPEN;
|
|
c->offset = 0;
|
|
c->aux = nil; /* paranoia */
|
|
return c;
|
|
}
|
|
|
|
static void
|
|
epclose(Ep *ep)
|
|
{
|
|
qlock(ep);
|
|
if(waserror()){
|
|
qunlock(ep);
|
|
nexterror();
|
|
}
|
|
if(ep->inuse){
|
|
ep->hp->epclose(ep);
|
|
ep->inuse = 0;
|
|
}
|
|
qunlock(ep);
|
|
poperror();
|
|
}
|
|
|
|
static void
|
|
usbclose(Chan *c)
|
|
{
|
|
int q;
|
|
Ep *ep;
|
|
|
|
q = QID(c->qid);
|
|
if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
|
|
return;
|
|
|
|
ep = getep(qid2epidx(q));
|
|
if(ep == nil)
|
|
return;
|
|
deprint("usbclose q %#x fid %d ref %ld\n", q, c->fid, ep->ref);
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
if(c->flag & COPEN){
|
|
free(c->aux);
|
|
c->aux = nil;
|
|
epclose(ep);
|
|
putep(ep); /* release ref kept since usbopen */
|
|
c->flag &= ~COPEN;
|
|
}
|
|
poperror();
|
|
putep(ep);
|
|
}
|
|
|
|
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];
|
|
|
|
if(ep->dev->isroot == 0 || ep->nb != 0 || n < 2 || ep->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;
|
|
|
|
ep->rhrepl = -1;
|
|
|
|
if(n > sizeof(b))
|
|
n = sizeof(b);
|
|
memmove(a, b, n);
|
|
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
rootport(Ep *ep, int port)
|
|
{
|
|
Hci *hp;
|
|
Udev *hub;
|
|
uint mask;
|
|
int rootport;
|
|
|
|
hp = ep->hp;
|
|
hub = ep->dev;
|
|
if(!hub->isroot)
|
|
return hub->rootport;
|
|
|
|
mask = hp->superspeed;
|
|
if(hub->speed != Superspeed)
|
|
mask = (1<<hp->nports)-1 & ~mask;
|
|
|
|
for(rootport = 1; mask != 0; rootport++){
|
|
if(mask & 1){
|
|
if(--port == 0)
|
|
return rootport;
|
|
}
|
|
mask >>= 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long
|
|
rhubwrite(Ep *ep, void *a, long n)
|
|
{
|
|
uchar *s;
|
|
int cmd;
|
|
int feature;
|
|
int port;
|
|
Hci *hp;
|
|
|
|
if(ep->dev == nil || ep->dev->isroot == 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;
|
|
cmd = s[Rreq];
|
|
feature = GET2(s+Rvalue);
|
|
port = rootport(ep, GET2(s+Rindex));
|
|
if(port == 0)
|
|
error("bad hub port number");
|
|
switch(feature){
|
|
case Rportenable:
|
|
ep->rhrepl = hp->portenable(hp, port, cmd == Rsetfeature);
|
|
break;
|
|
case Rportreset:
|
|
ep->rhrepl = hp->portreset(hp, port, cmd == Rsetfeature);
|
|
break;
|
|
case Rgetstatus:
|
|
ep->rhrepl = hp->portstatus(hp, port);
|
|
break;
|
|
default:
|
|
ep->rhrepl = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static long
|
|
usbread(Chan *c, void *a, long n, vlong offset)
|
|
{
|
|
int q;
|
|
Ep *ep;
|
|
int nr;
|
|
|
|
q = QID(c->qid);
|
|
|
|
if(c->qid.type == QTDIR)
|
|
return devdirread(c, a, n, nil, 0, usbgen);
|
|
|
|
if(q == Qctl || isqtype(q, Qepctl))
|
|
return ctlread(c, a, n, offset);
|
|
|
|
ep = getep(qid2epidx(q));
|
|
if(ep == nil)
|
|
error(Eio);
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
if(ep->dev->state == Ddetach)
|
|
error(Edetach);
|
|
if(ep->mode == OWRITE || ep->inuse == 0)
|
|
error(Ebadusefd);
|
|
switch(ep->ttype){
|
|
case Tnone:
|
|
error(Enotconf);
|
|
case Tctl:
|
|
nr = rhubread(ep, a, n);
|
|
if(nr >= 0){
|
|
n = nr;
|
|
break;
|
|
}
|
|
/* else fall */
|
|
default:
|
|
ddeprint("\nusbread q %#x fid %d cnt %ld off %lld\n",q,c->fid,n,offset);
|
|
n = ep->hp->epread(ep, a, n);
|
|
break;
|
|
}
|
|
poperror();
|
|
putep(ep);
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
setmaxpkt(Ep *ep, char* s)
|
|
{
|
|
long spp, max; /* samples per packet */
|
|
|
|
if(ep->dev->speed == Fullspeed)
|
|
spp = (ep->hz * ep->pollival + 999) / 1000;
|
|
else
|
|
spp = (ep->hz * ep->pollival * ep->ntds + 7999) / 8000;
|
|
ep->maxpkt = spp * ep->samplesz;
|
|
deprint("usb: %s: setmaxpkt: hz %ld poll %ld"
|
|
" ntds %d %s speed -> spp %ld maxpkt %ld\n", s,
|
|
ep->hz, ep->pollival, ep->ntds, spname[ep->dev->speed],
|
|
spp, ep->maxpkt);
|
|
switch(ep->dev->speed){
|
|
case Fullspeed:
|
|
max = 1024;
|
|
break;
|
|
case Highspeed:
|
|
max = 3*1024;
|
|
break;
|
|
case Superspeed:
|
|
max = 48*1024;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
if(ep->maxpkt*ep->ntds > max){
|
|
print("usb: %s: maxpkt %ld > %ld for %s, truncating\n",
|
|
s, ep->maxpkt*ep->ntds, max, spname[ep->dev->speed]);
|
|
ep->maxpkt = max/ep->ntds;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
cb = parsecmd(a, n);
|
|
if(waserror()){
|
|
free(cb);
|
|
nexterror();
|
|
}
|
|
ct = lookupcmd(cb, usbctls, nelem(usbctls));
|
|
dprint("usb ctl %s\n", cb->f[0]);
|
|
switch(ct->index){
|
|
case CMdebug:
|
|
if(strcmp(cb->f[1], "on") == 0)
|
|
debug = 1;
|
|
else if(strcmp(cb->f[1], "off") == 0)
|
|
debug = 0;
|
|
else
|
|
debug = strtol(cb->f[1], nil, 0);
|
|
print("usb: debug %d\n", debug);
|
|
for(i = 0; i < epmax; i++)
|
|
if((ep = getep(i)) != nil){
|
|
if(ep->hp->debug != nil)
|
|
ep->hp->debug(ep->hp, debug);
|
|
putep(ep);
|
|
}
|
|
break;
|
|
case CMdump:
|
|
dumpeps();
|
|
break;
|
|
}
|
|
free(cb);
|
|
poperror();
|
|
return n;
|
|
}
|
|
|
|
static long
|
|
ctlwrite(Chan *c, void *a, long n)
|
|
{
|
|
int q;
|
|
Ep *ep;
|
|
|
|
q = QID(c->qid);
|
|
if(q == Qctl)
|
|
return usbctl(a, n);
|
|
|
|
ep = getep(qid2epidx(q));
|
|
if(ep == nil)
|
|
error(Eio);
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
if(ep->dev->state == Ddetach)
|
|
error(Edetach);
|
|
if(isqtype(q, Qepctl) && c->aux != nil){
|
|
/* Be sure we don't keep a cloned ep name */
|
|
free(c->aux);
|
|
c->aux = nil;
|
|
error("read, not write, expected");
|
|
}
|
|
n = epctl(ep, c, a, n);
|
|
putep(ep);
|
|
poperror();
|
|
return n;
|
|
}
|
|
|
|
static long
|
|
usbwrite(Chan *c, void *a, long n, vlong off)
|
|
{
|
|
int nr, q;
|
|
Ep *ep;
|
|
|
|
if(c->qid.type == QTDIR)
|
|
error(Eisdir);
|
|
|
|
q = QID(c->qid);
|
|
|
|
if(q == Qctl || isqtype(q, Qepctl))
|
|
return ctlwrite(c, a, n);
|
|
|
|
ep = getep(qid2epidx(q));
|
|
if(ep == nil)
|
|
error(Eio);
|
|
if(waserror()){
|
|
putep(ep);
|
|
nexterror();
|
|
}
|
|
if(ep->dev->state == Ddetach)
|
|
error(Edetach);
|
|
if(ep->mode == OREAD || ep->inuse == 0)
|
|
error(Ebadusefd);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
Dev usbdevtab = {
|
|
L'u',
|
|
"usb",
|
|
|
|
usbreset,
|
|
usbinit,
|
|
usbshutdown,
|
|
usbattach,
|
|
usbwalk,
|
|
usbstat,
|
|
usbopen,
|
|
devcreate,
|
|
usbclose,
|
|
usbread,
|
|
devbread,
|
|
usbwrite,
|
|
devbwrite,
|
|
devremove,
|
|
devwstat,
|
|
};
|