shithub: riscv

ref: f6ac182f86bf0bcc777a76db50f94227ef6f7962
dir: /sys/src/9/port/devusb.c/

View raw version
/*
 * 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->hubnb);
	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 = 1;
		nep->hz = 0;
		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%lluX irq %d\n",
		epnb, hcitypes[cardno].type, (uvlong)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 void
isotiming(Ep *ep)
{
	long spp, max;

	switch(ep->dev->speed){
	case Fullspeed:
		max = 1024;
		break;
	case Highspeed:
		max = 3*1024;
		break;
	case Superspeed:
		max = 48*1024;
		break;
	default:
		error(Egreg);
		return;
	}

	if(ep->ntds <= 0)
		error(Egreg);
	max /= ep->ntds;
	if(max < ep->samplesz)
		error(Egreg);
	if(ep->pollival <= 0)
		error(Egreg);

	if(ep->hz <= 0){
		spp = ep->maxpkt / ep->samplesz;
		spp *= ep->ntds;
		if(ep->dev->speed == Fullspeed || ep->dev->speed == Lowspeed)
			ep->hz = (1000 * spp) / ep->pollival;
		else
			ep->hz = (8000 * spp) / ep->pollival;
		if(ep->hz <= 0)
			error(Egreg);
	}
	if(ep->dev->speed == Fullspeed || ep->dev->speed == Lowspeed)
		spp = (ep->hz * ep->pollival + 999) / 1000;
	else
		spp = (ep->hz * ep->pollival + 7999) / 8000;
	spp /= ep->ntds;
	ep->maxpkt = spp * ep->samplesz;
	if(ep->maxpkt > max){
		print("ep%d.%d: maxpkt %ld > %ld for %s, truncating\n",
			ep->dev->nb, ep->nb,
			ep->maxpkt, max, spname[ep->dev->speed]);
		ep->maxpkt = max;
	}
}

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->ttype == Tiso)
		isotiming(ep);
	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)
			break;
		/* else fall */
	default:
		ddeprint("\nusbread q %#x fid %d cnt %ld off %lld\n",q,c->fid,n,offset);
	Again:
		nr = ep->hp->epread(ep, a, n);
		if(nr == 0 && ep->ttype == Tiso){
			tsleep(&up->sleep, return0, nil, 2*ep->pollival);
			goto Again;
		}
		break;
	}
	poperror();
	putep(ep);
	return nr;
}

/*
 * 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->hubnb = d->nb;
		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;
		ep->hz = 0; /* recalculate */
		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;
		ep->hz = 0; /* recalculate */
		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;
		ep->hz = 0; /* recalculate */
		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;
		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 > 1000000000)
			error("hz not in [1:1000000000]");
		qlock(ep);
		ep->hz = l;
		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,
};