~kris/9p

9hist

219e80a1a54784b13b5165d8887a88c3be595a9b — David du Colombier 25 years ago 3c31d13
Plan 9 from Bell Labs 2001-06-24
2 files changed, 3 insertions(+), 1086 deletions(-)

M pc/devusb.c
M port/qio.c
M pc/devusb.c => pc/devusb.c +1 -1085
@@ 879,1088 879,4 @@ schedendpt(Endpt *e)
	QH *qh;
	int q;

	if(!e->periodic || e->sched >= 0)
		return 0;
	ub = &ubus;
	if(e->epq == nil){
		e->epq = allocqh(ub);
		if(e->epq == nil)
			return -1;
	}
	qlock(ub->tree);
	q = pickschedq(ub->tree, e->pollms, e->bw, ~0);	/* TO DO: bus bandwidth limit */
	if(q < 0)
		return -1;
	ub->tree->bw[q] += e->bw;
	qh = &ub->tree->root[q];
	e->sched = q;
	e->epq->head = qh->entries;
	e->epq->entries = Terminate;
	qh->entries = PADDR(e->epq) | IsQH;
	qunlock(ub->tree);
	return 0;
}

static void
unschedendpt(Endpt *e)
{
	Ctlr *ub;
	ulong p;
	QH *qh;
	int q;

	ub = &ubus;
	if(!e->periodic || (q = e->sched) < 0)
		return;
	p = PADDR(e->epq) | IsQH;
	qlock(ub->tree);
	ub->tree->bw[q] -= e->bw;
	qh = &ub->tree->root[q];
	for(; qh->entries != p; qh = QFOL(qh->entries))
		if(qh->entries & Terminate || (qh->entries & IsQH) == 0){
			qunlock(ub->tree);
			panic("usb: unschedendpt");
		}
	qh->entries = e->epq->head;
	qunlock(ub->tree);
	e->epq->head = Terminate;
}

static Endpt *
devendpt(Udev *d, int id, int add)
{
	Endpt *e, **p;
	Ctlr *ub;

	ub = &ubus;
	p = &d->ep[id&0xF];
	lock(d);
	if((e = *p) != nil){
		incref(e);
		unlock(d);
		return e;
	}
	unlock(d);
	if(!add)
		return nil;
	e = mallocz(sizeof(*e), 1);
	e->ref = 1;
	e->x = id&0xF;
	e->id = id;
	e->periodic = 0;
	e->sched = -1;
	e->maxpkt = 8;
	e->pollms = 0;
	e->bw = 0;
	e->nbuf = 1;
	e->dev = d;
	e->active = 0;
	e->epq = allocqh(ub);
	if(e->epq == nil)
		panic("devendpt");

	lock(d);
	if(*p != nil){
		incref(*p);
		unlock(d);
		free(e);
		return *p;
	}
	*p = e;
	unlock(d);
	e->rq = qopen(8*1024, 0, nil, e);
	e->wq = qopen(8*1024, 0, nil, e);
	return e;
}

static void
freept(Endpt *e)
{
	if(e != nil && decref(e) == 0){
		XPRINT("freept(%d,%d)\n", e->dev->x, e->x);
		unschedendpt(e);
		e->dev->ep[e->x] = nil;
		eptdeactivate(e);
		if(e->epq != nil)
			freeqh(&ubus, e->epq);
		free(e);
	}
}

static void
usbdevreset(Udev *d)
{
	d->state = Disabled;
	if(Class(d->csp) == Hubclass)
		for(d = d->ports; d != nil; d = d->next)
			usbdevreset(d);
}

static void
freedev(Udev *d)
{
	int i;

	if(d != nil && decref(d) == 0){
		for(i=0; i<nelem(d->ep); i++)
			freept(d->ep[i]);
		if(d->x >= 0)
			usbdev[d->x] = nil;
		free(d);
	}
}

static void
hubportreset(Udev *h, int p)
{
	USED(h, p);
	/* reset state of each attached device? */
}

static	int	ioports[] = {-1, Portsc0, Portsc1};

static void
portreset(int port)
{
	Ctlr *ub;
	int i, p;

	/* should check that device not being configured on other port? */
	p = ioports[port];
	ub = &ubus;
	qlock(&ub->resetl);
	if(waserror()){
		qunlock(&ub->resetl);
		nexterror();
	}
	XPRINT("r: %x\n", IN(p));
	ilock(ub);
	OUT(p, PortReset);
	delay(12);	/* BUG */
	XPRINT("r2: %x\n", IN(p));
	OUT(p, IN(p) & ~PortReset);
	XPRINT("r3: %x\n", IN(p));
	OUT(p, IN(p) | PortEnable);
	microdelay(64);
	for(i=0; i<1000 && (IN(p) & PortEnable) == 0; i++)
		;
	XPRINT("r': %x %d\n", IN(p), i);
	OUT(p, (IN(p) & ~PortReset)|PortEnable);
	iunlock(ub);
	hubportreset(nil, port);
	poperror();
	qunlock(&ub->resetl);
}

static void
portenable(int port, int on)
{
	Ctlr *ub;
	int w, p;

	/* should check that device not being configured on other port? */
	p = ioports[port];
	ub = &ubus;
	qlock(&ub->resetl);
	if(waserror()){
		qunlock(&ub->resetl);
		nexterror();
	}
	ilock(ub);
	w = IN(p);
	if(on)
		w |= PortEnable;
	else
		w &= ~PortEnable;
	OUT(p, w);
	microdelay(64);
	iunlock(ub);
	XPRINT("e: %x\n", IN(p));
	if(!on)
		hubportreset(nil, port);
	poperror();
	qunlock(&ub->resetl);
}

static int
portinfo(Ctlr *ub, int *p0, int *p1)
{
	int m, v;

	ilock(ub);
	m = 0;
	if((v = IN(Portsc0)) & PortChange){
		OUT(Portsc0, v);
		m |= 1<<0;
	}
	*p0 = v;
	if((v = IN(Portsc1)) & PortChange){
		OUT(Portsc1, v);
		m |= 1<<1;
	}
	*p1 = v;
	iunlock(ub);
	return m;
}

static void
interrupt(Ureg*, void *a)
{
	Ctlr *ub;
	Endpt *e;
	int s;
	QH *q;

	ub = a;
	s = IN(Status);
	XPRINT("usbint: #%x f%d\n", s, IN(Frnum));
	if (s & 0x1a) {
		XPRINT("cmd #%x sofmod #%x\n", IN(Cmd), inb(ub->io+SOFMod));
		XPRINT("sc0 #%x sc1 #%x\n", IN(Portsc0), IN(Portsc1));
	}
	OUT(Status, s);

	XPRINT("cleanq(ub->ctlq, 0, 0)\n");
	cleanq(ub->ctlq, 0, 0);
	XPRINT("cleanq(ub->bulkq, 0, Vf)\n");
	cleanq(ub->bulkq, 0, Vf);
	XPRINT("clean recvq\n");
	for (q = ub->recvq->next; q; q = q->hlink) {
		XPRINT("cleanq(q, 0, Vf)\n");
		cleanq(q, 0, Vf);
	}
	ilock(&activends);
	for(e = activends.f; e != nil; e = e->activef)
		if(e->epq != nil) {
			XPRINT("cleanq(e->epq, 0, 0)\n");
			cleanq(e->epq, 0, 0);
		}
	iunlock(&activends);
}

enum
{
	Qtopdir = 0,
	Q2nd,
	Qbusctl,
	Qnew,
	Qport,
	Q3rd,
	Qctl,
	Qsetup,
	Qdebug,
	Qstatus,
	Qep0,
	/* other endpoint files */
};

/*
 * Qid path is:
 *	 8 bits of file type (qids above)
 *	10 bits of slot number +1; 0 means not attached to device
 */
#define	QSHIFT	8	/* location in qid of device # */
#define	QMASK	((1<<QSHIFT)-1)

#define	QID(q)		((q).path&QMASK)
#define	DEVPATH(q)	(((q)&~CHDIR)>>QSHIFT)

static Dirtab usbdir2[] = {
	"new",		{Qnew},		0,	0666,
	"ctl",		{Qbusctl},	0,	0666,
	"port",		{Qport},	0,	0444,
};

static Dirtab usbdir3[]={
	"ctl",		{Qctl},		0,	0666,
	"setup",	{Qsetup},	0,	0666,
	"status",	{Qstatus},	0,	0444,
	"debug",	{Qdebug},	1,	0666,
	/* epNdata names are generated on demand */
};

static Udev *
usbdeviceofpath(ulong path)
{
	int s;

	s = DEVPATH(path);
	if(s == 0)
		return nil;
	return usbdev[s-1];
}

static Udev *
usbdevice(Chan *c)
{
	Udev *d;

	d = usbdeviceofpath(c->qid.path);
	if(d == nil || d->id != c->qid.vers || d->state == Disabled)
		error(Ehungup);
	return d;
}

static Udev *
usbnewdevice(void)
{
	Udev *d;
	Endpt *e;
	int i;

	d = nil;
	qlock(&usbstate);
	if(waserror()){
		qunlock(&usbstate);
		nexterror();
	}
	for(i=0; i<nelem(usbdev); i++)
		if(usbdev[i] == nil){
			ubus.idgen++;
			d = mallocz(sizeof(*d), 1);
			d->ref = 1;
			d->x = i;
			d->id = (ubus.idgen << 8) | i;
			d->state = Enabled;
			e = devendpt(d, 0, 1);	/* always provide control endpoint 0 */
			e->mode = ORDWR;
			e->periodic = 0;
			e->sched = -1;
			usbdev[i] = d;
			break;
		}
	poperror();
	qunlock(&usbstate);
	return d;
}

static void
usbreset(void)
{
	Pcidev *cfg;
	int i;
	ulong port;
	QTree *qt;
	TD *t;
	Ctlr *ub;

	ub = &ubus;
	memset(&cfg, 0, sizeof(cfg));
	cfg = pcimatch(0, 0x8086, 0x7112);	/* Intel chipset PIIX 4*/
	if(cfg == nil) {
		cfg = pcimatch(0, 0x1106, 0x0586);	/* Via chipset */
		if(cfg == nil) {
			DPRINT("No USB device found\n");
			return;
		}
	}
	port = cfg->mem[4].bar & ~0x0F;
	if (port == 0) {
		print("usb: failed to map registers\n");
		return;
	}

	DPRINT("USB: %x/%x port 0x%lux size 0x%x irq %d\n",
		cfg->vid, cfg->did, port, cfg->mem[4].size, cfg->intl);

	i = inb(port+SOFMod);
	if(0){
		OUT(Cmd, 4);	/* global reset */
		delay(15);
		OUT(Cmd, 0);	/* end reset */
		delay(4);
	}
	outb(port+SOFMod, i);
	/*
	 * Interrupt handler.
	 * Bail out if no IRQ assigned by the BIOS.
	 */
	if(cfg->intl == 0xFF || cfg->intl == 0)
		return;
	intrenable(cfg->intl, interrupt, ub, cfg->tbdf, "usb");

	ub->io = port;
	ub->tdpool = xspanalloc(128*sizeof(TD), 16, 0);
	for(i=128; --i>=0;){
		ub->tdpool[i].next = ub->freetd;
		ub->freetd = &ub->tdpool[i];
	}
	ub->qhpool = xspanalloc(32*sizeof(QH), 16, 0);
	for(i=32; --i>=0;){
		ub->qhpool[i].next = ub->freeqh;
		ub->freeqh = &ub->qhpool[i];
	}

	/*
	 * the root of the periodic (interrupt & isochronous) scheduling tree
	 * points to the control queue and the bandwidth sop for bulk traffic.
	 * this is looped following the instructions in PIIX4 errata 29773804.pdf:
	 * a QH links to a looped but inactive TD as its sole entry,
	 * with its head entry leading on to the bulk traffic, the last QH of which
	 * links back to the empty QH.
	 */
	ub->ctlq = allocqh(ub);
	ub->bwsop = allocqh(ub);
	ub->bulkq = allocqh(ub);
	ub->recvq = allocqh(ub);
	t = alloctd(ub);	/* inactive TD, looped */
	t->link = PADDR(t);
	ub->bwsop->entries = PADDR(t);
	ub->ctlq->head = PADDR(ub->bwsop) | IsQH;
	ub->bwsop->head = PADDR(ub->bulkq) | IsQH;
	ub->bulkq->head = PADDR(ub->recvq) | IsQH;
	ub->recvq->head = PADDR(ub->bwsop) | IsQH;	/* loop back */
	XPRINT("usbcmd\t0x%.4x\nusbsts\t0x%.4x\nusbintr\t0x%.4x\nfrnum\t0x%.2x\n",
		IN(Cmd), IN(Status), IN(Usbintr), inb(port+Frnum));
	XPRINT("frbaseadd\t0x%.4x\nsofmod\t0x%x\nportsc1\t0x%.4x\nportsc2\t0x%.4x\n",
		IN(Flbaseadd), inb(port+SOFMod), IN(Portsc0), IN(Portsc1));
	OUT(Cmd, 0);	/* stop */
	ub->frames = xspanalloc(FRAMESIZE, FRAMESIZE, 0);
	qt = mkqhtree(ub->frames, NFRAME, 32);
	if(qt == nil){
		print("usb: can't allocate scheduling tree\n");
		ub->io = 0;
		return;
	}
	qt->root->head = PADDR(ub->ctlq) | IsQH;
	ub->tree = qt;
	XPRINT("usb tree: nel=%d depth=%d\n", qt->nel, qt->depth);

	outl(port+Flbaseadd, PADDR(ub->frames));
	OUT(Frnum, 0);
	OUT(Usbintr, 0xF);	/* enable all interrupts */
	XPRINT("cmd 0x%x sofmod 0x%x\n", IN(Cmd), inb(port+SOFMod));
	XPRINT("sc0 0x%x sc1 0x%x\n", IN(Portsc0), IN(Portsc1));
}

void
usbinit(void)
{
	Udev *d;

	if(ubus.io != 0 && usbdev[0] == nil){
		d = usbnewdevice();	/* reserve device 0 for configuration */
		incref(d);
		d->state = Attached;
	}
}

Chan *
usbattach(char *spec)
{
	Ctlr *ub;

	ub = &ubus;
	if(ub->io == 0)
		error(Enodev);
	if((IN(Cmd)&1)==0 || *spec)
		OUT(Cmd, 1);	/* run */
//	pprint("at: c=%x s=%x c0=%x\n", IN(Cmd), IN(Status), IN(Portsc0));
	return devattach('U', spec);
}

Chan *
usbclone(Chan *c, Chan *nc)
{
	return devclone(c, nc);
}

static int
usbgen(Chan *c, Dirtab*, int, int s, Dir *dp)
{
	int t;
	Qid q;
	ulong path;
	Udev *d;
	Dirtab *tab;
	Endpt *e;
	char buf[NAMELEN];

	/*
	 * Top level directory contains the name of the device.
	 */
	if(c->qid.path == CHDIR){
		if(s == DEVDOTDOT){
			devdir(c, (Qid){CHDIR, 0}, "#U", 0, eve, 0555, dp);
			return 1;
		}
		if(s == 0){
			devdir(c, (Qid){CHDIR|Q2nd, 0}, "usb", 0, eve, 0555, dp);
			return 1;
		}
		return -1;
	}

	/*
	 * Second level contains "new" plus all the clients.
	 */
	t = QID(c->qid);
	if(t < Q3rd){
		if(s == DEVDOTDOT){
			devdir(c, (Qid){CHDIR, 0}, "#U", 0, eve, 0555, dp);
			return 1;
		}
		if(s < nelem(usbdir2)){
			tab = &usbdir2[s];
			devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
			return 1;
		}
		if((s -= nelem(usbdir2)) >= 0 && s < nelem(usbdev)){
			d = usbdev[s];
			if(d == nil)
				return 0;
			sprint(buf, "%d", s);
			q = (Qid){CHDIR|((s+1)<<QSHIFT)|Q3rd, d->id};
			devdir(c, q, buf, 0, eve, 0555, dp);
			return 1;
		}
		return -1;
	}

	/*
	 * Third level.
	 */
	path = c->qid.path&~(CHDIR|QMASK);	/* slot component */
	q.vers = c->qid.vers;
	if(s == DEVDOTDOT){
		devdir(c, (Qid){CHDIR|Q2nd, 0}, "usb", 0, eve, 0555, dp);
		return 1;
	}
	if(s < nelem(usbdir3)){
		Dirtab *tab = &usbdir3[s];
		q.path = path | tab->qid.path;
		devdir(c, q, tab->name, tab->length, eve, tab->perm, dp);
		return 1;
	}

	/* active endpoints */
	d = usbdeviceofpath(path);
	if(d == nil)
		return -1;
	s -= nelem(usbdir3);
	if(s < 0 || s >= nelem(d->ep))
		return -1;
	if(s == 0 || (e = d->ep[s]) == nil)	/* ep0data is called "setup" */
		return 0;
	sprint(buf, "ep%ddata", s);
	q.path = path | (Qep0+s);
	devdir(c, q, buf, 0, eve, e->mode==OREAD? 0444: e->mode==OWRITE? 0222: 0666, dp);
	return 1;
}

int
usbwalk(Chan *c, char *name)
{
	if(strcmp(name, "..") == 0){
		switch(QID(c->qid)){
		case Qtopdir:
			return 1;
		case Q2nd:
			c->qid = (Qid){CHDIR|Qtopdir, 0};
			break;
		case Q3rd:
			c->qid = (Qid){CHDIR|Q2nd, 0};
			break;
		default:
			panic("usbwalk %lux", c->qid.path);
		}
		return 1;
	}
	return devwalk(c, name, nil, 0, usbgen);
}

void
usbstat(Chan *c, char *db)
{
	devstat(c, db, nil, 0, usbgen);
}

Chan *
usbopen(Chan *c, int omode)
{
	Udev *d;
	int f, s;

	if(c->qid.path & CHDIR)
		return devopen(c, omode, nil, 0, usbgen);

	f = 0;
	if(QID(c->qid) == Qnew){
		d = usbnewdevice();
		if(d == nil)
			error(Enodev);
		c->qid.path = Qctl|((d->x+1)<<QSHIFT);
		c->qid.vers = d->id;
		f = 1;
	}

	if(c->qid.path < Q3rd)
		return devopen(c, omode, nil, 0, usbgen);

	qlock(&usbstate);
	if(waserror()){
		qunlock(&usbstate);
		nexterror();
	}

	switch(QID(c->qid)){
	case Qctl:
		d = usbdevice(c);
		if(0&&d->busy)
			error(Einuse);
		d->busy = 1;
		if(!f)
			incref(d);
		break;

	default:
		d = usbdevice(c);
		s = QID(c->qid) - Qep0;
		if(s >= 0 && s < nelem(d->ep)){
			Endpt *e;
			if((e = d->ep[s]) == nil)
				error(Enodev);
			if(schedendpt(e) < 0)
				error("can't schedule USB endpoint");
			eptactivate(e);
		}
		incref(d);
		break;
	}
	poperror();
	qunlock(&usbstate);
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

void
usbcreate(Chan *c, char *name, int omode, ulong perm)
{
	USED(c, name, omode, perm);
	error(Eperm);
}

void
usbremove(Chan*)
{
	error(Eperm);
}

void
usbwstat(Chan *c, char *dp)
{
	USED(c, dp);
	error(Eperm);
}

void
usbclose(Chan *c)
{
	Udev *d;

	if(c->qid.path & CHDIR || c->qid.path < Q3rd)
		return;
	qlock(&usbstate);
	if(waserror()){
		qunlock(&usbstate);
		nexterror();
	}
	d = usbdevice(c);
	if(QID(c->qid) == Qctl)
		d->busy = 0;
	if(c->flag & COPEN)
		freedev(d);
	poperror();
	qunlock(&usbstate);
}

static int
eptinput(void *arg)
{
	Endpt *e;

	e = arg;
	return e->eof || e->err || qcanread(e->rq);
}

static long
readusb(Endpt *e, void *a, long n)
{
	Block *b;
	uchar *p;
	long l, i;

	XPRINT("qlock(%p)\n", &e->rlock);
	qlock(&e->rlock);
	XPRINT("got qlock(%p)\n", &e->rlock);
	if(waserror()){
		qunlock(&e->rlock);
		eptcancel(e);
		nexterror();
	}
	p = a;
	do {
		if(e->eof) {
			XPRINT("e->eof\n");
			break;
		}
		if(e->err)
			error(e->err);
		qrcv(e);
		if(!e->iso)
			e->data01 ^= 1;
		sleep(&e->rr, eptinput, e);
		if(e->err)
			error(e->err);
		b = qget(e->rq);	/* TO DO */
		if(b == nil) {
			XPRINT("b == nil\n");
			break;
		}
		if(waserror()){
			freeb(b);
			nexterror();
		}
		l = BLEN(b);
		if((i = l) > n)
			i = n;
		if(i > 0){
			memmove(p, b->rp, i);
			p += i;
		}
		poperror();
		freeb(b);
		n -= i;
	} while (l == e->maxpkt && n > 0);
	poperror();
	qunlock(&e->rlock);
	return p-(uchar*)a;
}

long
usbread(Chan *c, void *a, long n, vlong offset)
{
	Endpt *e;
	Udev *d;
	char buf[48], *s;
	int t, w0, w1, ps, l, i;

	if(c->qid.path & CHDIR)
		return devdirread(c, a, n, nil, 0, usbgen);

	t = QID(c->qid);
	switch(t){
	case Qbusctl:
		snprint(buf, sizeof(buf), "%11d %11d ", 0, 0);
		return readstr(offset, a, n, buf);

	case Qport:
		ps = portinfo(&ubus, &w0, &w1);
		snprint(buf, sizeof(buf), "0x%ux 0x%ux 0x%ux ", ps, w0, w1);
		return readstr(offset, a, n, buf);

	case Qctl:
		d = usbdevice(c);
		sprint(buf, "%11d %11d ", d->x, d->id);
		return readstr(offset, a, n, buf);

	case Qsetup:	/* endpoint 0 */
		d = usbdevice(c);
		if((e = d->ep[0]) == nil)
			error(Eio);	/* can't happen */
		e->data01 = 1;
		n = readusb(e, a, n);
		if(e->setin){
			e->setin = 0;
			e->data01 = 1;
			writeusb(e, "", 0, TokOUT);
		}
		break;

	case Qdebug:
		n=0;
		break;

	case Qstatus:
		d = usbdevice(c);
		s = smalloc(READSTR);
		if(waserror()){
			free(s);
			nexterror();
		}
		l = snprint(s, READSTR, "%s %#6.6lux\n", devstates[d->state], d->csp);
		for(i=0; i<nelem(d->ep); i++)
			if((e = d->ep[i]) != nil)	/* TO DO: freeze e */
				l += snprint(s+l, READSTR-l, "%2d %#6.6lux %10lud bytes %10lud blocks\n", i, e->csp, e->nbytes, e->nblocks);
		n = readstr(offset, a, n, s);
		poperror();
		free(s);
		break;

	default:
		d = usbdevice(c);
		if((t -= Qep0) < 0 || t >= nelem(d->ep))
			error(Eio);
		if((e = d->ep[t]) == nil || e->mode == OWRITE)
			error(Eio);	/* can't happen */
		n=readusb(e, a, n);
		break;
	}
	return n;
}

static int
qisempty(void *arg)
{
	return ((QH*)arg)->entries & Terminate;
}

static long
writeusb(Endpt *e, void *a, long n, int tok)
{
	long i;
	Block *b;
	uchar *p;
	QH *qh;

	p = a;
	qlock(&e->wlock);
	if(waserror()){
		qunlock(&e->wlock);
		eptcancel(e);
		nexterror();
	}
	do {
		int j;

		if(e->err)
			error(e->err);
		if((i = n) >= e->maxpkt)
			i = e->maxpkt;
		b = allocb(i);
		if(waserror()){
			freeb(b);
			nexterror();
		}
		XPRINT("out [%ld]", i);
		for (j = 0; j < i; j++) XPRINT(" %.2x", p[j]);
		XPRINT("\n");
		memmove(b->wp, p, i);
		b->wp += i;
		p += i;
		n -= i;
		poperror();
		qh = qxmit(e, b, tok);
		tok = TokOUT;
		if(!e->iso)
			e->data01 ^= 1;
		if(e->ntd >= e->nbuf) {
			XPRINT("writeusb: sleep %lux\n", &e->wr);
			sleep(&e->wr, qisempty, qh);
			XPRINT("writeusb: awake\n");
		}
	} while(n > 0);
	poperror();
	qunlock(&e->wlock);
	return p-(uchar*)a;
}

long
usbwrite(Chan *c, void *a, long n, vlong)
{
	Udev *d;
	Endpt *e;
	int id, nw, nf, t, i;
	char cmd[50], *fields[10];

	if(c->qid.path & CHDIR)
		error(Egreg);
	t = QID(c->qid);
	if(t == Qbusctl){
		if(n >= sizeof(cmd)-1)
			n = sizeof(cmd)-1;
		memmove(cmd, a, n);
		cmd[n] = 0;
		nf = getfields(cmd, fields, nelem(fields), 0, " \t\n");
		if(nf==1 && strcmp(fields[0], "dump")==0){
			dumpframe(-1, -1);
			return n;
		}
		if(nf < 2)
			error(Ebadarg);
		id = strtol(fields[1], nil, 0);
		if(id != 1 && id != 2)
			error(Ebadarg);	/* there are two ports on the root hub */
		if(strcmp(fields[0], "reset") == 0)
			portreset(id);
		else if(strcmp(fields[0], "enable") == 0)
			portenable(id, 1);
		else if(strcmp(fields[0], "disable") == 0)
			portenable(id, 0);
		else
			error(Ebadarg);
		return n;
	}
	d = usbdevice(c);
	t = QID(c->qid);
	switch(t){
	case Qctl:
		if(n >= sizeof(cmd)-1)
			n = sizeof(cmd)-1;
		memmove(cmd, a, n);
		cmd[n] = 0;
		nf = getfields(cmd, fields, nelem(fields), 0, " \t\n");
		if(nf > 1 && strcmp(fields[0], "speed") == 0){
			d->ls = strtoul(fields[1], nil, 0) == 0;
		} else if(nf > 3 && strcmp(fields[0], "class") == 0){
			i = strtoul(fields[2], nil, 0);
			d->npt = strtoul(fields[1], nil, 0);
			/* class config# csp ( == class subclass proto) */
			if (i < 0 || i >= nelem(d->ep)
			 || d->npt > nelem(d->ep) || i >= d->npt)
				error(Ebadarg);
			if (i == 0) {
				d->csp = strtoul(fields[3], nil, 0);
			}
			if(d->ep[i] == nil)
				d->ep[i] = devendpt(d, i, 1);
			d->ep[i]->csp = strtoul(fields[3], nil, 0);
		}else if(nf > 2 && strcmp(fields[0], "data") == 0){
			i = strtoul(fields[1], nil, 0);
			if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
				error(Ebadarg);
			e = d->ep[i];
			e->data01 = strtoul(fields[2], nil, 0) != 0;
		}else if(nf > 2 && strcmp(fields[0], "maxpkt") == 0){
			i = strtoul(fields[1], nil, 0);
			if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
				error(Ebadarg);
			e = d->ep[i];
			e->maxpkt = strtoul(fields[2], nil, 0);
			if(e->maxpkt > 1500)
				e->maxpkt = 1500;
		}else if(nf > 2 && strcmp(fields[0], "debug") == 0){
			i = strtoul(fields[1], nil, 0);
			if(i < -1 || i >= nelem(d->ep) || d->ep[i] == nil)
				error(Ebadarg);
			if (i == -1)
				debug = 0;
			else {
				debug = 1;
				e = d->ep[i];
				e->debug = strtoul(fields[2], nil, 0);
			}
		}else if(nf > 1 && strcmp(fields[0], "unstall") == 0){
			i = strtoul(fields[1], nil, 0);
			if(i < 0 || i >= nelem(d->ep) || d->ep[i] == nil)
				error(Ebadarg);
			e = d->ep[i];
			e->err = nil;
		}else if(nf == 6 && strcmp(fields[0], "ep") == 0){
			/* ep n maxpkt mode poll nbuf */
			i = strtoul(fields[1], nil, 0);
			if(i < 0 || i >= nelem(d->ep)) {
				XPRINT("field 1: 0 <= %d < %d\n", i, nelem(d->ep));
				error(Ebadarg);
			}
			if((e = d->ep[i]) == nil)
				e = devendpt(d, i, 1);
			if(waserror()){
				freept(e);
				nexterror();
			}
			i = strtoul(fields[2], nil, 0);
			if(i < 8 || i > 1023)
				i = 8;
			e->maxpkt = i;
			e->mode = strcmp(fields[3],"r") == 0? OREAD :
					  strcmp(fields[3],"w") == 0? OWRITE : ORDWR;
			e->periodic = 0;
			e->sched = -1;
			if(strcmp(fields[4], "bulk") == 0){
				Ctlr *ub;

				ub = &ubus;
				/* Each bulk device gets a queue head hanging off the
				 * bulk queue head
				 */
				if (e->epq == nil) {
					e->epq = allocqh(ub);
					if(e->epq == nil)
						panic("usbwrite: allocqh");
				}
				queueqh(e->epq);
			} else {
				e->periodic = 1;
				i = strtoul(fields[4], nil, 0);
				if(i > 0 && i <= 1000)
					e->pollms = i;
				else {
					XPRINT("field 4: 0 <= %d <= 1000\n", i);
					error(Ebadarg);
				}
			}
			i = strtoul(fields[5], nil, 0);
			if(i >= 1 && i <= 32)
				e->nbuf = i;
			poperror();
		}else {
			XPRINT("command %s, fields %d\n", fields[0], nf);
			error(Ebadarg);
		}
		return n;

	case Qsetup:	/* SETUP endpoint 0 */
		/* should canqlock etc */
		if((e = d->ep[0]) == nil)
			error(Eio);	/* can't happen */
		if(n < 8 || n > 1023)
			error(Eio);
		nw = *(uchar*)a & RD2H;
		e->data01 = 0;
		n = writeusb(e, a, n, TokSETUP);
		if(nw == 0){	/* host to device: use IN[DATA1] to ack */
			e->data01 = 1;
			nw = readusb(e, cmd, 8);
			if(nw != 0)
				error(Eio);	/* could provide more status */
		}else
			e->setin = 1;	/* two-phase */
		break;

	default:	/* sends DATA[01] */
		if((t -= Qep0) < 0 || t >= nelem(d->ep)) {
			print("t = %d\n", t);
			error(Eio);
		}
		if((e = d->ep[t]) == nil || e->mode == OREAD) {
			error(Eio);	/* can't happen */
		}
		n = writeusb(e, a, n, TokOUT);
		break;
	}
	return n;
}

Dev usbdevtab = {
	'U',
	"usb",

	usbreset,
	usbinit,
	usbattach,
	usbclone,
	usbwalk,
	usbstat,
	usbopen,
	devcreate,
	usbclose,
	usbread,
	devbread,
	usbwrite,
	devbwrite,
	devremove,
	devwstat,
};
	if(!e->periodic || e->sched

M port/qio.c => port/qio.c +2 -1
@@ 1220,9 1220,10 @@ qwrite(Queue *q, void *vp, int len)
	QDEBUG if(!islo())
		print("qwrite hi %lux\n", getcallerpc(&q));

	next = nil;
	b = mem2bl(vp, len);
	if(waserror()){
		freeblist(b);
		freeblist(next);
		nexterror();
	}
	for(; b != nil; b = next){