git: 9front

Download patch

ref: f10a8c7f63c2c099958aaa2d49b6365f642dae3b
parent: 4259a75c8360b5daef471608e1f6160d0604313f
author: aiju <aiju@phicode.de>
date: Wed Jul 27 16:07:30 EDT 2011

nusb: improved

--- a/lib/usbdb
+++ b/lib/usbdb
@@ -1,3 +1,7 @@
+nusb/kb
+	csp=0x010103
+	csp=0x020103
+
 nusb/disk
 	class=8
 
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/disk.c
@@ -1,0 +1,982 @@
+/*
+ * usb/disk - usb mass storage file server
+ *
+ * supports only the scsi command interface, not ata.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "scsireq.h"
+#include "usb.h"
+#include "ums.h"
+
+enum
+{
+	Qdir = 0,
+	Qctl,
+	Qraw,
+	Qdata,
+	Qpart,
+	Qmax = Maxparts,
+};
+
+typedef struct Dirtab Dirtab;
+struct Dirtab
+{
+	char	*name;
+	int	mode;
+};
+
+ulong ctlmode = 0664;
+
+/*
+ * Partition management (adapted from disk/partfs)
+ */
+
+Part *
+lookpart(Umsc *lun, char *name)
+{
+	Part *part, *p;
+	
+	part = lun->part;
+	for(p=part; p < &part[Qmax]; p++){
+		if(p->inuse && strcmp(p->name, name) == 0)
+			return p;
+	}
+	return nil;
+}
+
+Part *
+freepart(Umsc *lun)
+{
+	Part *part, *p;
+	
+	part = lun->part;
+	for(p=part; p < &part[Qmax]; p++){
+		if(!p->inuse)
+			return p;
+	}
+	return nil;
+}
+
+int
+addpart(Umsc *lun, char *name, vlong start, vlong end, ulong mode)
+{
+	Part *p;
+
+	if(start < 0 || start > end || end > lun->blocks){
+		werrstr("bad partition boundaries");
+		return -1;
+	}
+	if(lookpart(lun, name) != nil) {
+		werrstr("partition name already in use");
+		return -1;
+	}
+	p = freepart(lun);
+	if(p == nil){
+		werrstr("no free partition slots");
+		return -1;
+	}
+	p->inuse = 1;
+	free(p->name);
+	p->id = p - lun->part;
+	p->name = estrdup(name);
+	p->offset = start;
+	p->length = end - start;
+	p->mode = mode;
+	return 0;
+}
+
+int
+delpart(Umsc *lun, char *s)
+{
+	Part *p;
+
+	p = lookpart(lun, s);
+	if(p == nil || p->id <= Qdata){
+		werrstr("partition not found");
+		return -1;
+	}
+	p->inuse = 0;
+	free(p->name);
+	p->name = nil;
+	p->vers++;
+	return 0;
+}
+
+void
+fixlength(Umsc *lun, vlong blocks)
+{
+	Part *part, *p;
+	
+	part = lun->part;
+	part[Qdata].length = blocks;
+	for(p=&part[Qdata+1]; p < &part[Qmax]; p++){
+		if(p->inuse && p->offset + p->length > blocks){
+			if(p->offset > blocks){
+				p->offset =blocks;
+				p->length = 0;
+			}else
+				p->length = blocks - p->offset;
+		}
+	}
+}
+
+void
+makeparts(Umsc *lun)
+{
+	addpart(lun, "/", 0, 0, DMDIR | 0555);
+	addpart(lun, "ctl", 0, 0, 0664);
+	addpart(lun, "raw", 0, 0, 0640);
+	addpart(lun, "data", 0, lun->blocks, 0640);
+}
+
+/*
+ * ctl parsing & formatting (adapted from partfs)
+ */
+
+static char*
+ctlstring(Usbfs *fs)
+{
+	Part *p, *part;
+	Fmt fmt;
+	Umsc *lun;
+	Ums *ums;
+	
+	ums = fs->dev->aux;
+	lun = fs->aux;
+	part = &lun->part[0];
+
+	fmtstrinit(&fmt);
+	fmtprint(&fmt, "dev %s\n", fs->dev->dir);
+	fmtprint(&fmt, "lun %ld\n", lun - &ums->lun[0]);
+	if(lun->flags & Finqok)
+		fmtprint(&fmt, "inquiry %s\n", lun->inq);
+	if(lun->blocks > 0)
+		fmtprint(&fmt, "geometry %llud %ld\n", lun->blocks, lun->lbsize);
+	for (p = &part[Qdata+1]; p < &part[Qmax]; p++)
+		if (p->inuse)
+			fmtprint(&fmt, "part %s %lld %lld\n",
+				p->name, p->offset, p->offset + p->length);
+	return fmtstrflush(&fmt);
+}
+
+static int
+ctlparse(Usbfs *fs, char *msg)
+{
+	vlong start, end;
+	char *argv[16];
+	int argc;
+	Umsc *lun;
+	
+	lun = fs->aux;
+	argc = tokenize(msg, argv, nelem(argv));
+
+	if(argc < 1){
+		werrstr("empty control message");
+		return -1;
+	}
+
+	if(strcmp(argv[0], "part") == 0){
+		if(argc != 4){
+			werrstr("part takes 3 args");
+			return -1;
+		}
+		start = strtoll(argv[2], 0, 0);
+		end = strtoll(argv[3], 0, 0);
+		return addpart(lun, argv[1], start, end, 0640);
+	}else if(strcmp(argv[0], "delpart") == 0){
+		if(argc != 2){
+			werrstr("delpart takes 1 arg");
+			return -1;
+		}
+		return delpart(lun, argv[1]);
+	}
+	werrstr("unknown ctl");
+	return -1;
+}
+
+/*
+ * These are used by scuzz scsireq
+ */
+int exabyte, force6bytecmds;
+
+int diskdebug;
+
+static void
+ding(void *, char *msg)
+{
+	if(strstr(msg, "alarm") != nil)
+		noted(NCONT);
+	noted(NDFLT);
+}
+
+static int
+getmaxlun(Dev *dev)
+{
+	uchar max;
+	int r;
+
+	max = 0;
+	r = Rd2h|Rclass|Riface;
+	if(usbcmd(dev, r, Getmaxlun, 0, 0, &max, 1) < 0){
+		dprint(2, "disk: %s: getmaxlun failed: %r\n", dev->dir);
+	}else{
+		max &= 017;			/* 15 is the max. allowed */
+		dprint(2, "disk: %s: maxlun %d\n", dev->dir, max);
+	}
+	return max;
+}
+
+static int
+umsreset(Ums *ums)
+{
+	int r;
+
+	r = Rh2d|Rclass|Riface;
+	if(usbcmd(ums->dev, r, Umsreset, 0, 0, nil, 0) < 0){
+		fprint(2, "disk: reset: %r\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int
+umsrecover(Ums *ums)
+{
+	if(umsreset(ums) < 0)
+		return -1;
+	if(unstall(ums->dev, ums->epin, Ein) < 0)
+		dprint(2, "disk: unstall epin: %r\n");
+
+	/* do we need this when epin == epout? */
+	if(unstall(ums->dev, ums->epout, Eout) < 0)
+		dprint(2, "disk: unstall epout: %r\n");
+	return 0;
+}
+
+static void
+umsfatal(Ums *ums)
+{
+	int i;
+
+	devctl(ums->dev, "detach");
+	for(i = 0; i < ums->maxlun; i++)
+		usbfsdel(&ums->lun[i].fs);
+}
+
+static int
+ispow2(uvlong ul)
+{
+	return (ul & (ul - 1)) == 0;
+}
+
+/*
+ * return smallest power of 2 >= n
+ */
+static int
+log2(int n)
+{
+	int i;
+
+	for(i = 0; (1 << i) < n; i++)
+		;
+	return i;
+}
+
+static int
+umscapacity(Umsc *lun)
+{
+	uchar data[32];
+
+	lun->blocks = 0;
+	lun->capacity = 0;
+	lun->lbsize = 0;
+	memset(data, 0, sizeof data);
+	if(SRrcapacity(lun, data) < 0 && SRrcapacity(lun, data)  < 0)
+		return -1;
+	lun->blocks = GETBELONG(data);
+	lun->lbsize = GETBELONG(data+4);
+	if(lun->blocks == 0xFFFFFFFF){
+		if(SRrcapacity16(lun, data) < 0){
+			lun->lbsize = 0;
+			lun->blocks = 0;
+			return -1;
+		}else{
+			lun->lbsize = GETBELONG(data + 8);
+			lun->blocks = (uvlong)GETBELONG(data)<<32 |
+				GETBELONG(data + 4);
+		}
+	}
+	lun->blocks++; /* SRcapacity returns LBA of last block */
+	lun->capacity = (vlong)lun->blocks * lun->lbsize;
+	fixlength(lun, lun->blocks);
+	if(diskdebug)
+		fprint(2, "disk: logical block size %lud, # blocks %llud\n",
+			lun->lbsize, lun->blocks);
+	return 0;
+}
+
+static int
+umsinit(Ums *ums)
+{
+	uchar i;
+	Umsc *lun;
+	int some;
+
+	umsreset(ums);
+	ums->maxlun = getmaxlun(ums->dev);
+	ums->lun = mallocz((ums->maxlun+1) * sizeof(*ums->lun), 1);
+	some = 0;
+	for(i = 0; i <= ums->maxlun; i++){
+		lun = &ums->lun[i];
+		lun->ums = ums;
+		lun->umsc = lun;
+		lun->lun = i;
+		lun->flags = Fopen | Fusb | Frw10;
+		if(SRinquiry(lun) < 0 && SRinquiry(lun) < 0){
+			dprint(2, "disk: lun %d inquiry failed\n", i);
+			continue;
+		}
+		switch(lun->inquiry[0]){
+		case Devdir:
+		case Devworm:		/* a little different than the others */
+		case Devcd:
+		case Devmo:
+			break;
+		default:
+			fprint(2, "disk: lun %d is not a disk (type %#02x)\n",
+				i, lun->inquiry[0]);
+			continue;
+		}
+		SRstart(lun, 1);
+		/*
+		 * we ignore the device type reported by inquiry.
+		 * Some devices return a wrong value but would still work.
+		 */
+		some++;
+		lun->inq = smprint("%.48s", (char *)lun->inquiry+8);
+		umscapacity(lun);
+	}
+	if(some == 0){
+		dprint(2, "disk: all luns failed\n");
+		devctl(ums->dev, "detach");
+		return -1;
+	}
+	return 0;
+}
+
+
+/*
+ * called by SR*() commands provided by scuzz's scsireq
+ */
+long
+umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
+{
+	Cbw cbw;
+	Csw csw;
+	int n, nio, left;
+	Ums *ums;
+
+	ums = umsc->ums;
+
+	memcpy(cbw.signature, "USBC", 4);
+	cbw.tag = ++ums->seq;
+	cbw.datalen = data->count;
+	cbw.flags = data->write? CbwDataOut: CbwDataIn;
+	cbw.lun = umsc->lun;
+	if(cmd->count < 1 || cmd->count > 16)
+		print("disk: umsrequest: bad cmd count: %ld\n", cmd->count);
+
+	cbw.len = cmd->count;
+	assert(cmd->count <= sizeof(cbw.command));
+	memcpy(cbw.command, cmd->p, cmd->count);
+	memset(cbw.command + cmd->count, 0, sizeof(cbw.command) - cmd->count);
+
+	werrstr("");		/* we use %r later even for n == 0 */
+	if(diskdebug){
+		fprint(2, "disk: cmd: tag %#lx: ", cbw.tag);
+		for(n = 0; n < cbw.len; n++)
+			fprint(2, " %2.2x", cbw.command[n]&0xFF);
+		fprint(2, " datalen: %ld\n", cbw.datalen);
+	}
+
+	/* issue tunnelled scsi command */
+	if(write(ums->epout->dfd, &cbw, CbwLen) != CbwLen){
+		fprint(2, "disk: cmd: %r\n");
+		goto Fail;
+	}
+
+	/* transfer the data */
+	nio = data->count;
+	if(nio != 0){
+		if(data->write)
+			n = write(ums->epout->dfd, data->p, nio);
+		else{
+			n = read(ums->epin->dfd, data->p, nio);
+			left = nio - n;
+			if (n >= 0 && left > 0)	/* didn't fill data->p? */
+				memset(data->p + n, 0, left);
+		}
+		nio = n;
+		if(diskdebug)
+			if(n < 0)
+				fprint(2, "disk: data: %r\n");
+			else
+				fprint(2, "disk: data: %d bytes\n", n);
+		if(n <= 0)
+			if(data->write == 0)
+				unstall(ums->dev, ums->epin, Ein);
+	}
+
+	/* read the transfer's status */
+	n = read(ums->epin->dfd, &csw, CswLen);
+	if(n <= 0){
+		/* n == 0 means "stalled" */
+		unstall(ums->dev, ums->epin, Ein);
+		n = read(ums->epin->dfd, &csw, CswLen);
+	}
+
+	if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){
+		dprint(2, "disk: read n=%d: status: %r\n", n);
+		goto Fail;
+	}
+	if(csw.tag != cbw.tag){
+		dprint(2, "disk: status tag mismatch\n");
+		goto Fail;
+	}
+	if(csw.status >= CswPhaseErr){
+		dprint(2, "disk: phase error\n");
+		goto Fail;
+	}
+	if(csw.dataresidue == 0 || ums->wrongresidues)
+		csw.dataresidue = data->count - nio;
+	if(diskdebug){
+		fprint(2, "disk: status: %2.2ux residue: %ld\n",
+			csw.status, csw.dataresidue);
+		if(cbw.command[0] == ScmdRsense){
+			fprint(2, "sense data:");
+			for(n = 0; n < data->count - csw.dataresidue; n++)
+				fprint(2, " %2.2x", data->p[n]);
+			fprint(2, "\n");
+		}
+	}
+	switch(csw.status){
+	case CswOk:
+		*status = STok;
+		break;
+	case CswFailed:
+		*status = STcheck;
+		break;
+	default:
+		dprint(2, "disk: phase error\n");
+		goto Fail;
+	}
+	ums->nerrs = 0;
+	return data->count - csw.dataresidue;
+
+Fail:
+	*status = STharderr;
+	if(ums->nerrs++ > 15){
+		fprint(2, "disk: %s: too many errors: device detached\n", ums->dev->dir);
+		umsfatal(ums);
+	}else
+		umsrecover(ums);
+	return -1;
+}
+
+static int
+dwalk(Usbfs *fs, Fid *fid, char *name)
+{
+	Umsc *lun;
+	Part *p;
+	
+	lun = fs->aux;
+	
+	if((fid->qid.type & QTDIR) == 0){
+		werrstr("walk in non-directory");
+		return -1;
+	}
+	if(strcmp(name, "..") == 0)
+		return 0;
+	
+	p = lookpart(lun, name);
+	if(p == nil){
+		werrstr(Enotfound);
+		return -1;
+	}
+	fid->qid.path = p->id | fs->qid;
+	fid->qid.vers = p->vers;
+	fid->qid.type = p->mode >> 24;
+	return 0;
+}
+static int
+dstat(Usbfs *fs, Qid qid, Dir *d);
+
+static void
+dostat(Usbfs *fs, int path, Dir *d)
+{
+	Umsc *lun;
+	Part *p;
+
+	lun = fs->aux;
+	p = &lun->part[path];
+	d->qid.path = path;
+	d->qid.vers = p->vers;
+	d->qid.type =p->mode >> 24;
+	d->mode = p->mode;
+	d->length = (vlong) p->length * lun->lbsize;
+	strecpy(d->name, d->name + Namesz - 1, p->name);
+}
+
+static int
+dirgen(Usbfs *fs, Qid, int n, Dir *d, void*)
+{
+	Umsc *lun;
+	int i;
+	
+	lun = fs->aux;
+	for(i = Qctl; i < Qmax; i++){
+		if(lun->part[i].inuse == 0)
+			continue;
+		if(n-- == 0)
+			break;
+	}
+	if(i == Qmax)
+		return -1;
+	dostat(fs, i, d);
+	d->qid.path |= fs->qid;
+	return 0;
+}
+
+static int
+dstat(Usbfs *fs, Qid qid, Dir *d)
+{
+	int path;
+
+	path = qid.path & ~fs->qid;
+	dostat(fs, path, d);
+	d->qid.path |= fs->qid;
+	return 0;
+}
+
+static int
+dopen(Usbfs *fs, Fid *fid, int)
+{
+	ulong path;
+	Umsc *lun;
+
+	path = fid->qid.path & ~fs->qid;
+	lun = fs->aux;
+	switch(path){
+	case Qraw:
+		lun->phase = Pcmd;
+		break;
+	}
+	return 0;
+}
+
+/*
+ * check i/o parameters and compute values needed later.
+ * we shift & mask manually to avoid run-time calls to _divv and _modv,
+ * since we don't need general division nor its cost.
+ */
+static int
+setup(Umsc *lun, Part *p, char *data, int count, vlong offset)
+{
+	long nb, lbsize, lbshift, lbmask;
+	uvlong bno;
+
+	if(count < 0 || lun->lbsize <= 0 && umscapacity(lun) < 0 ||
+	    lun->lbsize == 0)
+		return -1;
+	lbsize = lun->lbsize;
+	assert(ispow2(lbsize));
+	lbshift = log2(lbsize);
+	lbmask = lbsize - 1;
+
+	bno = offset >> lbshift;	/* offset / lbsize */
+	nb = ((offset + count + lbsize - 1) >> lbshift) - bno;
+
+	if(bno + nb > p->length)		/* past end of partition? */
+		nb = p->length - bno;
+	if(nb * lbsize > Maxiosize)
+		nb = Maxiosize / lbsize;
+	lun->nb = nb;
+	if(bno >= p->length || nb == 0)
+		return 0;
+
+	bno += p->offset;		/* start of partition */
+	lun->offset = bno;
+	lun->off = offset & lbmask;		/* offset % lbsize */
+	if(lun->off == 0 && (count & lbmask) == 0)
+		lun->bufp = data;
+	else
+		/* not transferring full, aligned blocks; need intermediary */
+		lun->bufp = lun->buf;
+	return count;
+}
+
+/*
+ * Upon SRread/SRwrite errors we assume the medium may have changed,
+ * and ask again for the capacity of the media.
+ * BUG: How to proceed to avoid confussing dossrv??
+ */
+static long
+dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
+{
+	long n;
+	ulong path;
+	char buf[64];
+	char *s;
+	Part *p;
+	Umsc *lun;
+	Ums *ums;
+	Qid q;
+
+	q = fid->qid;
+	path = fid->qid.path & ~fs->qid;
+	ums = fs->dev->aux;
+	lun = fs->aux;
+
+	qlock(ums);
+	switch(path){
+	case Qdir:
+		count = usbdirread(fs, q, data, count, offset, dirgen, nil);
+		break;
+	case Qctl:
+		s = ctlstring(fs);
+		count = usbreadbuf(data, count, offset, s, strlen(s));
+		free(s);
+		break;
+	case Qraw:
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			count = -1;
+			break;
+		}
+		switch(lun->phase){
+		case Pcmd:
+			qunlock(ums);
+			werrstr("phase error");
+			return -1;
+		case Pdata:
+			lun->data.p = data;
+			lun->data.count = count;
+			lun->data.write = 0;
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
+			lun->phase = Pstatus;
+			if(count < 0)
+				lun->lbsize = 0;  /* medium may have changed */
+			break;
+		case Pstatus:
+			n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
+			count = usbreadbuf(data, count, 0LL, buf, n);
+			lun->phase = Pcmd;
+			break;
+		}
+		break;
+	case Qdata:
+	default:
+		p = &lun->part[path];
+		if(!p->inuse){
+			count = -1;
+			werrstr(Eperm);
+			break;
+		}
+		count = setup(lun, p, data, count, offset);
+		if (count <= 0)
+			break;
+		n = SRread(lun, lun->bufp, lun->nb * lun->lbsize);
+		if(n < 0){
+			lun->lbsize = 0;	/* medium may have changed */
+			count = -1;
+		} else if (lun->bufp == data)
+			count = n;
+		else{
+			/*
+			 * if n == lun->nb*lun->lbsize (as expected),
+			 * just copy count bytes.
+			 */
+			if(lun->off + count > n)
+				count = n - lun->off; /* short read */
+			if(count > 0)
+				memmove(data, lun->bufp + lun->off, count);
+		}
+		break;
+	}
+	qunlock(ums);
+	return count;
+}
+
+static long
+dwrite(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
+{
+	long len, ocount;
+	ulong path;
+	uvlong bno;
+	Ums *ums;
+	Part *p;
+	Umsc *lun;
+	char *s;
+
+	ums = fs->dev->aux;
+	lun = fs->aux;
+	path = fid->qid.path & ~fs->qid;
+
+	qlock(ums);
+	switch(path){
+	case Qdir:
+		count = -1;
+		werrstr(Eperm);
+		break;
+	case Qctl:
+		s = emallocz(count+1, 1);
+		memmove(s, data, count);
+		if(s[count-1] == '\n')
+			s[count-1] = 0;
+		if(ctlparse(fs, s) == -1)
+			count = -1;
+		free(s);
+		break;
+	case Qraw:
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			count = -1;
+			break;
+		}
+		switch(lun->phase){
+		case Pcmd:
+			if(count != 6 && count != 10){
+				qunlock(ums);
+				werrstr("bad command length");
+				return -1;
+			}
+			memmove(lun->rawcmd, data, count);
+			lun->cmd.p = lun->rawcmd;
+			lun->cmd.count = count;
+			lun->cmd.write = 1;
+			lun->phase = Pdata;
+			break;
+		case Pdata:
+			lun->data.p = data;
+			lun->data.count = count;
+			lun->data.write = 1;
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
+			lun->phase = Pstatus;
+			if(count < 0)
+				lun->lbsize = 0;  /* medium may have changed */
+			break;
+		case Pstatus:
+			lun->phase = Pcmd;
+			werrstr("phase error");
+			count = -1;
+			break;
+		}
+		break;
+	case Qdata:
+	default:
+		p = &lun->part[path];
+		if(!p->inuse){
+			count = -1;
+			werrstr(Eperm);
+			break;
+		}
+		len = ocount = count;
+		count = setup(lun, p, data, count, offset);
+		if (count <= 0)
+			break;
+		bno = lun->offset;
+		if (lun->bufp == lun->buf) {
+			count = SRread(lun, lun->bufp, lun->nb * lun->lbsize);
+			if(count < 0) {
+				lun->lbsize = 0;  /* medium may have changed */
+				break;
+			}
+			/*
+			 * if count == lun->nb*lun->lbsize, as expected, just
+			 * copy len (the original count) bytes of user data.
+			 */
+			if(lun->off + len > count)
+				len = count - lun->off; /* short read */
+			if(len > 0)
+				memmove(lun->bufp + lun->off, data, len);
+		}
+
+		lun->offset = bno;
+		count = SRwrite(lun, lun->bufp, lun->nb * lun->lbsize);
+		if(count < 0)
+			lun->lbsize = 0;	/* medium may have changed */
+		else{
+			if(lun->off + len > count)
+				count -= lun->off; /* short write */
+			/* never report more bytes written than requested */
+			if(count < 0)
+				count = 0;
+			else if(count > ocount)
+				count = ocount;
+		}
+		break;
+	}
+	qunlock(ums);
+	return count;
+}
+
+int
+findendpoints(Ums *ums)
+{
+	Ep *ep;
+	Usbdev *ud;
+	ulong csp, sc;
+	int i, epin, epout;
+
+	epin = epout = -1;
+	ud = ums->dev->usb;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			continue;
+		csp = ep->iface->csp;
+		sc = Subclass(csp);
+		if(!(Class(csp) == Clstorage && (Proto(csp) == Protobulk)))
+			continue;
+		if(sc != Subatapi && sc != Sub8070 && sc != Subscsi)
+			fprint(2, "disk: subclass %#ulx not supported. trying anyway\n", sc);
+		if(ep->type == Ebulk){
+			if(ep->dir == Eboth || ep->dir == Ein)
+				if(epin == -1)
+					epin =  ep->id;
+			if(ep->dir == Eboth || ep->dir == Eout)
+				if(epout == -1)
+					epout = ep->id;
+		}
+	}
+	dprint(2, "disk: ep ids: in %d out %d\n", epin, epout);
+	if(epin == -1 || epout == -1)
+		return -1;
+	ums->epin = openep(ums->dev, epin);
+	if(ums->epin == nil){
+		fprint(2, "disk: openep %d: %r\n", epin);
+		return -1;
+	}
+	if(epout == epin){
+		incref(ums->epin);
+		ums->epout = ums->epin;
+	}else
+		ums->epout = openep(ums->dev, epout);
+	if(ums->epout == nil){
+		fprint(2, "disk: openep %d: %r\n", epout);
+		closedev(ums->epin);
+		return -1;
+	}
+	if(ums->epin == ums->epout)
+		opendevdata(ums->epin, ORDWR);
+	else{
+		opendevdata(ums->epin, OREAD);
+		opendevdata(ums->epout, OWRITE);
+	}
+	if(ums->epin->dfd < 0 || ums->epout->dfd < 0){
+		fprint(2, "disk: open i/o ep data: %r\n");
+		closedev(ums->epin);
+		closedev(ums->epout);
+		return -1;
+	}
+	dprint(2, "disk: ep in %s out %s\n", ums->epin->dir, ums->epout->dir);
+
+	devctl(ums->epin, "timeout 2000");
+	devctl(ums->epout, "timeout 2000");
+
+	if(usbdebug > 1 || diskdebug > 2){
+		devctl(ums->epin, "debug 1");
+		devctl(ums->epout, "debug 1");
+		devctl(ums->dev, "debug 1");
+	}
+	return 0;
+}
+
+static int
+usage(void)
+{
+	werrstr("usage: usb/disk [-d] [-N nb]");
+	return -1;
+}
+
+static void
+umsdevfree(void *a)
+{
+	Ums *ums = a;
+
+	if(ums == nil)
+		return;
+	closedev(ums->epin);
+	closedev(ums->epout);
+	ums->epin = ums->epout = nil;
+	free(ums->lun);
+	free(ums);
+}
+
+static Srv diskfs = {
+	.walk = dwalk,
+	.open =	 dopen,
+	.read =	 dread,
+	.write = dwrite,
+	.stat =	 dstat,
+};
+
+int
+diskmain(Dev *dev, int argc, char **argv)
+{
+	Ums *ums;
+	Umsc *lun;
+	int i, devid;
+
+	devid = dev->id;
+	ARGBEGIN{
+	case 'd':
+		scsidebug(diskdebug);
+		diskdebug++;
+		break;
+	case 'N':
+		devid = atoi(EARGF(usage()));
+		break;
+	default:
+		return usage();
+	}ARGEND
+	if(argc != 0) {
+		return usage();
+	}
+	
+//	notify(ding);
+	ums = dev->aux = emallocz(sizeof(Ums), 1);
+	ums->maxlun = -1;
+	ums->dev = dev;
+	dev->free = umsdevfree;
+	if(findendpoints(ums) < 0){
+		werrstr("disk: endpoints not found");
+		return -1;
+	}
+
+	/*
+	 * SanDISK 512M gets residues wrong.
+	 */
+	if(dev->usb->vid == 0x0781 && dev->usb->did == 0x5150)
+		ums->wrongresidues = 1;
+
+	if(umsinit(ums) < 0){
+		dprint(2, "disk: umsinit: %r\n");
+		return -1;
+	}
+
+	for(i = 0; i <= ums->maxlun; i++){
+		lun = &ums->lun[i];
+		lun->fs = diskfs;
+		snprint(lun->fs.name, sizeof(lun->fs.name), "sdU%d.%d", devid, i);
+		lun->fs.dev = dev;
+		incref(dev);
+		lun->fs.aux = lun;
+		makeparts(lun);
+		usbfsadd(&lun->fs);
+	}
+	return 0;
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/mkfile
@@ -1,0 +1,24 @@
+</$objtype/mkfile
+
+TARG=disk
+OFILES=\
+	disk.$O\
+	scsireq.$O\
+	scsierrs.$O\
+
+HFILES =\
+	scsireq.h\
+	../lib/usb.h\
+	ums.h\
+
+LIB=../lib/usb.a$O
+
+BIN=/$objtype/bin/usb
+
+</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
+CLEANFILES=scsierrs.c
+
+scsierrs.c: /sys/lib/scsicodes mkscsierrs
+	mkscsierrs >scsierrs.c
+
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/mkscsierrs
@@ -1,0 +1,32 @@
+#!/bin/rc
+
+cat <<EOF
+#include <u.h>
+#include <libc.h>
+
+typedef struct Err Err;
+struct Err
+{
+	int n;
+	char *s;
+};
+
+static Err scsierrs[] = {
+EOF
+
+grep '^[0-9a-c][0-9a-c][0-9a-c][0-9a-c][ 	]' /sys/lib/scsicodes |
+	sed -e 's/^(....) (.*)/	{0x\1,	"\2"},\n/'
+cat <<EOF
+};
+
+char*
+scsierrmsg(int n)
+{
+	int i;
+
+	for(i = 0; i < nelem(scsierrs); i++)
+		if(scsierrs[i].n == n)
+			return scsierrs[i].s;
+	return "scsi error";
+}
+EOF
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/scsireq.c
@@ -1,0 +1,986 @@
+/*
+ * This is /sys/src/cmd/scuzz/scsireq.c
+ * changed to add more debug support, to keep
+ * disk compiling without a scuzz that includes these changes.
+ * Also, this includes minor tweaks for usb:
+ *	we set req.lun/unit to rp->lun/unit in SRreqsense
+ *	we set the rp->sense[0] bit Sd0valid in SRreqsense
+ * This does not use libdisk to retrieve the scsi error to make
+ * user see the diagnostics if we boot with debug enabled.
+ *
+ * BUGS:
+ *	no luns
+ *	and incomplete in many other ways
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include "scsireq.h"
+
+enum {
+	Debug = 0,
+};
+
+/*
+ * exabyte tape drives, at least old ones like the 8200 and 8505,
+ * are dumb: you have to read the exact block size on the tape,
+ * they don't take 10-byte SCSI commands, and various other fine points.
+ */
+extern int exabyte, force6bytecmds;
+
+static int debug = Debug;
+
+static char *scmdnames[256] = {
+[ScmdTur]	"Tur",
+[ScmdRewind]	"Rewind",
+[ScmdRsense]	"Rsense",
+[ScmdFormat]	"Format",
+[ScmdRblimits]	"Rblimits",
+[ScmdRead]	"Read",
+[ScmdWrite]	"Write",
+[ScmdSeek]	"Seek",
+[ScmdFmark]	"Fmark",
+[ScmdSpace]	"Space",
+[ScmdInq]	"Inq",
+[ScmdMselect6]	"Mselect6",
+[ScmdMselect10]	"Mselect10",
+[ScmdMsense6]	"Msense6",
+[ScmdMsense10]	"Msense10",
+[ScmdStart]	"Start",
+[ScmdRcapacity]	"Rcapacity",
+[ScmdRcapacity16]	"Rcap16",
+[ScmdExtread]	"Extread",
+[ScmdExtwrite]	"Extwrite",
+[ScmdExtseek]	"Extseek",
+
+[ScmdSynccache]	"Synccache",
+[ScmdRTOC]	"RTOC",
+[ScmdRdiscinfo]	"Rdiscinfo",
+[ScmdRtrackinfo]	"Rtrackinfo",
+[ScmdReserve]	"Reserve",
+[ScmdBlank]	"Blank",
+
+[ScmdCDpause]	"CDpause",
+[ScmdCDstop]	"CDstop",
+[ScmdCDplay]	"CDplay",
+[ScmdCDload]	"CDload",
+[ScmdCDscan]	"CDscan",
+[ScmdCDstatus]	"CDstatus",
+[Scmdgetconf]	"getconf",
+};
+
+long
+SRready(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRrewind(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRewind;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	if(SRrequest(rp) >= 0){
+		rp->offset = 0;
+		return 0;
+	}
+	return -1;
+}
+
+long
+SRreqsense(ScsiReq *rp)
+{
+	uchar cmd[6];
+	ScsiReq req;
+	long status;
+
+	if(rp->status == Status_SD){
+		rp->status = STok;
+		return 0;
+	}
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRsense;
+	cmd[4] = sizeof(req.sense);
+	memset(&req, 0, sizeof(req));
+	if(rp->flags&Fusb)
+		req.flags |= Fusb;
+	req.lun = rp->lun;
+	req.unit = rp->unit;
+	req.fd = rp->fd;
+	req.umsc = rp->umsc;
+	req.cmd.p = cmd;
+	req.cmd.count = sizeof cmd;
+	req.data.p = rp->sense;
+	req.data.count = sizeof(rp->sense);
+	req.data.write = 0;
+	status = SRrequest(&req);
+	rp->status = req.status;
+	if(status != -1)
+		rp->sense[0] |= Sd0valid;
+	return status;
+}
+
+long
+SRformat(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFormat;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRrblimits(ScsiReq *rp, uchar *list)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRblimits;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+static int
+dirdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
+{
+	long n;
+
+	n = nbytes / rp->lbsize;
+	if(rp->offset <= Max24off && n <= 256 && (rp->flags & Frw10) == 0){
+		PUTBE24(cmd+1, rp->offset);
+		cmd[4] = n;
+		cmd[5] = 0;
+		return 6;
+	}
+	cmd[0] |= ScmdExtread;
+	cmd[1] = 0;
+	PUTBELONG(cmd+2, rp->offset);
+	cmd[6] = 0;
+	cmd[7] = n>>8;
+	cmd[8] = n;
+	cmd[9] = 0;
+	return 10;
+}
+
+static int
+seqdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
+{
+	long n;
+
+	/* don't set Cmd1sili; we want the ILI bit instead of a fatal error */
+	cmd[1] = rp->flags&Fbfixed? Cmd1fixed: 0;
+	n = nbytes / rp->lbsize;
+	PUTBE24(cmd+2, n);
+	cmd[5] = 0;
+	return 6;
+}
+
+extern int diskdebug;
+
+long
+SRread(ScsiReq *rp, void *buf, long nbytes)
+{
+	uchar cmd[10];
+	long n;
+
+	if(rp->lbsize == 0 || (nbytes % rp->lbsize) || nbytes > Maxiosize){
+		if(diskdebug)
+			if (nbytes % rp->lbsize)
+				fprint(2, "disk: i/o size %ld %% %ld != 0\n",
+					nbytes, rp->lbsize);
+			else
+				fprint(2, "disk: i/o size %ld > %d\n",
+					nbytes, Maxiosize);
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi read cmd */
+	cmd[0] = ScmdRead;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+
+	/* issue it */
+	n = SRrequest(rp);
+	if(n != -1){			/* it worked? */
+		rp->offset += n / rp->lbsize;
+		return n;
+	}
+
+	/* request failed; maybe we just read a short record? */
+	if (exabyte) {
+		fprint(2, "read error\n");
+		rp->status = STcheck;
+		return n;
+	}
+	if(rp->status != Status_SD || !(rp->sense[0] & Sd0valid))
+		return -1;
+	/* compute # of bytes not read */
+	n = GETBELONG(rp->sense+3) * rp->lbsize;
+	if(!(rp->flags & Fseqdev))
+		return -1;
+
+	/* device is a tape or something similar */
+	if (rp->sense[2] == Sd2filemark || rp->sense[2] == 0x08 ||
+	    rp->sense[2] & Sd2ili && n > 0)
+		rp->data.count = nbytes - n;
+	else
+		return -1;
+	n = rp->data.count;
+	if (!rp->readblock++ || debug)
+		fprint(2, "SRread: tape data count %ld%s\n", n,
+			(rp->sense[2] & Sd2ili? " with ILI": ""));
+	rp->status = STok;
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+long
+SRwrite(ScsiReq *rp, void *buf, long nbytes)
+{
+	uchar cmd[10];
+	long n;
+
+	if(rp->lbsize == 0 || (nbytes % rp->lbsize) || nbytes > Maxiosize){
+		if(diskdebug)
+			if (nbytes % rp->lbsize)
+				fprint(2, "disk: i/o size %ld %% %ld != 0\n",
+					nbytes, rp->lbsize);
+			else
+				fprint(2, "disk: i/o size %ld > %d\n",
+					nbytes, Maxiosize);
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi write cmd */
+	cmd[0] = ScmdWrite;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+
+	/* issue it */
+	if((n = SRrequest(rp)) == -1){
+		if (exabyte) {
+			fprint(2, "write error\n");
+			rp->status = STcheck;
+			return n;
+		}
+		if(rp->status != Status_SD || rp->sense[2] != Sd2eom)
+			return -1;
+		if(rp->sense[0] & Sd0valid){
+			n -= GETBELONG(rp->sense+3) * rp->lbsize;
+			rp->data.count = nbytes - n;
+		}
+		else
+			rp->data.count = nbytes;
+		n = rp->data.count;
+	}
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+long
+SRseek(ScsiReq *rp, long offset, int type)
+{
+	uchar cmd[10];
+
+	switch(type){
+
+	case 0:
+		break;
+
+	case 1:
+		offset += rp->offset;
+		if(offset >= 0)
+			break;
+		/*FALLTHROUGH*/
+
+	default:
+		if(diskdebug)
+			fprint(2, "disk: seek failed\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(cmd, 0, sizeof cmd);
+	if(offset <= Max24off && (rp->flags & Frw10) == 0){
+		cmd[0] = ScmdSeek;
+		PUTBE24(cmd+1, offset & Max24off);
+		rp->cmd.count = 6;
+	}else{
+		cmd[0] = ScmdExtseek;
+		PUTBELONG(cmd+2, offset);
+		rp->cmd.count = 10;
+	}
+	rp->cmd.p = cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	SRrequest(rp);
+	if(rp->status == STok) {
+		rp->offset = offset;
+		return offset;
+	}
+	return -1;
+}
+
+long
+SRfilemark(ScsiReq *rp, ulong howmany)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFmark;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRspace(ScsiReq *rp, uchar code, long howmany)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdSpace;
+	cmd[1] = code;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	/*
+	 * what about rp->offset?
+	 */
+	return SRrequest(rp);
+}
+
+long
+SRinquiry(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdInq;
+	cmd[4] = sizeof rp->inquiry;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	memset(rp->inquiry, 0, sizeof rp->inquiry);
+	rp->data.p = rp->inquiry;
+	rp->data.count = sizeof rp->inquiry;
+	rp->data.write = 0;
+	if(SRrequest(rp) >= 0){
+		rp->flags |= Finqok;
+		return 0;
+	}
+	rp->flags &= ~Finqok;
+	return -1;
+}
+
+long
+SRmodeselect6(ScsiReq *rp, uchar *list, long nbytes)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMselect6;
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRmodeselect10(ScsiReq *rp, uchar *list, long nbytes)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[0] = ScmdMselect10;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRmodesense6(ScsiReq *rp, uchar page, uchar *list, long nbytes)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense6;
+	cmd[2] = page;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRmodesense10(ScsiReq *rp, uchar page, uchar *list, long nbytes)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense10;
+	cmd[2] = page;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRstart(ScsiReq *rp, uchar code)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdStart;
+	cmd[4] = code;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRrcapacity(ScsiReq *rp, uchar *data)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRcapacity;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = data;
+	rp->data.count = 8;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRrcapacity16(ScsiReq *rp, uchar *data)
+{
+	uchar cmd[16];
+	uint i;
+
+	i = 32;
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRcapacity16;
+	cmd[1] = 0x10;
+	cmd[10] = i>>24;
+	cmd[11] = i>>16;
+	cmd[12] = i>>8;
+	cmd[13] = i;
+
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = data;
+	rp->data.count = i;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+void
+scsidebug(int d)
+{
+	debug = d;
+	if(debug)
+		fprint(2, "scsidebug on\n");
+}
+
+static long
+request(int fd, ScsiPtr *cmd, ScsiPtr *data, int *status)
+{
+	long n, r;
+	char buf[16];
+
+	/* this was an experiment but it seems to be a good idea */
+	*status = STok;
+
+	/* send SCSI command */
+	if(write(fd, cmd->p, cmd->count) != cmd->count){
+		fprint(2, "scsireq: write cmd: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+
+	/* read or write actual data */
+	werrstr("");
+//	alarm(5*1000);
+	if(data->write)
+		n = write(fd, data->p, data->count);
+	else {
+		n = read(fd, data->p, data->count);
+		if (n < 0)
+			memset(data->p, 0, data->count);
+		else if (n < data->count)
+			memset(data->p + n, 0, data->count - n);
+	}
+//	alarm(0);
+	if (n != data->count && n <= 0) {
+		if (debug)
+			fprint(2,
+	"request: tried to %s %ld bytes of data for cmd 0x%x but got %r\n",
+				(data->write? "write": "read"),
+				data->count, cmd->p[0]);
+	} else if (n != data->count && (data->write || debug))
+		fprint(2, "request: %s %ld of %ld bytes of actual data\n",
+			(data->write? "wrote": "read"), n, data->count);
+
+	/* read status */
+	buf[0] = '\0';
+	r = read(fd, buf, sizeof buf-1);
+	if(exabyte && r <= 0 || !exabyte && r < 0){
+		fprint(2, "scsireq: read status: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+	if (r >= 0)
+		buf[r] = '\0';
+	*status = atoi(buf);
+	if(n < 0 && (exabyte || *status != STcheck))
+		fprint(2, "scsireq: status 0x%2.2uX: data transfer: %r\n",
+			*status);
+	return n;
+}
+
+static char*
+seprintcmd(char *s, char* e, char *cmd, int count, int args)
+{
+	uint c;
+
+	if(count < 6)
+		return seprint(s, e, "<short cmd>");
+	c = cmd[0];
+	if(scmdnames[c] != nil)
+		s = seprint(s, e, "%s", scmdnames[c]);
+	else
+		s = seprint(s, e, "cmd:%#02uX", c);
+	if(args != 0)
+		switch(c){
+		case ScmdRsense:
+		case ScmdInq:
+		case ScmdMselect6:
+		case ScmdMsense6:
+			s = seprint(s, e, " sz %d", cmd[4]);
+			break;
+		case ScmdSpace:
+			s = seprint(s, e, " code %d", cmd[1]);
+			break;
+		case ScmdStart:
+			s = seprint(s, e, " code %d", cmd[4]);
+			break;
+	
+		}
+	return s;
+}
+
+static char*
+seprintdata(char *s, char *se, uchar *p, int count)
+{
+	int i;
+
+	if(count == 0)
+		return s;
+	for(i = 0; i < 20 && i < count; i++)
+		s = seprint(s, se, " %02x", p[i]);
+	return s;
+}
+
+static void
+SRdumpReq(ScsiReq *rp)
+{
+	char buf[128];
+	char *s;
+	char *se;
+
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "lun %d ", rp->lun);
+	s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 1);
+	s = seprint(s, se, " [%ld]", rp->data.count);
+	if(rp->cmd.write)
+		seprintdata(s, se, rp->data.p, rp->data.count);
+	fprint(2, "scsi⇒ %s\n", buf);
+}
+
+static void
+SRdumpRep(ScsiReq *rp)
+{
+	char buf[128];
+	char *s;
+	char *se;
+
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "lun %d ", rp->lun);
+	s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 0);
+	switch(rp->status){
+	case STok:
+		s = seprint(s, se, " good [%ld] ", rp->data.count);
+		if(rp->cmd.write == 0)
+			s = seprintdata(s, se, rp->data.p, rp->data.count);
+		break;
+	case STnomem:
+		s = seprint(s, se, " buffer allocation failed");
+		break;
+	case STharderr:
+		s = seprint(s, se, " controller error");
+		break;
+	case STtimeout:
+		s = seprint(s, se, " bus timeout");
+		break;
+	case STcheck:
+		s = seprint(s, se, " check condition");
+		break;
+	case STcondmet:
+		s = seprint(s, se, " condition met/good");
+		break;
+	case STbusy:
+		s = seprint(s, se, " busy");
+		break;
+	case STintok:
+		s = seprint(s, se, " intermediate/good");
+		break;
+	case STintcondmet:
+		s = seprint(s, se, " intermediate/condition met/good");
+		break;
+	case STresconf:
+		s = seprint(s, se, " reservation conflict");
+		break;
+	case STterminated:
+		s = seprint(s, se, " command terminated");
+		break;
+	case STqfull:
+		s = seprint(s, se, " queue full");
+		break;
+	default:
+		s = seprint(s, se, " sts=%#x", rp->status);
+	}
+	USED(s);
+	fprint(2, "scsi← %s\n", buf);
+}
+
+static char*
+scsierr(ScsiReq *rp)
+{
+	int ec;
+
+	switch(rp->status){
+	case 0:
+		return "";
+	case Status_SD:
+		ec = (rp->sense[12] << 8) | rp->sense[13];
+		return scsierrmsg(ec);
+	case Status_SW:
+		return "software error";
+	case Status_BADARG:
+		return "bad argument";
+	case Status_RO:
+		return "device is read only";
+	default:
+		return "unknown";
+	}
+}
+
+static void
+SRdumpErr(ScsiReq *rp)
+{
+	char buf[128];
+	char *se;
+
+	se = buf+sizeof(buf);
+	seprintcmd(buf, se, (char*)rp->cmd.p, rp->cmd.count, 0);
+	print("\t%s status: %s\n", buf, scsierr(rp));
+}
+
+long
+SRrequest(ScsiReq *rp)
+{
+	long n;
+	int status;
+
+retry:
+	if(debug)
+		SRdumpReq(rp);
+	if(rp->flags&Fusb)
+		n = umsrequest(rp->umsc, &rp->cmd, &rp->data, &status);
+	else
+		n = request(rp->fd, &rp->cmd, &rp->data, &status);
+	rp->status = status;
+	if(status == STok)
+		rp->data.count = n;
+	if(debug)
+		SRdumpRep(rp);
+	switch(status){
+	case STok:
+		break;
+	case STcheck:
+		if(rp->cmd.p[0] != ScmdRsense && SRreqsense(rp) != -1)
+			rp->status = Status_SD;
+		if(debug || exabyte)
+			SRdumpErr(rp);
+		werrstr("%s", scsierr(rp));
+		return -1;
+	case STbusy:
+		sleep(1000);		/* TODO: try a shorter sleep? */
+		goto retry;
+	default:
+		if(debug || exabyte)
+			SRdumpErr(rp);
+		werrstr("%s", scsierr(rp));
+		return -1;
+	}
+	return n;
+}
+
+int
+SRclose(ScsiReq *rp)
+{
+	if((rp->flags & Fopen) == 0){
+		if(diskdebug)
+			fprint(2, "disk: closing closed file\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	close(rp->fd);
+	rp->flags = 0;
+	return 0;
+}
+
+static int
+dirdevopen(ScsiReq *rp)
+{
+	uvlong blocks;
+	uchar data[8+4+20];	/* 16-byte result: lba, blksize, reserved */
+
+	memset(data, 0, sizeof data);
+	if(SRstart(rp, 1) == -1 || SRrcapacity(rp, data) == -1)
+		return -1;
+	rp->lbsize = GETBELONG(data+4);
+	blocks =     GETBELONG(data);
+	if(debug)
+		fprint(2, "disk: dirdevopen: 10-byte logical block size %lud, "
+			"# blocks %llud\n", rp->lbsize, blocks);
+	if(blocks == 0xffffffff){
+		if(SRrcapacity16(rp, data) == -1)
+			return -1;
+		rp->lbsize = GETBELONG(data + 8);
+		blocks = (vlong)GETBELONG(data)<<32 | GETBELONG(data + 4);
+		if(debug)
+			fprint(2, "disk: dirdevopen: 16-byte logical block size"
+				" %lud, # blocks %llud\n", rp->lbsize, blocks);
+	}
+	/* some newer dev's don't support 6-byte commands */
+	if(blocks > Max24off && !force6bytecmds)
+		rp->flags |= Frw10;
+	return 0;
+}
+
+static int
+seqdevopen(ScsiReq *rp)
+{
+	uchar mode[16], limits[6];
+
+	if(SRrblimits(rp, limits) == -1)
+		return -1;
+	if(limits[1] == 0 && limits[2] == limits[4] && limits[3] == limits[5]){
+		rp->flags |= Fbfixed;
+		rp->lbsize = limits[4]<<8 | limits[5];
+		if(debug)
+			fprint(2, "disk: seqdevopen: 10-byte logical block size %lud\n",
+				rp->lbsize);
+		return 0;
+	}
+	/*
+	 * On some older hardware the optional 10-byte
+	 * modeselect command isn't implemented.
+	 */
+	if (force6bytecmds)
+		rp->flags |= Fmode6;
+	if(!(rp->flags & Fmode6)){
+		/* try 10-byte command first */
+		memset(mode, 0, sizeof mode);
+		mode[3] = 0x10;		/* device-specific param. */
+		mode[7] = 8;		/* block descriptor length */
+		/*
+		 * exabytes can't handle this, and
+		 * modeselect(10) is optional.
+		 */
+		if(SRmodeselect10(rp, mode, sizeof mode) != -1){
+			rp->lbsize = 1;
+			return 0;	/* success */
+		}
+		/* can't do 10-byte commands, back off to 6-byte ones */
+		rp->flags |= Fmode6;
+	}
+
+	/* 6-byte command */
+	memset(mode, 0, sizeof mode);
+	mode[2] = 0x10;		/* device-specific param. */
+	mode[3] = 8;		/* block descriptor length */
+	/*
+	 * bsd sez exabytes need this bit (NBE: no busy enable) in
+	 * vendor-specific page (0), but so far we haven't needed it.
+	mode[12] |= 8;
+	 */
+	if(SRmodeselect6(rp, mode, 4+8) == -1)
+		return -1;
+	rp->lbsize = 1;
+	return 0;
+}
+
+static int
+wormdevopen(ScsiReq *rp)
+{
+	long status;
+	uchar list[MaxDirData];
+
+	if (SRstart(rp, 1) == -1 ||
+	    (status = SRmodesense10(rp, Allmodepages, list, sizeof list)) == -1)
+		return -1;
+	/* nbytes = list[0]<<8 | list[1]; */
+
+	/* # of bytes of block descriptors of 8 bytes each; not even 1? */
+	if((list[6]<<8 | list[7]) < 8)
+		rp->lbsize = 2048;
+	else
+		/* last 3 bytes of block 0 descriptor */
+		rp->lbsize = GETBE24(list+13);
+	if(debug)
+		fprint(2, "disk: wormdevopen: 10-byte logical block size %lud\n",
+			rp->lbsize);
+	return status;
+}
+
+int
+SRopenraw(ScsiReq *rp, char *unit)
+{
+	char name[128];
+
+	if(rp->flags & Fopen){
+		if(diskdebug)
+			fprint(2, "disk: opening open file\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(rp, 0, sizeof *rp);
+	rp->unit = unit;
+
+	snprint(name, sizeof name, "%s/raw", unit);
+	if((rp->fd = open(name, ORDWR)) == -1){
+		rp->status = STtimeout;
+		return -1;
+	}
+	rp->flags = Fopen;
+	return 0;
+}
+
+int
+SRopen(ScsiReq *rp, char *unit)
+{
+	if(SRopenraw(rp, unit) == -1)
+		return -1;
+	SRready(rp);
+	if(SRinquiry(rp) >= 0){
+		switch(rp->inquiry[0]){
+
+		default:
+			fprint(2, "unknown device type 0x%.2x\n", rp->inquiry[0]);
+			rp->status = Status_SW;
+			break;
+
+		case Devdir:
+		case Devcd:
+		case Devmo:
+			if(dirdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case Devseq:
+			rp->flags |= Fseqdev;
+			if(seqdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case Devprint:
+			rp->flags |= Fprintdev;
+			return 0;
+
+		case Devworm:
+			rp->flags |= Fwormdev;
+			if(wormdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case Devjuke:
+			rp->flags |= Fchanger;
+			return 0;
+		}
+	}
+	SRclose(rp);
+	return -1;
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/scsireq.h
@@ -1,0 +1,238 @@
+/*
+ * This is /sys/src/cmd/scuzz/scsireq.h
+ * changed to add more debug support, and to keep
+ * disk compiling without a scuzz that includes these changes.
+ *
+ * scsireq.h is also included by usb/disk and cdfs.
+ */
+typedef struct Umsc Umsc;
+#pragma incomplete Umsc
+
+enum {					/* fundamental constants/defaults */
+	MaxDirData	= 255,		/* max. direct data returned */
+	/*
+	 * Because we are accessed via devmnt, we can never get i/o counts
+	 * larger than 8216 (Msgsize and devmnt's offered iounit) - 24
+	 * (IOHDRSZ) = 8K.
+	 */
+	Maxiosize	= 8216 - IOHDRSZ, /* max. I/O transfer size */
+};
+
+typedef struct {
+	uchar	*p;
+	long	count;
+	uchar	write;
+} ScsiPtr;
+
+typedef struct {
+	int	flags;
+	char	*unit;			/* unit directory */
+	int	lun;
+	ulong	lbsize;
+	uvlong	offset;			/* in blocks of lbsize bytes */
+	int	fd;
+	Umsc	*umsc;			/* lun */
+	ScsiPtr	cmd;
+	ScsiPtr	data;
+	int	status;			/* returned status */
+	uchar	sense[MaxDirData];	/* returned sense data */
+	uchar	inquiry[MaxDirData];	/* returned inquiry data */
+	int	readblock;		/* flag: read a block since open */
+} ScsiReq;
+
+enum {					/* software flags */
+	Fopen		= 0x0001,	/* open */
+	Fseqdev		= 0x0002,	/* sequential-access device */
+	Fwritten	= 0x0004,	/* device written */
+	Fronly		= 0x0008,	/* device is read-only */
+	Fwormdev	= 0x0010,	/* write-once read-multiple device */
+	Fprintdev	= 0x0020,	/* printer */
+	Fbfixed		= 0x0040,	/* fixed block size */
+	Fchanger	= 0x0080,	/* medium-changer device */
+	Finqok		= 0x0100,	/* inquiry data is OK */
+	Fmode6		= 0x0200,	/* use 6-byte modeselect */
+	Frw10		= 0x0400,	/* use 10-byte read/write */
+	Fusb		= 0x0800,	/* USB transparent scsi */
+};
+
+enum {
+	STnomem		=-4,		/* buffer allocation failed */
+	STharderr	=-3,		/* controller error of some kind */
+	STtimeout	=-2,		/* bus timeout */
+	STok		= 0,		/* good */
+	STcheck		= 0x02,		/* check condition */
+	STcondmet	= 0x04,		/* condition met/good */
+	STbusy		= 0x08,		/* busy */
+	STintok		= 0x10,		/* intermediate/good */
+	STintcondmet	= 0x14,		/* intermediate/condition met/good */
+	STresconf	= 0x18,		/* reservation conflict */
+	STterminated	= 0x22,		/* command terminated */
+	STqfull		= 0x28,		/* queue full */
+};
+
+enum {					/* status */
+	Status_SD	= 0x80,		/* sense-data available */
+	Status_SW	= 0x83,		/* internal software error */
+	Status_BADARG	= 0x84,		/* bad argument to request */
+	Status_RO	= 0x85,		/* device is read-only */
+};
+
+enum {					/* SCSI command codes */
+	ScmdTur		= 0x00,		/* test unit ready */
+	ScmdRewind	= 0x01,		/* rezero/rewind */
+	ScmdRsense	= 0x03,		/* request sense */
+	ScmdFormat	= 0x04,		/* format unit */
+	ScmdRblimits	= 0x05,		/* read block limits */
+	ScmdRead	= 0x08,		/* read */
+	ScmdWrite	= 0x0A,		/* write */
+	ScmdSeek	= 0x0B,		/* seek */
+	ScmdFmark	= 0x10,		/* write filemarks */
+	ScmdSpace	= 0x11,		/* space forward/backward */
+	ScmdInq		= 0x12,		/* inquiry */
+	ScmdMselect6	= 0x15,		/* mode select */
+	ScmdMselect10	= 0x55,		/* mode select */
+	ScmdMsense6	= 0x1A,		/* mode sense */
+	ScmdMsense10	= 0x5A,		/* mode sense */
+	ScmdStart	= 0x1B,		/* start/stop unit */
+	ScmdRcapacity	= 0x25,		/* read capacity */
+	ScmdRcapacity16	= 0x9e,		/* long read capacity */
+	ScmdExtread	= 0x28,		/* extended read */
+	ScmdExtwrite	= 0x2A,		/* extended write */
+	ScmdExtseek	= 0x2B,		/* extended seek */
+
+	ScmdSynccache	= 0x35,		/* flush cache */
+	ScmdRTOC	= 0x43,		/* read TOC data */
+	ScmdRdiscinfo	= 0x51,		/* read disc information */
+	ScmdRtrackinfo	= 0x52,		/* read track information */
+	ScmdReserve	= 0x53,		/* reserve track */
+	ScmdBlank	= 0xA1,		/* blank *-RW media */
+
+	ScmdCDpause	= 0x4B,		/* pause/resume */
+	ScmdCDstop	= 0x4E,		/* stop play/scan */
+	ScmdCDplay	= 0xA5,		/* play audio */
+	ScmdCDload	= 0xA6,		/* load/unload */
+	ScmdCDscan	= 0xBA,		/* fast forward/reverse */
+	ScmdCDstatus	= 0xBD,		/* mechanism status */
+	Scmdgetconf	= 0x46,		/* get configuration */
+
+	ScmdEInitialise	= 0x07,		/* initialise element status */
+	ScmdMMove	= 0xA5,		/* move medium */
+	ScmdEStatus	= 0xB8,		/* read element status */
+	ScmdMExchange	= 0xA6,		/* exchange medium */
+	ScmdEposition	= 0x2B,		/* position to element */
+
+	ScmdReadDVD	= 0xAD,		/* read dvd structure */
+	ScmdReportKey	= 0xA4,		/* read dvd key */
+	ScmdSendKey	= 0xA3,		/* write dvd key */
+
+	ScmdClosetracksess= 0x5B,
+	ScmdRead12	= 0xA8,
+	ScmdSetcdspeed	= 0xBB,
+	ScmdReadcd	= 0xBE,
+
+	/* vendor-specific */
+	ScmdFwaddr	= 0xE2,		/* first writeable address */
+	ScmdTreserve	= 0xE4,		/* reserve track */
+	ScmdTinfo	= 0xE5,		/* read track info */
+	ScmdTwrite	= 0xE6,		/* write track */
+	ScmdMload	= 0xE7,		/* medium load/unload */
+	ScmdFixation	= 0xE9,		/* fixation */
+};
+
+enum {
+	/* sense data byte 0 */
+	Sd0valid	= 0x80,		/* valid sense data present */
+
+	/* sense data byte 2 */
+	/* incorrect-length indicator, difference in bytes 3—6 */
+	Sd2ili		= 0x20,
+	Sd2eom		= 0x40,		/* end of medium (tape) */
+	Sd2filemark	= 0x80,		/* at a filemark (tape) */
+
+	/* command byte 1 */
+	Cmd1fixed	= 1,		/* use fixed-length blocks */
+	Cmd1sili	= 2,		/* don't set Sd2ili */
+
+	/* limit of block #s in 24-bit ccbs */
+	Max24off	= (1<<21) - 1,	/* 2⁲ⁱ - 1 */
+
+	/* mode pages */
+	Allmodepages = 0x3F,
+};
+
+/* scsi device types, from the scsi standards */
+enum {
+	Devdir,			/* usually disk */
+	Devseq,			/* usually tape */
+	Devprint,
+	Dev3,
+	Devworm,		/* also direct, but special */
+	Devcd,			/* also direct */
+	Dev6,
+	Devmo,			/* also direct */
+	Devjuke,
+};
+
+/* p arguments should be of type uchar* */
+#define GETBELONG(p) ((ulong)(p)[0]<<24 | (ulong)(p)[1]<<16 | (p)[2]<<8 | (p)[3])
+#define PUTBELONG(p, ul) ((p)[0] = (ul)>>24, (p)[1] = (ul)>>16, \
+			  (p)[2] = (ul)>>8,  (p)[3] = (ul))
+#define GETBE24(p)	((ulong)(p)[0]<<16 | (p)[1]<<8 | (p)[2])
+#define PUTBE24(p, ul)	((p)[0] = (ul)>>16, (p)[1] = (ul)>>8, (p)[2] = (ul))
+
+long	SRready(ScsiReq*);
+long	SRrewind(ScsiReq*);
+long	SRreqsense(ScsiReq*);
+long	SRformat(ScsiReq*);
+long	SRrblimits(ScsiReq*, uchar*);
+long	SRread(ScsiReq*, void*, long);
+long	SRwrite(ScsiReq*, void*, long);
+long	SRseek(ScsiReq*, long, int);
+long	SRfilemark(ScsiReq*, ulong);
+long	SRspace(ScsiReq*, uchar, long);
+long	SRinquiry(ScsiReq*);
+long	SRmodeselect6(ScsiReq*, uchar*, long);
+long	SRmodeselect10(ScsiReq*, uchar*, long);
+long	SRmodesense6(ScsiReq*, uchar, uchar*, long);
+long	SRmodesense10(ScsiReq*, uchar, uchar*, long);
+long	SRstart(ScsiReq*, uchar);
+long	SRrcapacity(ScsiReq*, uchar*);
+long	SRrcapacity16(ScsiReq*, uchar*);
+
+long	SRblank(ScsiReq*, uchar, uchar);	/* MMC CD-R/CD-RW commands */
+long	SRsynccache(ScsiReq*);
+long	SRTOC(ScsiReq*, void*, int, uchar, uchar);
+long	SRrdiscinfo(ScsiReq*, void*, int);
+long	SRrtrackinfo(ScsiReq*, void*, int, int);
+
+long	SRcdpause(ScsiReq*, int);		/* MMC CD audio commands */
+long	SRcdstop(ScsiReq*);
+long	SRcdload(ScsiReq*, int, int);
+long	SRcdplay(ScsiReq*, int, long, long);
+long	SRcdstatus(ScsiReq*, uchar*, int);
+long	SRgetconf(ScsiReq*, uchar*, int);
+
+/*	old CD-R/CD-RW commands */
+long	SRfwaddr(ScsiReq*, uchar, uchar, uchar, uchar*);
+long	SRtreserve(ScsiReq*, long);
+long	SRtinfo(ScsiReq*, uchar, uchar*);
+long	SRwtrack(ScsiReq*, void*, long, uchar, uchar);
+long	SRmload(ScsiReq*, uchar);
+long	SRfixation(ScsiReq*, uchar);
+
+long	SReinitialise(ScsiReq*);		/* CHANGER commands */
+long	SRestatus(ScsiReq*, uchar, uchar*, int);
+long	SRmmove(ScsiReq*, int, int, int, int);
+
+long	SRrequest(ScsiReq*);
+int	SRclose(ScsiReq*);
+int	SRopenraw(ScsiReq*, char*);
+int	SRopen(ScsiReq*, char*);
+
+void	makesense(ScsiReq*);
+
+long	umsrequest(struct Umsc*, ScsiPtr*, ScsiPtr*, int*);
+
+void	scsidebug(int);
+
+char*	scsierrmsg(int n);
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/ums.h
@@ -1,0 +1,124 @@
+/*
+ * mass storage transport protocols and subclasses,
+ * from usb mass storage class specification overview rev 1.2
+ */
+
+typedef struct Umsc Umsc;
+typedef struct Ums Ums;
+typedef struct Cbw Cbw;			/* command block wrapper */
+typedef struct Csw Csw;			/* command status wrapper */
+typedef struct Part Part;
+
+enum
+{
+	Protocbi =	0,	/* control/bulk/interrupt; mainly floppies */
+	Protocb =	1,	/*   "  with no interrupt; mainly floppies */
+	Protobulk =	0x50,	/* bulk only */
+
+	Subrbc =	1,	/* reduced blk cmds */
+	Subatapi =	2,	/* cd/dvd using sff-8020i or mmc-2 cmd blks */
+	Subqic 	=	3,	/* QIC-157 tapes */
+	Subufi =	4,	/* floppy */
+	Sub8070 =	5,	/* removable media, atapi-like */
+	Subscsi =	6,	/* scsi transparent cmd set */
+	Subisd200 =	7,	/* ISD200 ATA */
+	Subdev =	0xff,	/* use device's value */
+
+	Umsreset =	0xFF,
+	Getmaxlun =	0xFE,
+
+//	Maxlun		= 256,
+	Maxlun		= 32,
+
+	CMreset = 1,
+
+	Pcmd = 0,
+	Pdata,
+	Pstatus,
+
+	CbwLen		= 31,
+	CbwDataIn	= 0x80,
+	CbwDataOut	= 0x00,
+	CswLen		= 13,
+	CswOk		= 0,
+	CswFailed	= 1,
+	CswPhaseErr	= 2,
+	
+	Maxparts		= 16,
+};
+
+/*
+ * corresponds to a lun.
+ * these are ~600+Maxiosize bytes each; ScsiReq is not tiny.
+ */
+
+struct Part
+{
+	int id;
+	int inuse;
+	int vers;
+	ulong mode;
+	char	*name;
+	vlong offset;		/* in lbsize units */
+	vlong length;		/* in lbsize units */
+};
+
+
+struct Umsc
+{
+	ScsiReq;
+	uvlong	blocks;
+	vlong	capacity;
+
+	/* from setup */
+	char	*bufp;
+	long	off;		/* offset within a block */
+	long	nb;		/* byte count */
+
+	/* partitions */
+	Part part[Maxparts];
+
+	uchar 	rawcmd[10];
+	uchar	phase;
+	char	*inq;
+	Ums	*ums;
+	char	buf[Maxiosize];
+};
+
+struct Ums
+{
+	QLock;
+	Dev	*dev;
+	Dev	*epin;
+	Dev	*epout;
+	Umsc	*lun;
+	uchar	maxlun;
+	int	seq;
+	int	nerrs;
+	int	wrongresidues;
+};
+
+/*
+ * USB transparent SCSI devices
+ */
+struct Cbw
+{
+	char	signature[4];		/* "USBC" */
+	long	tag;
+	long	datalen;
+	uchar	flags;
+	uchar	lun;
+	uchar	len;
+	char	command[16];
+};
+
+struct Csw
+{
+	char	signature[4];		/* "USBS" */
+	long	tag;
+	long	dataresidue;
+	uchar	status;
+};
+
+
+int	diskmain(Dev*, int, char**);
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/hid.h
@@ -1,0 +1,65 @@
+/*
+ * USB keyboard/mouse constants
+ */
+enum {
+
+	Stack = 32 * 1024,
+
+	/* HID class subclass protocol ids */
+	PtrCSP		= 0x020103,	/* mouse.boot.hid */
+	KbdCSP		= 0x010103,	/* keyboard.boot.hid */
+
+	/* Requests */
+	Getreport = 0x01,
+	Setreport = 0x09,
+	Getproto	= 0x03,
+	Setproto	= 0x0b,
+
+	/* protocols for SET_PROTO request */
+	Bootproto	= 0,
+	Reportproto	= 1,
+
+	/* protocols for SET_REPORT request */
+	Reportout = 0x0200,
+};
+
+enum {
+	/* keyboard modifier bits */
+	Mlctrl		= 0,
+	Mlshift		= 1,
+	Mlalt		= 2,
+	Mlgui		= 3,
+	Mrctrl		= 4,
+	Mrshift		= 5,
+	Mralt		= 6,
+	Mrgui		= 7,
+
+	/* masks for byte[0] */
+	Mctrl		= 1<<Mlctrl | 1<<Mrctrl,
+	Mshift		= 1<<Mlshift | 1<<Mrshift,
+	Malt		= 1<<Mlalt | 1<<Mralt,
+	Mcompose	= 1<<Mlalt,
+	Maltgr		= 1<<Mralt,
+	Mgui		= 1<<Mlgui | 1<<Mrgui,
+
+	MaxAcc = 3,			/* max. ptr acceleration */
+	PtrMask= 0xf,			/* 4 buttons: should allow for more. */
+
+};
+
+/*
+ * Plan 9 keyboard driver constants.
+ */
+enum {
+	/* Scan codes (see kbd.c) */
+	SCesc1		= 0xe0,		/* first of a 2-character sequence */
+	SCesc2		= 0xe1,
+	SClshift		= 0x2a,
+	SCrshift		= 0x36,
+	SCctrl		= 0x1d,
+	SCcompose	= 0x38,
+	Keyup		= 0x80,		/* flag bit */
+	Keymask		= 0x7f,		/* regular scan code bits */
+};
+
+int kbmain(Dev *d, int argc, char*argv[]);
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/kb.c
@@ -1,0 +1,595 @@
+/*
+ * USB Human Interaction Device: keyboard and mouse.
+ *
+ * If there's no usb keyboard, it tries to setup the mouse, if any.
+ * It should be started at boot time.
+ *
+ * Mouse events are converted to the format of mouse(3)'s
+ * mousein file.
+ * Keyboard keycodes are translated to scan codes and sent to kbin(3).
+ *
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "hid.h"
+
+enum
+{
+	Awakemsg=0xdeaddead,
+	Diemsg = 0xbeefbeef,
+};
+
+typedef struct KDev KDev;
+typedef struct Kin Kin;
+
+struct KDev
+{
+	Dev*	dev;		/* usb device*/
+	Dev*	ep;		/* endpoint to get events */
+	Kin*	in;		/* used to send events to kernel */
+	Channel*repeatc;	/* only for keyboard */
+	int	accel;		/* only for mouse */
+};
+
+/*
+ * Kbdin and mousein files must be shared among all instances.
+ */
+struct Kin
+{
+	int	ref;
+	int	fd;
+	char*	name;
+};
+
+/*
+ * Map for the logitech bluetooth mouse with 8 buttons and wheels.
+ *	{ ptr ->mouse}
+ *	{ 0x01, 0x01 },	// left
+ *	{ 0x04, 0x02 },	// middle
+ *	{ 0x02, 0x04 },	// right
+ *	{ 0x40, 0x08 },	// up
+ *	{ 0x80, 0x10 },	// down
+ *	{ 0x10, 0x08 },	// side up
+ *	{ 0x08, 0x10 },	// side down
+ *	{ 0x20, 0x02 }, // page
+ * besides wheel and regular up/down report the 4th byte as 1/-1
+ */
+
+/*
+ * key code to scan code; for the page table used by
+ * the logitech bluetooth keyboard.
+ */
+static char sctab[256] = 
+{
+[0x00]	0x0,	0x0,	0x0,	0x0,	0x1e,	0x30,	0x2e,	0x20,
+[0x08]	0x12,	0x21,	0x22,	0x23,	0x17,	0x24,	0x25,	0x26,
+[0x10]	0x32,	0x31,	0x18,	0x19,	0x10,	0x13,	0x1f,	0x14,
+[0x18]	0x16,	0x2f,	0x11,	0x2d,	0x15,	0x2c,	0x2,	0x3,
+[0x20]	0x4,	0x5,	0x6,	0x7,	0x8,	0x9,	0xa,	0xb,
+[0x28]	0x1c,	0x1,	0xe,	0xf,	0x39,	0xc,	0xd,	0x1a,
+[0x30]	0x1b,	0x2b,	0x2b,	0x27,	0x28,	0x29,	0x33,	0x34,
+[0x38]	0x35,	0x3a,	0x3b,	0x3c,	0x3d,	0x3e,	0x3f,	0x40,
+[0x40]	0x41,	0x42,	0x43,	0x44,	0x57,	0x58,	0x63,	0x46,
+[0x48]	0x77,	0x52,	0x47,	0x49,	0x53,	0x4f,	0x51,	0x4d,
+[0x50]	0x4b,	0x50,	0x48,	0x45,	0x35,	0x37,	0x4a,	0x4e,
+[0x58]	0x1c,	0x4f,	0x50,	0x51,	0x4b,	0x4c,	0x4d,	0x47,
+[0x60]	0x48,	0x49,	0x52,	0x53,	0x56,	0x7f,	0x74,	0x75,
+[0x68]	0x55,	0x59,	0x5a,	0x5b,	0x5c,	0x5d,	0x5e,	0x5f,
+[0x70]	0x78,	0x79,	0x7a,	0x7b,	0x0,	0x0,	0x0,	0x0,
+[0x78]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x71,
+[0x80]	0x73,	0x72,	0x0,	0x0,	0x0,	0x7c,	0x0,	0x0,
+[0x88]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0x90]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0x98]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xa0]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xa8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xb0]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xb8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xc0]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xc8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xd0]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xd8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xe0]	0x1d,	0x2a,	0x38,	0x7d,	0x61,	0x36,	0x64,	0x7e,
+[0xe8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x73,	0x72,	0x71,
+[0xf0]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+[0xf8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+};
+
+static QLock inlck;
+static Kin kbdin =
+{
+	.ref = 0,
+	.name = "/dev/kbin",
+	.fd = -1,
+};
+static Kin ptrin =
+{
+	.ref = 0,
+	.name = "#m/mousein",
+	.fd = -1,
+};
+
+static int kbdebug;
+
+static int
+setbootproto(KDev* f, int eid)
+{
+	int r, id;
+
+	r = Rh2d|Rclass|Riface;
+	id = f->dev->usb->ep[eid]->iface->id;
+	return usbcmd(f->dev, r, Setproto, Bootproto, id, nil, 0);
+}
+
+static int
+setleds(KDev* f, int, uchar leds)
+{
+	return usbcmd(f->dev, Rh2d|Rclass|Riface, Setreport, Reportout, 0, &leds, 1);
+}
+
+/*
+ * Try to recover from a babble error. A port reset is the only way out.
+ * BUG: we should be careful not to reset a bundle with several devices.
+ */
+static void
+recoverkb(KDev *f)
+{
+	int i;
+
+	close(f->dev->dfd);		/* it's for usbd now */
+	devctl(f->dev, "reset");
+	for(i = 0; i < 10; i++){
+		sleep(500);
+		if(opendevdata(f->dev, ORDWR) >= 0){
+			setbootproto(f, f->ep->id);
+			break;
+		}
+		/* else usbd still working... */
+	}
+}
+
+static void
+kbfatal(KDev *kd, char *sts)
+{
+	Dev *dev;
+
+	if(sts != nil)
+		fprint(2, "kb: fatal: %s\n", sts);
+	else
+		fprint(2, "kb: exiting\n");
+	if(kd->repeatc != nil)
+		nbsendul(kd->repeatc, Diemsg);
+	dev = kd->dev;
+	kd->dev = nil;
+	if(kd->ep != nil)
+		closedev(kd->ep);
+	kd->ep = nil;
+	devctl(dev, "detach");
+	closedev(dev);
+	/*
+	 * free(kd); done by closedev.
+	 */
+	threadexits(sts);
+}
+
+static int
+scale(KDev *f, int x)
+{
+	int sign = 1;
+
+	if(x < 0){
+		sign = -1;
+		x = -x;
+	}
+	switch(x){
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		break;
+	case 4:
+		x = 6 + (f->accel>>2);
+		break;
+	case 5:
+		x = 9 + (f->accel>>1);
+		break;
+	default:
+		x *= MaxAcc;
+		break;
+	}
+	return sign*x;
+}
+
+/*
+ * ps2 mouse is processed mostly at interrupt time.
+ * for usb we do what we can.
+ */
+static void
+sethipri(void)
+{
+	char fn[30];
+	int fd;
+
+	snprint(fn, sizeof(fn), "/proc/%d/ctl", getpid());
+	fd = open(fn, OWRITE);
+	if(fd < 0)
+		return;
+	fprint(fd, "pri 13");
+	close(fd);
+}
+
+static void
+ptrwork(void* a)
+{
+	static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7};
+	int x, y, b, c, ptrfd;
+	int	mfd, nerrs;
+	char	buf[32];
+	char	mbuf[80];
+	KDev*	f = a;
+	int	hipri;
+
+	hipri = nerrs = 0;
+	ptrfd = f->ep->dfd;
+	mfd = f->in->fd;
+
+	if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
+		kbfatal(f, "weird mouse maxpkt");
+	for(;;){
+		memset(buf, 0, sizeof buf);
+		if(f->ep == nil)
+			kbfatal(f, nil);
+		c = read(ptrfd, buf, f->ep->maxpkt);
+		assert(f->dev != nil);
+		assert(f->ep != nil);
+		if(c < 0){
+			dprint(2, "kb: mouse: %s: read: %r\n", f->ep->dir);
+			if(++nerrs < 3){
+				recoverkb(f);
+				continue;
+			}
+		}
+		if(c <= 0)
+			kbfatal(f, nil);
+		if(c < 3)
+			continue;
+		if(f->accel){
+			x = scale(f, buf[1]);
+			y = scale(f, buf[2]);
+		}else{
+			x = buf[1];
+			y = buf[2];
+		}
+		b = maptab[buf[0] & 0x7];
+		if(c > 3 && buf[3] == 1)	/* up */
+			b |= 0x08;
+		if(c > 3 && buf[3] == -1)	/* down */
+			b |= 0x10;
+		if(kbdebug > 1)
+			fprint(2, "kb: m%11d %11d %11d\n", x, y, b);
+		seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b);
+		if(write(mfd, mbuf, strlen(mbuf)) < 0)
+			kbfatal(f, "mousein i/o");
+		if(hipri == 0){
+			sethipri();
+			hipri = 1;
+		}
+	}
+}
+
+static void
+stoprepeat(KDev *f)
+{
+	sendul(f->repeatc, Awakemsg);
+}
+
+static void
+startrepeat(KDev *f, uchar esc1, uchar sc)
+{
+	ulong c;
+
+	if(esc1)
+		c = SCesc1 << 8 | (sc & 0xff);
+	else
+		c = sc;
+	sendul(f->repeatc, c);
+}
+
+static void
+putscan(int kbinfd, uchar esc, uchar sc)
+{
+	uchar s[2] = {SCesc1, 0};
+
+	if(sc == 0x41){
+		kbdebug += 2;
+		return;
+	}
+	if(sc == 0x42){
+		kbdebug = 0;
+		return;
+	}
+	if(kbdebug)
+		fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc);
+	s[1] = sc;
+	if(esc && sc != 0)
+		write(kbinfd, s, 2);
+	else if(sc != 0)
+		write(kbinfd, s+1, 1);
+}
+
+static void
+repeatproc(void* a)
+{
+	KDev *f;
+	Channel *repeatc;
+	int kbdinfd;
+	ulong l, t, i;
+	uchar esc1, sc;
+
+	/*
+	 * too many jumps here.
+	 * Rewrite instead of debug, if needed.
+	 */
+	f = a;
+	repeatc = f->repeatc;
+	kbdinfd = f->in->fd;
+	l = Awakemsg;
+Repeat:
+	if(l == Diemsg)
+		goto Abort;
+	while(l == Awakemsg)
+		l = recvul(repeatc);
+	if(l == Diemsg)
+		goto Abort;
+	esc1 = l >> 8;
+	sc = l;
+	t = 160;
+	for(;;){
+		for(i = 0; i < t; i += 5){
+			if(l = nbrecvul(repeatc))
+				goto Repeat;
+			sleep(5);
+		}
+		putscan(kbdinfd, esc1, sc);
+		t = 30;
+	}
+Abort:
+	chanfree(repeatc);
+	threadexits("aborted");
+
+}
+
+
+#define hasesc1(sc)	(((sc) > 0x47) || ((sc) == 0x38))
+
+static void
+putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
+{
+	/* BUG: Should be a single write */
+	if((mods&mask) && !(omods&mask))
+		putscan(fd, esc, sc);
+	if(!(mods&mask) && (omods&mask))
+		putscan(fd, esc, Keyup|sc);
+}
+
+/*
+ * This routine diffs the state with the last known state
+ * and invents the scan codes that would have been sent
+ * by a non-usb keyboard in that case. This also requires supplying
+ * the extra esc1 byte as well as keyup flags.
+ * The aim is to allow future addition of other keycode pages
+ * for other keyboards.
+ */
+static uchar
+putkeys(KDev *f, uchar buf[], uchar obuf[], int n, uchar dk)
+{
+	int i, j;
+	uchar uk;
+	int fd;
+
+	fd = f->in->fd;
+	putmod(fd, buf[0], obuf[0], Mctrl, 0, SCctrl);
+	putmod(fd, buf[0], obuf[0], (1<<Mlshift), 0, SClshift);
+	putmod(fd, buf[0], obuf[0], (1<<Mrshift), 0, SCrshift);
+	putmod(fd, buf[0], obuf[0], Mcompose, 0, SCcompose);
+	putmod(fd, buf[0], obuf[0], Maltgr, 1, SCcompose);
+
+	/* Report key downs */
+	for(i = 2; i < n; i++){
+		for(j = 2; j < n; j++)
+			if(buf[i] == obuf[j])
+			 	break;
+		if(j == n && buf[i] != 0){
+			dk = sctab[buf[i]];
+			putscan(fd, hasesc1(dk), dk);
+			startrepeat(f, hasesc1(dk), dk);
+		}
+	}
+
+	/* Report key ups */
+	uk = 0;
+	for(i = 2; i < n; i++){
+		for(j = 2; j < n; j++)
+			if(obuf[i] == buf[j])
+				break;
+		if(j == n && obuf[i] != 0){
+			uk = sctab[obuf[i]];
+			putscan(fd, hasesc1(uk), uk|Keyup);
+		}
+	}
+	if(uk && (dk == 0 || dk == uk)){
+		stoprepeat(f);
+		dk = 0;
+	}
+	return dk;
+}
+
+static int
+kbdbusy(uchar* buf, int n)
+{
+	int i;
+
+	for(i = 1; i < n; i++)
+		if(buf[i] == 0 || buf[i] != buf[0])
+			return 0;
+	return 1;
+}
+
+static void
+kbdwork(void *a)
+{
+	int c, i, kbdfd, nerrs;
+	uchar dk, buf[64], lbuf[64];
+	char err[128];
+	KDev *f = a;
+
+	kbdfd = f->ep->dfd;
+
+	if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
+		kbfatal(f, "weird maxpkt");
+
+	f->repeatc = chancreate(sizeof(ulong), 0);
+	if(f->repeatc == nil)
+		kbfatal(f, "chancreate failed");
+
+	proccreate(repeatproc, f, Stack);
+	memset(lbuf, 0, sizeof lbuf);
+	dk = nerrs = 0;
+	for(;;){
+		memset(buf, 0, sizeof buf);
+		c = read(kbdfd, buf, f->ep->maxpkt);
+		assert(f->dev != nil);
+		assert(f->ep != nil);
+		if(c < 0){
+			rerrstr(err, sizeof(err));
+			fprint(2, "kb: %s: read: %s\n", f->ep->dir, err);
+			if(strstr(err, "babble") != 0 && ++nerrs < 3){
+				recoverkb(f);
+				continue;
+			}
+		}
+		if(c <= 0)
+			kbfatal(f, nil);
+		if(c < 3)
+			continue;
+		if(kbdbusy(buf + 2, c - 2))
+			continue;
+		if(usbdebug > 2 || kbdebug > 1){
+			fprint(2, "kbd mod %x: ", buf[0]);
+			for(i = 2; i < c; i++)
+				fprint(2, "kc %x ", buf[i]);
+			fprint(2, "\n");
+		}
+		dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk);
+		memmove(lbuf, buf, c);
+		nerrs = 0;
+	}
+}
+
+static void
+freekdev(void *a)
+{
+	KDev *kd;
+
+	kd = a;
+	if(kd->in != nil){
+		qlock(&inlck);
+		if(--kd->in->ref == 0){
+			close(kd->in->fd);
+			kd->in->fd = -1;
+		}
+		qunlock(&inlck);
+	}
+	dprint(2, "freekdev\n");
+	free(kd);
+}
+
+static void
+kbstart(Dev *d, Ep *ep, Kin *in, void (*f)(void*), int accel)
+{
+	KDev *kd;
+
+	qlock(&inlck);
+	if(in->fd < 0){
+		in->fd = open(in->name, OWRITE);
+		if(in->fd < 0){
+			fprint(2, "kb: %s: %r\n", in->name);
+			qunlock(&inlck);
+			return;
+		}
+	}
+	in->ref++;	/* for kd->in = in */
+	qunlock(&inlck);
+	kd = d->aux = emallocz(sizeof(KDev), 1);
+	d->free = freekdev;
+	kd->in = in;
+	kd->dev = d;
+	if(setbootproto(kd, ep->id) < 0){
+		fprint(2, "kb: %s: bootproto: %r\n", d->dir);
+		return;
+	}
+	kd->accel = accel;
+	kd->ep = openep(d, ep->id);
+	if(kd->ep == nil){
+		fprint(2, "kb: %s: openep %d: %r\n", d->dir, ep->id);
+		return;
+	}
+	if(opendevdata(kd->ep, OREAD) < 0){
+		fprint(2, "kb: %s: opendevdata: %r\n", kd->ep->dir);
+		closedev(kd->ep);
+		kd->ep = nil;
+		return;
+	}
+	if(setleds(kd, ep->id, 0) < 0){
+		fprint(2, "kb: %s: setleds: %r\n", d->dir);
+		return;
+	}
+	incref(d);
+	proccreate(f, kd, Stack);
+}
+
+static void
+usage(void)
+{
+	werrstr("usage: usb/kb [-dkm] [-a n] [-N nb]");
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char* argv[])
+{
+	int accel, i;
+	Dev *d;
+	Ep *ep;
+	Usbdev *ud;
+
+	accel = 0;
+	ARGBEGIN{
+	case 'a':
+		accel = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'd':
+		kbdebug++;
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc != 1)
+		usage();
+	d = getdev(atoi(*argv));
+	if(d == nil)
+		sysfatal("getdev: %r");
+	ud = d->usb;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			break;
+		if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == KbdCSP)
+			kbstart(d, ep, &kbdin, kbdwork, accel);
+		if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == PtrCSP)
+			kbstart(d, ep, &ptrin, ptrwork, accel);
+	}
+	threadexits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/mkfile
@@ -1,0 +1,20 @@
+</$objtype/mkfile
+
+TARG=kb
+OFILES=kb.$O
+HFILES=\
+	../lib/usb.h\
+	hid.h\
+
+LIB=../lib/usb.a$O
+
+BIN=/$objtype/bin/nusb
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
+
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/dev.c
@@ -1,0 +1,512 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+
+/*
+ * epN.M -> N
+ */
+static int
+nameid(char *s)
+{
+	char *r;
+	char nm[20];
+
+	r = strrchr(s, 'p');
+	if(r == nil)
+		return -1;
+	strecpy(nm, nm+sizeof(nm), r+1);
+	r = strchr(nm, '.');
+	if(r == nil)
+		return -1;
+	*r = 0;
+	return atoi(nm);
+}
+
+Dev*
+openep(Dev *d, int id)
+{
+	char *mode;	/* How many modes? */
+	Ep *ep;
+	Altc *ac;
+	Dev *epd;
+	Usbdev *ud;
+	char name[40];
+
+	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
+		return nil;
+	if(d->cfd < 0 || d->usb == nil){
+		werrstr("device not configured");
+		return nil;
+	}
+	ud = d->usb;
+	if(id < 0 || id >= nelem(ud->ep) || ud->ep[id] == nil){
+		werrstr("bad enpoint number");
+		return nil;
+	}
+	ep = ud->ep[id];
+	mode = "rw";
+	if(ep->dir == Ein)
+		mode = "r";
+	if(ep->dir == Eout)
+		mode = "w";
+	snprint(name, sizeof(name), "/dev/usb/ep%d.%d", d->id, id);
+	if(access(name, AEXIST) == 0){
+		dprint(2, "%s: %s already exists; trying to open\n", argv0, name);
+		epd = opendev(name);
+		if(epd != nil)
+			epd->maxpkt = ep->maxpkt;	/* guess */
+		return epd;
+	}
+	if(devctl(d, "new %d %d %s", id, ep->type, mode) < 0){
+		dprint(2, "%s: %s: new: %r\n", argv0, d->dir);
+		return nil;
+	}
+	epd = opendev(name);
+	if(epd == nil)
+		return nil;
+	epd->id = id;
+	if(devctl(epd, "maxpkt %d", ep->maxpkt) < 0)
+		fprint(2, "%s: %s: openep: maxpkt: %r\n", argv0, epd->dir);
+	else
+		dprint(2, "%s: %s: maxpkt %d\n", argv0, epd->dir, ep->maxpkt);
+	epd->maxpkt = ep->maxpkt;
+	ac = ep->iface->altc[0];
+	if(ep->ntds > 1 && devctl(epd, "ntds %d", ep->ntds) < 0)
+		fprint(2, "%s: %s: openep: ntds: %r\n", argv0, epd->dir);
+	else
+		dprint(2, "%s: %s: ntds %d\n", argv0, epd->dir, ep->ntds);
+
+	/*
+	 * For iso endpoints and high speed interrupt endpoints the pollival is
+	 * actually 2ⁿ and not n.
+	 * The kernel usb driver must take that into account.
+	 * It's simpler this way.
+	 */
+
+	if(ac != nil && (ep->type == Eintr || ep->type == Eiso) && ac->interval != 0)
+		if(devctl(epd, "pollival %d", ac->interval) < 0)
+			fprint(2, "%s: %s: openep: pollival: %r\n", argv0, epd->dir);
+	return epd;
+}
+
+Dev*
+opendev(char *fn)
+{
+	Dev *d;
+	int l;
+
+	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
+		return nil;
+	d = emallocz(sizeof(Dev), 1);
+	incref(d);
+
+	l = strlen(fn);
+	d->dfd = -1;
+	/*
+	 * +30 to allocate extra size to concat "/<epfilename>"
+	 * we should probably remove that feature from the manual
+	 * and from the code after checking out that nobody relies on
+	 * that.
+	 */
+	d->dir = emallocz(l + 30, 0);
+	strcpy(d->dir, fn);
+	strcpy(d->dir+l, "/ctl");
+	d->cfd = open(d->dir, ORDWR|OCEXEC);
+	d->dir[l] = 0;
+	d->id = nameid(fn);
+	if(d->cfd < 0){
+		werrstr("can't open endpoint %s: %r", d->dir);
+		free(d->dir);
+		free(d);
+		return nil;
+	}
+	dprint(2, "%s: opendev %#p %s\n", argv0, d, fn);
+	return d;
+}
+
+int
+opendevdata(Dev *d, int mode)
+{
+	char buf[80]; /* more than enough for a usb path */
+
+	seprint(buf, buf+sizeof(buf), "%s/data", d->dir);
+	d->dfd = open(buf, mode|OCEXEC);
+	return d->dfd;
+}
+
+enum
+{
+	/*
+	 * Max device conf is also limited by max control request size as
+	 * limited by Maxctllen in the kernel usb.h (both limits are arbitrary).
+	 */
+	Maxdevconf = 4 * 1024,	/* asking for 16K kills Newsham's disk */
+};
+
+int
+loaddevconf(Dev *d, int n)
+{
+	uchar *buf;
+	int nr;
+	int type;
+
+	if(n >= nelem(d->usb->conf)){
+		werrstr("loaddevconf: bug: out of configurations in device");
+		fprint(2, "%s: %r\n", argv0);
+		return -1;
+	}
+	buf = emallocz(Maxdevconf, 0);
+	type = Rd2h|Rstd|Rdev;
+	nr = usbcmd(d, type, Rgetdesc, Dconf<<8|n, 0, buf, Maxdevconf);
+	if(nr < Dconflen){
+		free(buf);
+		return -1;
+	}
+	if(d->usb->conf[n] == nil)
+		d->usb->conf[n] = emallocz(sizeof(Conf), 1);
+	nr = parseconf(d->usb, d->usb->conf[n], buf, nr);
+	free(buf);
+	return nr;
+}
+
+Ep*
+mkep(Usbdev *d, int id)
+{
+	Ep *ep;
+
+	d->ep[id] = ep = emallocz(sizeof(Ep), 1);
+	ep->id = id;
+	return ep;
+}
+
+static char*
+mkstr(uchar *b, int n)
+{
+	Rune r;
+	char *us;
+	char *s;
+	char *e;
+
+	if(n <= 2 || (n & 1) != 0)
+		return strdup("none");
+	n = (n - 2)/2;
+	b += 2;
+	us = s = emallocz(n*UTFmax+1, 0);
+	e = s + n*UTFmax+1;
+	for(; --n >= 0; b += 2){
+		r = GET2(b);
+		s = seprint(s, e, "%C", r);
+	}
+	return us;
+}
+
+char*
+loaddevstr(Dev *d, int sid)
+{
+	uchar buf[128];
+	int type;
+	int nr;
+
+	if(sid == 0)
+		return estrdup("none");
+	type = Rd2h|Rstd|Rdev;
+	nr=usbcmd(d, type, Rgetdesc, Dstr<<8|sid, 0, buf, sizeof(buf));
+	return mkstr(buf, nr);
+}
+
+int
+loaddevdesc(Dev *d)
+{
+	uchar buf[Ddevlen+255];
+	int nr;
+	int type;
+	Ep *ep0;
+
+	type = Rd2h|Rstd|Rdev;
+	nr = sizeof(buf);
+	memset(buf, 0, Ddevlen);
+	if((nr=usbcmd(d, type, Rgetdesc, Ddev<<8|0, 0, buf, nr)) < 0)
+		return -1;
+	/*
+	 * Several hubs are returning descriptors of 17 bytes, not 18.
+	 * We accept them and leave number of configurations as zero.
+	 * (a get configuration descriptor also fails for them!)
+	 */
+	if(nr < Ddevlen){
+		print("%s: %s: warning: device with short descriptor\n",
+			argv0, d->dir);
+		if(nr < Ddevlen-1){
+			werrstr("short device descriptor (%d bytes)", nr);
+			return -1;
+		}
+	}
+	d->usb = emallocz(sizeof(Usbdev), 1);
+	ep0 = mkep(d->usb, 0);
+	ep0->dir = Eboth;
+	ep0->type = Econtrol;
+	ep0->maxpkt = d->maxpkt = 8;		/* a default */
+	nr = parsedev(d, buf, nr);
+	if(nr >= 0){
+		d->usb->vendor = loaddevstr(d, d->usb->vsid);
+		if(strcmp(d->usb->vendor, "none") != 0){
+			d->usb->product = loaddevstr(d, d->usb->psid);
+			d->usb->serial = loaddevstr(d, d->usb->ssid);
+		}
+	}
+	return nr;
+}
+
+int
+configdev(Dev *d)
+{
+	int i;
+
+	if(d->dfd < 0)
+		opendevdata(d, ORDWR);
+	if(d->dfd < 0)
+		return -1;
+	if(loaddevdesc(d) < 0)
+		return -1;
+	for(i = 0; i < d->usb->nconf; i++)
+		if(loaddevconf(d, i) < 0)
+			return -1;
+	return 0;
+}
+
+static void
+closeconf(Conf *c)
+{
+	int i;
+	int a;
+
+	if(c == nil)
+		return;
+	for(i = 0; i < nelem(c->iface); i++)
+		if(c->iface[i] != nil){
+			for(a = 0; a < nelem(c->iface[i]->altc); a++)
+				free(c->iface[i]->altc[a]);
+			free(c->iface[i]);
+		}
+	free(c);
+}
+
+void
+closedev(Dev *d)
+{
+	int i;
+	Usbdev *ud;
+
+	if(d==nil || decref(d) != 0)
+		return;
+	dprint(2, "%s: closedev %#p %s\n", argv0, d, d->dir);
+	if(d->free != nil)
+		d->free(d->aux);
+	if(d->cfd >= 0)
+		close(d->cfd);
+	if(d->dfd >= 0)
+		close(d->dfd);
+	d->cfd = d->dfd = -1;
+	free(d->dir);
+	d->dir = nil;
+	ud = d->usb;
+	d->usb = nil;
+	if(ud != nil){
+		free(ud->vendor);
+		free(ud->product);
+		free(ud->serial);
+		for(i = 0; i < nelem(ud->ep); i++)
+			free(ud->ep[i]);
+		for(i = 0; i < nelem(ud->ddesc); i++)
+			free(ud->ddesc[i]);
+
+		for(i = 0; i < nelem(ud->conf); i++)
+			closeconf(ud->conf[i]);
+		free(ud);
+	}
+	free(d);
+}
+
+static char*
+reqstr(int type, int req)
+{
+	char *s;
+	static char* ds[] = { "dev", "if", "ep", "oth" };
+	static char buf[40];
+
+	if(type&Rd2h)
+		s = seprint(buf, buf+sizeof(buf), "d2h");
+	else
+		s = seprint(buf, buf+sizeof(buf), "h2d");
+	if(type&Rclass)
+		s = seprint(s, buf+sizeof(buf), "|cls");
+	else if(type&Rvendor)
+		s = seprint(s, buf+sizeof(buf), "|vnd");
+	else
+		s = seprint(s, buf+sizeof(buf), "|std");
+	s = seprint(s, buf+sizeof(buf), "|%s", ds[type&3]);
+
+	switch(req){
+	case Rgetstatus: s = seprint(s, buf+sizeof(buf), " getsts"); break;
+	case Rclearfeature: s = seprint(s, buf+sizeof(buf), " clrfeat"); break;
+	case Rsetfeature: s = seprint(s, buf+sizeof(buf), " setfeat"); break;
+	case Rsetaddress: s = seprint(s, buf+sizeof(buf), " setaddr"); break;
+	case Rgetdesc: s = seprint(s, buf+sizeof(buf), " getdesc"); break;
+	case Rsetdesc: s = seprint(s, buf+sizeof(buf), " setdesc"); break;
+	case Rgetconf: s = seprint(s, buf+sizeof(buf), " getcnf"); break;
+	case Rsetconf: s = seprint(s, buf+sizeof(buf), " setcnf"); break;
+	case Rgetiface: s = seprint(s, buf+sizeof(buf), " getif"); break;
+	case Rsetiface: s = seprint(s, buf+sizeof(buf), " setif"); break;
+	}
+	USED(s);
+	return buf;
+}
+
+static int
+cmdreq(Dev *d, int type, int req, int value, int index, uchar *data, int count)
+{
+	int ndata, n;
+	uchar *wp;
+	uchar buf[8];
+	char *hd, *rs;
+
+	assert(d != nil);
+	if(data == nil){
+		wp = buf;
+		ndata = 0;
+	}else{
+		ndata = count;
+		wp = emallocz(8+ndata, 0);
+	}
+	wp[0] = type;
+	wp[1] = req;
+	PUT2(wp+2, value);
+	PUT2(wp+4, index);
+	PUT2(wp+6, count);
+	if(data != nil)
+		memmove(wp+8, data, ndata);
+	if(usbdebug>2){
+		hd = hexstr(wp, ndata+8);
+		rs = reqstr(type, req);
+		fprint(2, "%s: %s val %d|%d idx %d cnt %d out[%d] %s\n",
+			d->dir, rs, value>>8, value&0xFF,
+			index, count, ndata+8, hd);
+		free(hd);
+	}
+	n = write(d->dfd, wp, 8+ndata);
+	if(wp != buf)
+		free(wp);
+	if(n < 0)
+		return -1;
+	if(n != 8+ndata){
+		dprint(2, "%s: cmd: short write: %d\n", argv0, n);
+		return -1;
+	}
+	return n;
+}
+
+static int
+cmdrep(Dev *d, void *buf, int nb)
+{
+	char *hd;
+
+	nb = read(d->dfd, buf, nb);
+	if(nb >0 && usbdebug > 2){
+		hd = hexstr(buf, nb);
+		fprint(2, "%s: in[%d] %s\n", d->dir, nb, hd);
+		free(hd);
+	}
+	return nb;
+}
+
+int
+usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count)
+{
+	int i, r, nerr;
+	char err[64];
+
+	/*
+	 * Some devices do not respond to commands some times.
+	 * Others even report errors but later work just fine. Retry.
+	 */
+	r = -1;
+	*err = 0;
+	for(i = nerr = 0; i < Uctries; i++){
+		if(type & Rd2h)
+			r = cmdreq(d, type, req, value, index, nil, count);
+		else
+			r = cmdreq(d, type, req, value, index, data, count);
+		if(r > 0){
+			if((type & Rd2h) == 0)
+				break;
+			r = cmdrep(d, data, count);
+			if(r > 0)
+				break;
+			if(r == 0)
+				werrstr("no data from device");
+		}
+		nerr++;
+		if(*err == 0)
+			rerrstr(err, sizeof(err));
+		sleep(Ucdelay);
+	}
+	if(r > 0 && i >= 2)
+		/* let the user know the device is not in good shape */
+		fprint(2, "%s: usbcmd: %s: required %d attempts (%s)\n",
+			argv0, d->dir, i, err);
+	return r;
+}
+
+int
+unstall(Dev *dev, Dev *ep, int dir)
+{
+	int r;
+
+	if(dir == Ein)
+		dir = 0x80;
+	else
+		dir = 0;
+	r = Rh2d|Rstd|Rep;
+	if(usbcmd(dev, r, Rclearfeature, Fhalt, ep->id|dir, nil, 0)<0){
+		werrstr("unstall: %s: %r", ep->dir);
+		return -1;
+	}
+	if(devctl(ep, "clrhalt") < 0){
+		werrstr("clrhalt: %s: %r", ep->dir);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * To be sure it uses a single write.
+ */
+int
+devctl(Dev *dev, char *fmt, ...)
+{
+	char buf[128];
+	va_list arg;
+	char *e;
+
+	va_start(arg, fmt);
+	e = vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	return write(dev->cfd, buf, e-buf);
+}
+
+Dev *
+getdev(int id)
+{
+	Dev *d;
+	char buf[40];
+	
+	snprint(buf, sizeof buf, "/dev/usb/ep%d.0", id);
+	d = opendev(buf);
+	if(d == nil)
+		return nil;
+	if(configdev(d) < 0){
+		closedev(d);
+		return nil;
+	}
+	return d;
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/dump.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include "usb.h"
+
+int usbdebug;
+
+static char *edir[] = {"in", "out", "inout"};
+static char *etype[] = {"ctl", "iso", "bulk", "intr"};
+static char* cnames[] =
+{
+	"none", "audio", "comms", "hid", "",
+	"", "", "printer", "storage", "hub", "data"
+};
+static char* devstates[] =
+{
+	"detached", "attached", "enabled", "assigned", "configured"
+};
+
+char*
+classname(int c)
+{
+	static char buf[30];
+
+	if(c >= 0 && c < nelem(cnames))
+		return cnames[c];
+	else{
+		seprint(buf, buf+30, "%d", c);
+		return buf;
+	}
+}
+
+char *
+hexstr(void *a, int n)
+{
+	int i;
+	char *dbuff, *s, *e;
+	uchar *b;
+
+	b = a;
+	dbuff = s = emallocz(1024, 0);
+	*s = 0;
+	e = s + 1024;
+	for(i = 0; i < n; i++)
+		s = seprint(s, e, " %.2ux", b[i]);
+	if(s == e)
+		fprint(2, "%s: usb/lib: hexdump: bug: small buffer\n", argv0);
+	return dbuff;
+}
+
+static char *
+seprintiface(char *s, char *e, Iface *i)
+{
+	int	j;
+	Altc	*a;
+	Ep	*ep;
+	char	*eds, *ets;
+
+	s = seprint(s, e, "\t\tiface csp %s.%uld.%uld\n",
+		classname(Class(i->csp)), Subclass(i->csp), Proto(i->csp));
+	for(j = 0; j < Naltc; j++){
+		a=i->altc[j];
+		if(a == nil)
+			break;
+		s = seprint(s, e, "\t\t  alt %d attr %d ival %d",
+			j, a->attrib, a->interval);
+		if(a->aux != nil)
+			s = seprint(s, e, " devspec %p\n", a->aux);
+		else
+			s = seprint(s, e, "\n");
+	}
+	for(j = 0; j < Nep; j++){
+		ep = i->ep[j];
+		if(ep == nil)
+			break;
+		eds = ets = "";
+		if(ep->dir <= nelem(edir))
+			eds = edir[ep->dir];
+		if(ep->type <= nelem(etype))
+			ets = etype[ep->type];
+		s = seprint(s, e, "\t\t  ep id %d addr %d dir %s type %s"
+			" itype %d maxpkt %d ntds %d\n",
+			ep->id, ep->addr, eds, ets, ep->isotype,
+			ep->maxpkt, ep->ntds);
+	}
+	return s;
+}
+
+static char*
+seprintconf(char *s, char *e, Usbdev *d, int ci)
+{
+	int i;
+	Conf *c;
+	char *hd;
+
+	c = d->conf[ci];
+	s = seprint(s, e, "\tconf: cval %d attrib %x %d mA\n",
+		c->cval, c->attrib, c->milliamps);
+	for(i = 0; i < Niface; i++)
+		if(c->iface[i] == nil)
+			break;
+		else
+			s = seprintiface(s, e, c->iface[i]);
+	for(i = 0; i < Nddesc; i++)
+		if(d->ddesc[i] == nil)
+			break;
+		else if(d->ddesc[i]->conf == c){
+			hd = hexstr((uchar*)&d->ddesc[i]->data,
+				d->ddesc[i]->data.bLength);
+			s = seprint(s, e, "\t\tdev desc %x[%d]: %s\n",
+				d->ddesc[i]->data.bDescriptorType,
+				d->ddesc[i]->data.bLength, hd);
+			free(hd);
+		}
+	return s;
+}
+
+int
+Ufmt(Fmt *f)
+{
+	int i;
+	Dev *d;
+	Usbdev *ud;
+	char buf[1024];
+	char *s, *e;
+
+	s = buf;
+	e = buf+sizeof(buf);
+	d = va_arg(f->args, Dev*);
+	if(d == nil)
+		return fmtprint(f, "<nildev>\n");
+	s = seprint(s, e, "%s", d->dir);
+	ud = d->usb;
+	if(ud == nil)
+		return fmtprint(f, "%s %ld refs\n", buf, d->ref);
+	s = seprint(s, e, " csp %s.%uld.%uld",
+		classname(Class(ud->csp)), Subclass(ud->csp), Proto(ud->csp));
+	s = seprint(s, e, " vid %#ux did %#ux", ud->vid, ud->did);
+	s = seprint(s, e, " refs %ld\n", d->ref);
+	s = seprint(s, e, "\t%s %s %s\n", ud->vendor, ud->product, ud->serial);
+	for(i = 0; i < Nconf; i++){
+		if(ud->conf[i] == nil)
+			break;
+		else
+			s = seprintconf(s, e, ud, i);
+	}
+	return fmtprint(f, "%s", buf);
+}
+
+char*
+estrdup(char *s)
+{
+	char *d;
+
+	d = strdup(s);
+	if(d == nil)
+		sysfatal("strdup: %r");
+	setmalloctag(d, getcallerpc(&s));
+	return d;
+}
+
+void*
+emallocz(ulong size, int zero)
+{
+	void *x;
+
+	x = malloc(size);
+	if(x == nil)
+		sysfatal("malloc: %r");
+	if(zero)
+		memset(x, 0, size);
+	setmalloctag(x, getcallerpc(&size));
+	return x;
+}
+
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/mkfile
@@ -1,0 +1,24 @@
+</$objtype/mkfile
+
+LIB=usb.a$O
+OFILES=\
+	dev.$O\
+	dump.$O\
+	parse.$O\
+
+HFILES=\
+	usb.h\
+
+UPDATE=\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	mkfile\
+
+</sys/src/cmd/mklib
+
+install:V:	$LIB
+	date
+safeinstall:V: install
+safeinstallall:V: installall
+nuke:V:
+	rm -f *.[$OS] y.tab.? y.output y.error *.a[$OS]
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/parse.c
@@ -1,0 +1,270 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include "usb.h"
+
+int
+parsedev(Dev *xd, uchar *b, int n)
+{
+	Usbdev *d;
+	DDev *dd;
+	char *hd;
+
+	d = xd->usb;
+	assert(d != nil);
+	dd = (DDev*)b;
+	if(usbdebug>1){
+		hd = hexstr(b, Ddevlen);
+		fprint(2, "%s: parsedev %s: %s\n", argv0, xd->dir, hd);
+		free(hd);
+	}
+	if(dd->bLength < Ddevlen){
+		werrstr("short dev descr. (%d < %d)", dd->bLength, Ddevlen);
+		return -1;
+	}
+	if(dd->bDescriptorType != Ddev){
+		werrstr("%d is not a dev descriptor", dd->bDescriptorType);
+		return -1;
+	}
+	d->csp = CSP(dd->bDevClass, dd->bDevSubClass, dd->bDevProtocol);
+	d->ep[0]->maxpkt = xd->maxpkt = dd->bMaxPacketSize0;
+	d->class = dd->bDevClass;
+	d->nconf = dd->bNumConfigurations;
+	if(d->nconf == 0)
+		dprint(2, "%s: %s: no configurations\n", argv0, xd->dir);
+	d->vid = GET2(dd->idVendor);
+	d->did = GET2(dd->idProduct);
+	d->dno = GET2(dd->bcdDev);
+	d->vsid = dd->iManufacturer;
+	d->psid = dd->iProduct;
+	d->ssid = dd->iSerialNumber;
+	if(n > Ddevlen && usbdebug>1)
+		fprint(2, "%s: %s: parsedev: %d bytes left",
+			argv0, xd->dir, n - Ddevlen);
+	return Ddevlen;
+}
+
+static int
+parseiface(Usbdev *d, Conf *c, uchar *b, int n, Iface **ipp, Altc **app)
+{
+	int class, subclass, proto;
+	int ifid, altid;
+	DIface *dip;
+	Iface *ip;
+
+	assert(d != nil && c != nil);
+	if(n < Difacelen){
+		werrstr("short interface descriptor");
+		return -1;
+	}
+	dip = (DIface *)b;
+	ifid = dip->bInterfaceNumber;
+	if(ifid < 0 || ifid >= nelem(c->iface)){
+		werrstr("bad interface number %d", ifid);
+		return -1;
+	}
+	if(c->iface[ifid] == nil)
+		c->iface[ifid] = emallocz(sizeof(Iface), 1);
+	ip = c->iface[ifid];
+	class = dip->bInterfaceClass;
+	subclass = dip->bInterfaceSubClass;
+	proto = dip->bInterfaceProtocol;
+	ip->csp = CSP(class, subclass, proto);
+	if(d->csp == 0)				/* use csp from 1st iface */
+		d->csp = ip->csp;		/* if device has none */
+	if(d->class == 0)
+		d->class = class;
+	ip->id = ifid;
+	if(c == d->conf[0] && ifid == 0)	/* ep0 was already there */
+		d->ep[0]->iface = ip;
+	altid = dip->bAlternateSetting;
+	if(altid < 0 || altid >= nelem(ip->altc)){
+		werrstr("bad alternate conf. number %d", altid);
+		return -1;
+	}
+	if(ip->altc[altid] == nil)
+		ip->altc[altid] = emallocz(sizeof(Altc), 1);
+	*ipp = ip;
+	*app = ip->altc[altid];
+	return Difacelen;
+}
+
+extern Ep* mkep(Usbdev *, int);
+
+static int
+parseendpt(Usbdev *d, Conf *c, Iface *ip, Altc *altc, uchar *b, int n, Ep **epp)
+{
+	int i, dir, epid;
+	Ep *ep;
+	DEp *dep;
+
+	assert(d != nil && c != nil && ip != nil && altc != nil);
+	if(n < Deplen){
+		werrstr("short endpoint descriptor");
+		return -1;
+	}
+	dep = (DEp *)b;
+	altc->attrib = dep->bmAttributes;	/* here? */
+	altc->interval = dep->bInterval;
+
+	epid = dep->bEndpointAddress & 0xF;
+	assert(epid < nelem(d->ep));
+	if(dep->bEndpointAddress & 0x80)
+		dir = Ein;
+	else
+		dir = Eout;
+	ep = d->ep[epid];
+	if(ep == nil){
+		ep = mkep(d, epid);
+		ep->dir = dir;
+	}else if((ep->addr & 0x80) != (dep->bEndpointAddress & 0x80))
+		ep->dir = Eboth;
+	ep->maxpkt = GET2(dep->wMaxPacketSize);
+	ep->ntds = 1 + ((ep->maxpkt >> 11) & 3);
+	ep->maxpkt &= 0x7FF;
+	ep->addr = dep->bEndpointAddress;
+	ep->type = dep->bmAttributes & 0x03;
+	ep->isotype = (dep->bmAttributes>>2) & 0x03;
+	ep->conf = c;
+	ep->iface = ip;
+	for(i = 0; i < nelem(ip->ep); i++)
+		if(ip->ep[i] == nil)
+			break;
+	if(i == nelem(ip->ep)){
+		werrstr("parseendpt: bug: too many end points on interface "
+			"with csp %#lux", ip->csp);
+		fprint(2, "%s: %r\n", argv0);
+		return -1;
+	}
+	*epp = ip->ep[i] = ep;
+	return Dep;
+}
+
+static char*
+dname(int dtype)
+{
+	switch(dtype){
+	case Ddev:	return "device";
+	case Dconf: 	return "config";
+	case Dstr: 	return "string";
+	case Diface:	return "interface";
+	case Dep:	return "endpoint";
+	case Dreport:	return "report";
+	case Dphysical:	return "phys";
+	default:	return "desc";
+	}
+}
+
+int
+parsedesc(Usbdev *d, Conf *c, uchar *b, int n)
+{
+	int	len, nd, tot;
+	Iface	*ip;
+	Ep 	*ep;
+	Altc	*altc;
+	char	*hd;
+
+	assert(d != nil && c != nil);
+	tot = 0;
+	ip = nil;
+	ep = nil;
+	altc = nil;
+	for(nd = 0; nd < nelem(d->ddesc); nd++)
+		if(d->ddesc[nd] == nil)
+			break;
+
+	while(n > 2 && b[0] != 0 && b[0] <= n){
+		len = b[0];
+		if(usbdebug>1){
+			hd = hexstr(b, len);
+			fprint(2, "%s:\t\tparsedesc %s %x[%d] %s\n",
+				argv0, dname(b[1]), b[1], b[0], hd);
+			free(hd);
+		}
+		switch(b[1]){
+		case Ddev:
+		case Dconf:
+			werrstr("unexpected descriptor %d", b[1]);
+			ddprint(2, "%s\tparsedesc: %r", argv0);
+			break;
+		case Diface:
+			if(parseiface(d, c, b, n, &ip, &altc) < 0){
+				ddprint(2, "%s\tparsedesc: %r\n", argv0);
+				return -1;
+			}
+			break;
+		case Dep:
+			if(ip == nil || altc == nil){
+				werrstr("unexpected endpoint descriptor");
+				break;
+			}
+			if(parseendpt(d, c, ip, altc, b, n, &ep) < 0){
+				ddprint(2, "%s\tparsedesc: %r\n", argv0);
+				return -1;
+			}
+			break;
+		default:
+			if(nd == nelem(d->ddesc)){
+				fprint(2, "%s: parsedesc: too many "
+					"device-specific descriptors for device"
+					" %s %s\n",
+					argv0, d->vendor, d->product);
+				break;
+			}
+			d->ddesc[nd] = emallocz(sizeof(Desc)+b[0], 0);
+			d->ddesc[nd]->iface = ip;
+			d->ddesc[nd]->ep = ep;
+			d->ddesc[nd]->altc = altc;
+			d->ddesc[nd]->conf = c;
+			memmove(&d->ddesc[nd]->data, b, len);
+			++nd;
+		}
+		n -= len;
+		b += len;
+		tot += len;
+	}
+	return tot;
+}
+
+int
+parseconf(Usbdev *d, Conf *c, uchar *b, int n)
+{
+	DConf* dc;
+	int	l;
+	int	nr;
+	char	*hd;
+
+	assert(d != nil && c != nil);
+	dc = (DConf*)b;
+	if(usbdebug>1){
+		hd = hexstr(b, Dconflen);
+		fprint(2, "%s:\tparseconf  %s\n", argv0, hd);
+		free(hd);
+	}
+	if(dc->bLength < Dconflen){
+		werrstr("short configuration descriptor");
+		return -1;
+	}
+	if(dc->bDescriptorType != Dconf){
+		werrstr("not a configuration descriptor");
+		return -1;
+	}
+	c->cval = dc->bConfigurationValue;
+	c->attrib = dc->bmAttributes;
+	c->milliamps = dc->MaxPower*2;
+	l = GET2(dc->wTotalLength);
+	if(n < l){
+		werrstr("truncated configuration info");
+		return -1;
+	}
+	n -= Dconflen;
+	b += Dconflen;
+	nr = 0;
+	if(n > 0 && (nr=parsedesc(d, c, b, n)) < 0)
+		return -1;
+	n -= nr;
+	if(n > 0 && usbdebug>1)
+		fprint(2, "%s:\tparseconf: %d bytes left\n", argv0, n);
+	return l;
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/usb.h
@@ -1,0 +1,361 @@
+typedef struct Altc Altc;
+typedef struct Conf Conf;
+typedef struct DConf DConf;
+typedef struct DDesc DDesc;
+typedef struct DDev DDev;
+typedef struct DEp DEp;
+typedef struct DIface DIface;
+typedef struct Desc Desc;
+typedef struct Dev Dev;
+typedef struct Ep Ep;
+typedef struct Iface Iface;
+typedef struct Usbdev Usbdev;
+
+enum {
+	/* fundamental constants */
+	Nep	= 256,	/* max. endpoints per usb device & per interface */
+
+	/* tunable parameters */
+	Nconf	= 16,	/* max. configurations per usb device */
+	Nddesc	= 8*Nep, /* max. device-specific descriptors per usb device */
+	Niface	= 16,	/* max. interfaces per configuration */
+	Naltc	= 256,	/* max. alt configurations per interface */
+	Uctries	= 4,	/* no. of tries for usbcmd */
+	Ucdelay	= 50,	/* delay before retrying */
+
+	/* request type */
+	Rh2d	= 0<<7,		/* host to device */
+	Rd2h	= 1<<7,		/* device to host */ 
+
+	Rstd	= 0<<5,		/* types */
+	Rclass	= 1<<5,
+	Rvendor	= 2<<5,
+
+	Rdev	= 0,		/* recipients */
+	Riface	= 1,
+	Rep	= 2,		/* endpoint */
+	Rother	= 3,
+
+	/* standard requests */
+	Rgetstatus	= 0,
+	Rclearfeature	= 1,
+	Rsetfeature	= 3,
+	Rsetaddress	= 5,
+	Rgetdesc	= 6,
+	Rsetdesc	= 7,
+	Rgetconf	= 8,
+	Rsetconf	= 9,
+	Rgetiface	= 10,
+	Rsetiface	= 11,
+	Rsynchframe	= 12,
+
+	Rgetcur	= 0x81,
+	Rgetmin	= 0x82,
+	Rgetmax	= 0x83,
+	Rgetres	= 0x84,
+	Rsetcur	= 0x01,
+	Rsetmin	= 0x02,
+	Rsetmax	= 0x03,
+	Rsetres	= 0x04,
+
+	/* dev classes */
+	Clnone		= 0,		/* not in usb */
+	Claudio		= 1,
+	Clcomms		= 2,
+	Clhid		= 3,
+	Clprinter	= 7,
+	Clstorage	= 8,
+	Clhub		= 9,
+	Cldata		= 10,
+
+	/* standard descriptor sizes */
+	Ddevlen		= 18,
+	Dconflen	= 9,
+	Difacelen	= 9,
+	Deplen		= 7,
+
+	/* descriptor types */
+	Ddev		= 1,
+	Dconf		= 2,
+	Dstr		= 3,
+	Diface		= 4,
+	Dep		= 5,
+	Dreport		= 0x22,
+	Dfunction	= 0x24,
+	Dphysical	= 0x23,
+
+	/* feature selectors */
+	Fdevremotewakeup = 1,
+	Fhalt 	= 0,
+
+	/* device state */
+	Detached = 0,
+	Attached,
+	Enabled,
+	Assigned,
+	Configured,
+
+	/* endpoint direction */
+	Ein = 0,
+	Eout,
+	Eboth,
+
+	/* endpoint type */
+	Econtrol = 0,
+	Eiso = 1,
+	Ebulk = 2,
+	Eintr = 3,
+
+	/* endpoint isotype */
+	Eunknown = 0,
+	Easync = 1,
+	Eadapt = 2,
+	Esync = 3,
+
+	/* config attrib */
+	Cbuspowered = 1<<7,
+	Cselfpowered = 1<<6,
+	Cremotewakeup = 1<<5,
+
+	/* report types */
+	Tmtype	= 3<<2,
+	Tmitem	= 0xF0,
+	Tmain	= 0<<2,
+		Tinput	= 0x80,
+		Toutput	= 0x90,
+		Tfeature = 0xB0,
+		Tcoll	= 0xA0,
+		Tecoll	= 0xC0,
+	 Tglobal	= 1<<2,
+		Tusagepage = 0x00,
+		Tlmin	= 0x10,
+		Tlmax	= 0x20,
+		Tpmin	= 0x30,
+		Tpmax	= 0x40,
+		Tunitexp	= 0x50,
+		Tunit	= 0x60,
+		Trepsize	= 0x70,
+		TrepID	= 0x80,
+		Trepcount = 0x90,
+		Tpush	= 0xA0,
+		Tpop	= 0xB0,
+	 Tlocal	= 2<<2,
+		Tusage	= 0x00,
+		Tumin	= 0x10,
+		Tumax	= 0x20,
+		Tdindex	= 0x30,
+		Tdmin	= 0x40,
+		Tdmax	= 0x50,
+		Tsindex	= 0x70,
+		Tsmin	= 0x80,
+		Tsmax	= 0x90,
+		Tsetdelim = 0xA0,
+	 Treserved	= 3<<2,
+	 Tlong	= 0xFE,
+
+};
+
+/*
+ * Usb device (when used for ep0s) or endpoint.
+ * RC: One ref because of existing, another one per ogoing I/O.
+ * per-driver resources (including FS if any) are released by aux
+ * once the last ref is gone. This may include other Devs using
+ * to access endpoints for actual I/O.
+ */
+struct Dev
+{
+	Ref;
+	char*	dir;		/* path for the endpoint dir */
+	int	id;		/* usb id for device or ep. number */
+	int	dfd;		/* descriptor for the data file */
+	int	cfd;		/* descriptor for the control file */
+	int	maxpkt;		/* cached from usb description */
+	Ref	nerrs;		/* number of errors in requests */
+	Usbdev*	usb;		/* USB description */
+	void*	aux;		/* for the device driver */
+	void	(*free)(void*);	/* idem. to release aux */
+};
+
+/*
+ * device description as reported by USB (unpacked).
+ */
+struct Usbdev
+{
+	ulong	csp;		/* USB class/subclass/proto */
+	int	vid;		/* vendor id */
+	int	did;		/* product (device) id */
+	int	dno;		/* device release number */
+	char*	vendor;
+	char*	product;
+	char*	serial;
+	int	vsid;
+	int	psid;
+	int	ssid;
+	int	class;		/* from descriptor */
+	int	nconf;		/* from descriptor */
+	Conf*	conf[Nconf];	/* configurations */
+	Ep*	ep[Nep];	/* all endpoints in device */
+	Desc*	ddesc[Nddesc];	/* (raw) device specific descriptors */
+};
+
+struct Ep
+{
+	uchar	addr;		/* endpt address, 0-15 (|0x80 if Ein) */
+	uchar	dir;		/* direction, Ein/Eout */
+	uchar	type;		/* Econtrol, Eiso, Ebulk, Eintr */
+	uchar	isotype;		/* Eunknown, Easync, Eadapt, Esync */
+	int	id;
+	int	maxpkt;		/* max. packet size */
+	int	ntds;		/* nb. of Tds per µframe */
+	Conf*	conf;		/* the endpoint belongs to */
+	Iface*	iface;		/* the endpoint belongs to */
+};
+
+struct Altc
+{
+	int	attrib;
+	int	interval;
+	void*	aux;		/* for the driver program */
+};
+
+struct Iface
+{
+	int 	id;		/* interface number */
+	ulong	csp;		/* USB class/subclass/proto */
+	Altc*	altc[Naltc];
+	Ep*	ep[Nep];
+	void*	aux;		/* for the driver program */
+};
+
+struct Conf
+{
+	int	cval;		/* value for set configuration */
+	int	attrib;
+	int	milliamps;	/* maximum power in this config. */
+	Iface*	iface[Niface];
+};
+
+/*
+ * Device-specific descriptors.
+ * They show up mixed with other descriptors
+ * within a configuration.
+ * These are unknown to the library but handed to the driver.
+ */
+struct DDesc
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bbytes[1];
+	/* extra bytes allocated here to keep the rest of it */
+};
+
+struct Desc
+{
+	Conf*	conf;		/* where this descriptor was read */
+	Iface*	iface;		/* last iface before desc in conf. */
+	Ep*	ep;		/* last endpt before desc in conf. */
+	Altc*	altc;		/* last alt.c. before desc in conf. */
+	DDesc	data;		/* unparsed standard USB descriptor */
+};
+
+/*
+ * layout of standard descriptor types
+ */
+struct DDev
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bcdUSB[2];
+	uchar	bDevClass;
+	uchar	bDevSubClass;
+	uchar	bDevProtocol;
+	uchar	bMaxPacketSize0;
+	uchar	idVendor[2];
+	uchar	idProduct[2];
+	uchar	bcdDev[2];
+	uchar	iManufacturer;
+	uchar	iProduct;
+	uchar	iSerialNumber;
+	uchar	bNumConfigurations;
+};
+
+struct DConf
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	wTotalLength[2];
+	uchar	bNumInterfaces;
+	uchar	bConfigurationValue;
+	uchar	iConfiguration;
+	uchar	bmAttributes;
+	uchar	MaxPower;
+};
+
+struct DIface
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bInterfaceNumber;
+	uchar	bAlternateSetting;
+	uchar	bNumEndpoints;
+	uchar	bInterfaceClass;
+	uchar	bInterfaceSubClass;
+	uchar	bInterfaceProtocol;
+	uchar	iInterface;
+};
+
+struct DEp
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bEndpointAddress;
+	uchar	bmAttributes;
+	uchar	wMaxPacketSize[2];
+	uchar	bInterval;
+};
+
+#define Class(csp)	((csp) & 0xff)
+#define Subclass(csp)	(((csp)>>8) & 0xff)
+#define Proto(csp)	(((csp)>>16) & 0xff)
+#define CSP(c, s, p)	((c) | (s)<<8 | (p)<<16)
+
+#define	GET2(p)		(((p)[1] & 0xFF)<<8 | ((p)[0] & 0xFF))
+#define	PUT2(p,v)	{(p)[0] = (v); (p)[1] = (v)>>8;}
+#define	GET4(p)		(((p)[3]&0xFF)<<24 | ((p)[2]&0xFF)<<16 | \
+			 ((p)[1]&0xFF)<<8  | ((p)[0]&0xFF))
+#define	PUT4(p,v)	{(p)[0] = (v);     (p)[1] = (v)>>8; \
+			 (p)[2] = (v)>>16; (p)[3] = (v)>>24;}
+
+#define dprint if(usbdebug)fprint
+#define ddprint if(usbdebug > 1)fprint
+
+#pragma	varargck	type  "U"	Dev*
+#pragma	varargck	argpos	devctl	2
+
+int	Ufmt(Fmt *f);
+char*	classname(int c);
+void	closedev(Dev *d);
+int	configdev(Dev *d);
+int	devctl(Dev *dev, char *fmt, ...);
+void*	emallocz(ulong size, int zero);
+char*	estrdup(char *s);
+int	matchdevcsp(char *info, void *a);
+int	finddevs(int (*matchf)(char*,void*), void *farg, char** dirs, int ndirs);
+char*	hexstr(void *a, int n);
+int	loaddevconf(Dev *d, int n);
+int	loaddevdesc(Dev *d);
+char*	loaddevstr(Dev *d, int sid);
+Dev*	opendev(char *fn);
+int	opendevdata(Dev *d, int mode);
+Dev*	openep(Dev *d, int id);
+int	parseconf(Usbdev *d, Conf *c, uchar *b, int n);
+int	parsedesc(Usbdev *d, Conf *c, uchar *b, int n);
+int	parsedev(Dev *xd, uchar *b, int n);
+void	startdevs(char *args, char *argv[], int argc, int (*mf)(char*,void*), void*ma, int (*df)(Dev*,int,char**));
+int	unstall(Dev *dev, Dev *ep, int dir);
+int	usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count);
+Dev*	getdev(int id);
+
+extern int usbdebug;	/* more messages for bigger values */
+
+
--- a/sys/src/cmd/nusb/mkfile
+++ b/sys/src/cmd/nusb/mkfile
@@ -1,7 +1,8 @@
 </$objtype/mkfile
 
-# order matters here.  build lib first and usbd last.
 DIRS=\
+	lib\
+	kb\
 	usbd\
 
 UPDATE=\
@@ -21,7 +22,6 @@
 	for (i in $DIRS) @{
 		cd $i && mk $target
 	}
-	cp probe /$objtype/bin/usb/probe
 
 update:V:
 	update $UPDATEFLAGS $UPDATE
--- a/sys/src/cmd/nusb/usbd/dat.h
+++ b/sys/src/cmd/nusb/usbd/dat.h
@@ -1,6 +1,8 @@
 typedef struct Rule Rule;
 typedef struct Cond Cond;
-typedef struct Dev Dev;
+typedef struct Hub Hub;
+typedef struct DHub DHub;
+typedef struct Port Port;
 
 struct Rule {
 	char **argv;
@@ -17,6 +19,110 @@
 	Cond *and, *or;
 };
 
-struct Dev {
-	u32int class, vid, did;
+enum
+{
+	Stack	= 32*1024,
+
+	Dhub	= 0x29,		/* hub descriptor type */
+	Dhublen = 9,		/* hub descriptor length */
+
+	/* hub class feature selectors */
+	Fhublocalpower	= 0,
+	Fhubovercurrent	= 1,
+
+	Fportconnection	= 0,
+	Fportenable	= 1,
+	Fportsuspend	= 2,
+	Fportovercurrent = 3,
+	Fportreset	= 4,
+	Fportpower	= 8,
+	Fportlowspeed	= 9,
+	Fcportconnection	= 16,
+	Fcportenable	= 17,
+	Fcportsuspend	= 18,
+	Fcportovercurrent= 19,
+	Fcportreset	= 20,
+	Fportindicator	= 22,
+
+	/* Port status and status change bits
+	 * Constants at /sys/src/9/pc/usb.h starting with HP-
+	 * must have the same values or root hubs won't work.
+	 */
+	PSpresent	= 0x0001,
+	PSenable	= 0x0002,
+	PSsuspend	= 0x0004,
+	PSovercurrent	= 0x0008,
+	PSreset		= 0x0010,
+	PSpower		= 0x0100,
+	PSslow		= 0x0200,
+	PShigh		= 0x0400,
+
+	PSstatuschg	= 0x10000,	/* PSpresent changed */
+	PSchange	= 0x20000,	/* PSenable changed */
+
+
+	/* port/device state */
+	Pdisabled = 0,		/* must be 0 */
+	Pattached,
+	Pconfiged,
+
+	/* Delays, timeouts (ms) */
+//	Spawndelay	= 1000,		/* how often may we re-spawn a driver */
+	Spawndelay	= 250,		/* how often may we re-spawn a driver */
+//	Connectdelay	= 1000,		/* how much to wait after a connect */
+	Connectdelay	= 500,		/* how much to wait after a connect */
+	Resetdelay	= 20,		/* how much to wait after a reset */
+	Enabledelay	= 20,		/* how much to wait after an enable */
+	Powerdelay	= 100,		/* after powering up ports */
+	Pollms		= 250, 		/* port poll interval */
+	Chgdelay	= 100,		/* waiting for port become stable */
+	Chgtmout	= 1000,		/* ...but at most this much */
+
+	/*
+	 * device tab for embedded usb drivers.
+	 */
+	DCL = 0x01000000,		/* csp identifies just class */
+	DSC = 0x02000000,		/* csp identifies just subclass */
+	DPT = 0x04000000,		/* csp identifies just proto */
+
+};
+
+struct Hub
+{
+	uchar	pwrmode;
+	uchar	compound;
+	uchar	pwrms;		/* time to wait in ms */
+	uchar	maxcurrent;	/*    after powering port*/
+	int	leds;		/* has port indicators? */
+	int	maxpkt;
+	uchar	nport;
+	Port	*port;
+	int	failed;		/* I/O error while enumerating */
+	int	isroot;		/* set if root hub */
+	Dev	*dev;		/* for this hub */
+	Hub	*next;		/* in list of hubs */
+};
+
+struct Port
+{
+	int	state;		/* state of the device */
+	int	sts;		/* old port status */
+	uchar	removable;
+	uchar	pwrctl;
+	Dev	*dev;		/* attached device (if non-nil) */
+	Hub	*hub;		/* non-nil if hub attached */
+	int	devnb;		/* device number */
+	uvlong	*devmaskp;	/* ptr to dev mask */
+};
+
+/* USB HUB descriptor */
+struct DHub
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bNbrPorts;
+	uchar	wHubCharacteristics[2];
+	uchar	bPwrOn2PwrGood;
+	uchar	bHubContrCurrent;
+	uchar	DeviceRemovable[1];	/* variable length */
 };
--- a/sys/src/cmd/nusb/usbd/fns.h
+++ b/sys/src/cmd/nusb/usbd/fns.h
@@ -1,2 +1,4 @@
 void	parserules(char*);
-Rule*	rulesmatch(Dev*);
+Rule*	rulesmatch(Usbdev*);
+int	startdev(Port*);
+void	work(void);
--- /dev/null
+++ b/sys/src/cmd/nusb/usbd/hub.c
@@ -1,0 +1,690 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "dat.h"
+#include "fns.h"
+
+static Hub *hubs;
+static int nhubs;
+static int mustdump;
+static int pollms = Pollms;
+static Lock masklck;
+
+static char *dsname[] = { "disabled", "attached", "configed" };
+
+int
+getdevnb(uvlong *maskp)
+{
+	int i;
+
+	lock(&masklck);
+	for(i = 0; i < 8 * sizeof *maskp; i++)
+		if((*maskp & (1ULL<<i)) == 0){
+			*maskp |= 1ULL<<i;
+			unlock(&masklck);
+			return i;
+		}
+	unlock(&masklck);
+	return -1;
+}
+
+void
+putdevnb(uvlong *maskp, int id)
+{
+	lock(&masklck);
+	if(id >= 0)
+		*maskp &= ~(1ULL<<id);
+	unlock(&masklck);
+}
+
+static int
+hubfeature(Hub *h, int port, int f, int on)
+{
+	int cmd;
+
+	if(on)
+		cmd = Rsetfeature;
+	else
+		cmd = Rclearfeature;
+	return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
+}
+
+/*
+ * This may be used to detect overcurrent on the hub
+ */
+static void
+checkhubstatus(Hub *h)
+{
+	uchar buf[4];
+	int sts;
+
+	if(h->isroot)	/* not for root hubs */
+		return;
+	if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){
+		dprint(2, "%s: get hub status: %r\n", h->dev->dir);
+		return;
+	}
+	sts = GET2(buf);
+	dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
+}
+
+static int
+confighub(Hub *h)
+{
+	int type;
+	uchar buf[128];	/* room for extra descriptors */
+	int i;
+	Usbdev *d;
+	DHub *dd;
+	Port *pp;
+	int nr;
+	int nmap;
+	uchar *PortPwrCtrlMask;
+	int offset;
+	int mask;
+
+	d = h->dev->usb;
+	for(i = 0; i < nelem(d->ddesc); i++)
+		if(d->ddesc[i] == nil)
+			break;
+		else if(d->ddesc[i]->data.bDescriptorType == Dhub){
+			dd = (DHub*)&d->ddesc[i]->data;
+			nr = Dhublen;
+			goto Config;
+		}
+	type = Rd2h|Rclass|Rdev;
+	nr = usbcmd(h->dev, type, Rgetdesc, Dhub<<8|0, 0, buf, sizeof buf);
+	if(nr < Dhublen){
+		dprint(2, "%s: %s: getdesc hub: %r\n", argv0, h->dev->dir);
+		return -1;
+	}
+	dd = (DHub*)buf;
+Config:
+	h->nport = dd->bNbrPorts;
+	nmap = 1 + h->nport/8;
+	if(nr < 7 + 2*nmap){
+		fprint(2, "%s: %s: descr. too small\n", argv0, h->dev->dir);
+		return -1;
+	}
+	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
+	h->pwrms = dd->bPwrOn2PwrGood*2;
+	if(h->pwrms < Powerdelay)
+		h->pwrms = Powerdelay;
+	h->maxcurrent = dd->bHubContrCurrent;
+	h->pwrmode = dd->wHubCharacteristics[0] & 3;
+	h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;
+	h->leds = (dd->wHubCharacteristics[0] & (1<<7)) != 0;
+	PortPwrCtrlMask = dd->DeviceRemovable + nmap;
+	for(i = 1; i <= h->nport; i++){
+		pp = &h->port[i];
+		offset = i/8;
+		mask = 1<<(i%8);
+		pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;
+		pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
+	}
+	return 0;
+}
+
+static void
+configroothub(Hub *h)
+{
+	Dev *d;
+	char buf[128];
+	char *p;
+	int nr;
+
+	d = h->dev;
+	h->nport = 2;
+	h->maxpkt = 8;
+	seek(d->cfd, 0, 0);
+	nr = read(d->cfd, buf, sizeof(buf)-1);
+	if(nr < 0)
+		goto Done;
+	buf[nr] = 0;
+
+	p = strstr(buf, "ports ");
+	if(p == nil)
+		fprint(2, "%s: %s: no port information\n", argv0, d->dir);
+	else
+		h->nport = atoi(p+6);
+	p = strstr(buf, "maxpkt ");
+	if(p == nil)
+		fprint(2, "%s: %s: no maxpkt information\n", argv0, d->dir);
+	else
+		h->maxpkt = atoi(p+7);
+Done:
+	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
+	dprint(2, "%s: %s: ports %d maxpkt %d\n", argv0, d->dir, h->nport, h->maxpkt);
+}
+
+Hub*
+newhub(char *fn, Dev *d)
+{
+	Hub *h;
+	int i;
+	Usbdev *ud;
+
+	h = emallocz(sizeof(Hub), 1);
+	h->isroot = (d == nil);
+	if(h->isroot){
+		h->dev = opendev(fn);
+		if(h->dev == nil){
+			fprint(2, "%s: opendev: %s: %r", argv0, fn);
+			goto Fail;
+		}
+		if(opendevdata(h->dev, ORDWR) < 0){
+			fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);
+			goto Fail;
+		}
+		configroothub(h);	/* never fails */
+	}else{
+		h->dev = d;
+		if(confighub(h) < 0){
+			fprint(2, "%s: %s: config: %r\n", argv0, fn);
+			goto Fail;
+		}
+	}
+	if(h->dev == nil){
+		fprint(2, "%s: opendev: %s: %r\n", argv0, fn);
+		goto Fail;
+	}
+	devctl(h->dev, "hub");
+	ud = h->dev->usb;
+	if(h->isroot)
+		devctl(h->dev, "info roothub csp %#08ux ports %d",
+			0x000009, h->nport);
+	else{
+		devctl(h->dev, "info hub csp %#08ulx ports %d %q %q",
+			ud->csp, h->nport, ud->vendor, ud->product);
+		for(i = 1; i <= h->nport; i++)
+			if(hubfeature(h, i, Fportpower, 1) < 0)
+				fprint(2, "%s: %s: power: %r\n", argv0, fn);
+		sleep(h->pwrms);
+		for(i = 1; i <= h->nport; i++)
+			if(h->leds != 0)
+				hubfeature(h, i, Fportindicator, 1);
+	}
+	h->next = hubs;
+	hubs = h;
+	nhubs++;
+	dprint(2, "%s: hub %#p allocated:", argv0, h);
+	dprint(2, " ports %d pwrms %d max curr %d pwrm %d cmp %d leds %d\n",
+		h->nport, h->pwrms, h->maxcurrent,
+		h->pwrmode, h->compound, h->leds);
+	incref(h->dev);
+	return h;
+Fail:
+	if(d != nil)
+		devctl(d, "detach");
+	free(h->port);
+	free(h);
+	dprint(2, "%s: hub %#p failed to start:", argv0, h);
+	return nil;
+}
+
+static void portdetach(Hub *h, int p);
+
+/*
+ * If during enumeration we get an I/O error the hub is gone or
+ * in pretty bad shape. Because of retries of failed usb commands
+ * (and the sleeps they include) it can take a while to detach all
+ * ports for the hub. This detaches all ports and makes the hub void.
+ * The parent hub will detect a detach (probably right now) and
+ * close it later.
+ */
+static void
+hubfail(Hub *h)
+{
+	int i;
+
+	for(i = 1; i <= h->nport; i++)
+		portdetach(h, i);
+	h->failed = 1;
+}
+
+static void
+closehub(Hub *h)
+{
+	Hub **hl;
+
+	dprint(2, "%s: closing hub %#p\n", argv0, h);
+	for(hl = &hubs; *hl != nil; hl = &(*hl)->next)
+		if(*hl == h)
+			break;
+	if(*hl == nil)
+		sysfatal("closehub: no hub");
+	*hl = h->next;
+	nhubs--;
+	hubfail(h);		/* detach all ports */
+	free(h->port);
+	assert(h->dev != nil);
+	devctl(h->dev, "detach");
+	closedev(h->dev);
+	free(h);
+}
+
+static int
+portstatus(Hub *h, int p)
+{
+	Dev *d;
+	uchar buf[4];
+	int t;
+	int sts;
+	int dbg;
+
+	dbg = usbdebug;
+	if(dbg != 0 && dbg < 4)
+		usbdebug = 1;	/* do not be too chatty */
+	d = h->dev;
+	t = Rd2h|Rclass|Rother;
+	if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)
+		sts = -1;
+	else
+		sts = GET2(buf);
+	usbdebug = dbg;
+	return sts;
+}
+
+static char*
+stsstr(int sts)
+{
+	static char s[80];
+	char *e;
+
+	e = s;
+	if(sts&PSsuspend)
+		*e++ = 'z';
+	if(sts&PSreset)
+		*e++ = 'r';
+	if(sts&PSslow)
+		*e++ = 'l';
+	if(sts&PShigh)
+		*e++ = 'h';
+	if(sts&PSchange)
+		*e++ = 'c';
+	if(sts&PSenable)
+		*e++ = 'e';
+	if(sts&PSstatuschg)
+		*e++ = 's';
+	if(sts&PSpresent)
+		*e++ = 'p';
+	if(e == s)
+		*e++ = '-';
+	*e = 0;
+	return s;
+}
+
+static int
+getmaxpkt(Dev *d, int islow)
+{
+	uchar buf[64];	/* More room to try to get device-specific descriptors */
+	DDev *dd;
+
+	dd = (DDev*)buf;
+	if(islow)
+		dd->bMaxPacketSize0 = 8;
+	else
+		dd->bMaxPacketSize0 = 64;
+	if(usbcmd(d, Rd2h|Rstd|Rdev, Rgetdesc, Ddev<<8|0, 0, buf, sizeof(buf)) < 0)
+		return -1;
+	return dd->bMaxPacketSize0;
+}
+
+/*
+ * BUG: does not consider max. power avail.
+ */
+static Dev*
+portattach(Hub *h, int p, int sts)
+{
+	Dev *d;
+	Port *pp;
+	Dev *nd;
+	char fname[80];
+	char buf[40];
+	char *sp;
+	int mp;
+	int nr;
+
+	d = h->dev;
+	pp = &h->port[p];
+	nd = nil;
+	pp->state = Pattached;
+	dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);
+	sleep(Connectdelay);
+	if(hubfeature(h, p, Fportenable, 1) < 0)
+		dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
+	sleep(Enabledelay);
+	if(hubfeature(h, p, Fportreset, 1) < 0){
+		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	sleep(Resetdelay);
+	sts = portstatus(h, p);
+	if(sts < 0)
+		goto Fail;
+	if((sts & PSenable) == 0){
+		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
+		hubfeature(h, p, Fportenable, 1);
+		sts = portstatus(h, p);
+		if((sts & PSenable) == 0)
+			goto Fail;
+	}
+	sp = "full";
+	if(sts & PSslow)
+		sp = "low";
+	if(sts & PShigh)
+		sp = "high";
+	dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts);
+
+	if(devctl(d, "newdev %s %d", sp, p) < 0){
+		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	seek(d->cfd, 0, 0);
+	nr = read(d->cfd, buf, sizeof(buf)-1);
+	if(nr == 0){
+		fprint(2, "%s: %s: port %d: newdev: eof\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	if(nr < 0){
+		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	buf[nr] = 0;
+	snprint(fname, sizeof(fname), "/dev/usb/%s", buf);
+	nd = opendev(fname);
+	if(nd == nil){
+		fprint(2, "%s: %s: port %d: opendev: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	if(usbdebug > 2)
+		devctl(nd, "debug 1");
+	if(opendevdata(nd, ORDWR) < 0){
+		fprint(2, "%s: %s: opendevdata: %r\n", argv0, nd->dir);
+		goto Fail;
+	}
+	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
+		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	if(devctl(nd, "address") < 0){
+		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+
+	mp=getmaxpkt(nd, strcmp(sp, "low") == 0);
+	if(mp < 0){
+		dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}else{
+		dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);
+		devctl(nd, "maxpkt %d", mp);
+	}
+	if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)
+		dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",
+			argv0, d->dir, p, nd->dir);
+	if(configdev(nd) < 0){
+		dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	/*
+	 * We always set conf #1. BUG.
+	 */
+	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
+		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
+		unstall(nd, nd, Eout);
+		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
+			goto Fail;
+	}
+	dprint(2, "%s: %U", argv0, nd);
+	pp->state = Pconfiged;
+	dprint(2, "%s: %s: port %d: configed: %s\n",
+			argv0, d->dir, p, nd->dir);
+	return pp->dev = nd;
+Fail:
+	pp->state = Pdisabled;
+	pp->sts = 0;
+	if(pp->hub != nil)
+		pp->hub = nil;	/* hub closed by enumhub */
+	hubfeature(h, p, Fportenable, 0);
+	if(nd != nil)
+		devctl(nd, "detach");
+	closedev(nd);
+	return nil;
+}
+
+static void
+portdetach(Hub *h, int p)
+{
+	Dev *d;
+	Port *pp;
+	d = h->dev;
+	pp = &h->port[p];
+
+	/*
+	 * Clear present, so that we detect an attach on reconnects.
+	 */
+	pp->sts &= ~(PSpresent|PSenable);
+
+	if(pp->state == Pdisabled)
+		return;
+	pp->state = Pdisabled;
+	dprint(2, "%s: %s: port %d: detached\n", argv0, d->dir, p);
+
+	if(pp->hub != nil){
+		closehub(pp->hub);
+		pp->hub = nil;
+	}
+	if(pp->devmaskp != nil)
+		putdevnb(pp->devmaskp, pp->devnb);
+	pp->devmaskp = nil;
+	if(pp->dev != nil){
+		devctl(pp->dev, "detach");
+		closedev(pp->dev);
+		pp->dev = nil;
+	}
+}
+
+/*
+ * The next two functions are included to
+ * perform a port reset asked for by someone (usually a driver).
+ * This must be done while no other device is in using the
+ * configuration address and with care to keep the old address.
+ * To keep drivers decoupled from usbd they write the reset request
+ * to the #u/usb/epN.0/ctl file and then exit.
+ * This is unfortunate because usbd must now poll twice as much.
+ *
+ * An alternative to this reset process would be for the driver to detach
+ * the device. The next function could see that, issue a port reset, and
+ * then restart the driver once to see if it's a temporary error.
+ *
+ * The real fix would be to use interrupt endpoints for non-root hubs
+ * (would probably make some hubs fail) and add an events file to
+ * the kernel to report events to usbd. This is a severe change not
+ * yet implemented.
+ */
+static int
+portresetwanted(Hub *h, int p)
+{
+	char buf[5];
+	Port *pp;
+	Dev *nd;
+
+	pp = &h->port[p];
+	nd = pp->dev;
+	if(nd != nil && nd->cfd >= 0 && pread(nd->cfd, buf, 5, 0LL) == 5)
+		return strncmp(buf, "reset", 5) == 0;
+	else
+		return 0;
+}
+
+static void
+portreset(Hub *h, int p)
+{
+	int sts;
+	Dev *d, *nd;
+	Port *pp;
+
+	d = h->dev;
+	pp = &h->port[p];
+	nd = pp->dev;
+	dprint(2, "%s: %s: port %d: resetting\n", argv0, d->dir, p);
+	if(hubfeature(h, p, Fportreset, 1) < 0){
+		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	sleep(Resetdelay);
+	sts = portstatus(h, p);
+	if(sts < 0)
+		goto Fail;
+	if((sts & PSenable) == 0){
+		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
+		hubfeature(h, p, Fportenable, 1);
+		sts = portstatus(h, p);
+		if((sts & PSenable) == 0)
+			goto Fail;
+	}
+	nd = pp->dev;
+	opendevdata(nd, ORDWR);
+	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
+		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	if(devctl(nd, "address") < 0){
+		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
+		goto Fail;
+	}
+	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
+		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
+		unstall(nd, nd, Eout);
+		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
+			goto Fail;
+	}
+	if(nd->dfd >= 0)
+		close(nd->dfd);
+	return;
+Fail:
+	pp->state = Pdisabled;
+	pp->sts = 0;
+	if(pp->hub != nil)
+		pp->hub = nil;	/* hub closed by enumhub */
+	hubfeature(h, p, Fportenable, 0);
+	if(nd != nil)
+		devctl(nd, "detach");
+	closedev(nd);
+}
+
+static int
+portgone(Port *pp, int sts)
+{
+	if(sts < 0)
+		return 1;
+	/*
+	 * If it was enabled and it's not now then it may be reconnect.
+	 * We pretend it's gone and later we'll see it as attached.
+	 */
+	if((pp->sts & PSenable) != 0 && (sts & PSenable) == 0)
+		return 1;
+	return (pp->sts & PSpresent) != 0 && (sts & PSpresent) == 0;
+}
+
+static int
+enumhub(Hub *h, int p)
+{
+	int sts;
+	Dev *d;
+	Port *pp;
+	int onhubs;
+
+	if(h->failed)
+		return 0;
+	d = h->dev;
+	if(usbdebug > 3)
+		fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);
+
+	sts = portstatus(h, p);
+	if(sts < 0){
+		hubfail(h);		/* avoid delays on detachment */
+		return -1;
+	}
+	pp = &h->port[p];
+	onhubs = nhubs;
+	if((sts & PSsuspend) != 0){
+		if(hubfeature(h, p, Fportenable, 1) < 0)
+			dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
+		sleep(Enabledelay);
+		sts = portstatus(h, p);
+		fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
+	}
+	if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
+		if(portattach(h, p, sts) != nil)
+			if(startdev(pp) < 0)
+				portdetach(h, p);
+	}else if(portgone(pp, sts))
+		portdetach(h, p);
+	else if(portresetwanted(h, p))
+		portreset(h, p);
+	else if(pp->sts != sts){
+		dprint(2, "%s: %s port %d: sts %s %#x ->",
+			argv0, d->dir, p, stsstr(pp->sts), pp->sts);
+		dprint(2, " %s %#x\n",stsstr(sts), sts);
+	}
+	pp->sts = sts;
+	if(onhubs != nhubs)
+		return -1;
+	return 0;
+}
+
+static void
+dump(void)
+{
+	Hub *h;
+	int i;
+
+	mustdump = 0;
+	for(h = hubs; h != nil; h = h->next)
+		for(i = 1; i <= h->nport; i++)
+			fprint(2, "%s: hub %#p %s port %d: %U",
+				argv0, h, h->dev->dir, i, h->port[i].dev);
+
+}
+
+void
+work(void)
+{
+	char *fn;
+	Hub *h;
+	int i;
+
+	hubs = nil;
+	while((fn = rendezvous(work, nil)) != nil){
+		dprint(2, "%s: %s starting\n", argv0, fn);
+		h = newhub(fn, nil);
+		if(h == nil)
+			fprint(2, "%s: %s: newhub failed: %r\n", argv0, fn);
+		free(fn);
+	}
+	/*
+	 * Enumerate (and acknowledge after first enumeration).
+	 * Do NOT perform enumeration concurrently for the same
+	 * controller. new devices attached respond to a default
+	 * address (0) after reset, thus enumeration has to work
+	 * one device at a time at least before addresses have been
+	 * assigned.
+	 * Do not use hub interrupt endpoint because we
+	 * have to poll the root hub(s) in any case.
+	 */
+	for(;;){
+Again:
+		for(h = hubs; h != nil; h = h->next)
+			for(i = 1; i <= h->nport; i++)
+				if(enumhub(h, i) < 0){
+					/* changes in hub list; repeat */
+					goto Again;
+				}
+		sleep(pollms);
+		if(mustdump)
+			dump();
+	}
+}
--- a/sys/src/cmd/nusb/usbd/mkfile
+++ b/sys/src/cmd/nusb/usbd/mkfile
@@ -3,6 +3,7 @@
 OFILES=\
 	usbd.$O\
 	rules.$O\
+	hub.$O\
 
 HFILES=\
 	dat.h\
@@ -10,4 +11,6 @@
 
 TARG=usbd
 BIN=/$objtype/bin/nusb
+LIB=../lib/usb.a$O
 </sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
--- a/sys/src/cmd/nusb/usbd/rules.c
+++ b/sys/src/cmd/nusb/usbd/rules.c
@@ -2,6 +2,7 @@
 #include <libc.h>
 #include <thread.h>
 #include <ctype.h>
+#include "usb.h"
 #include "dat.h"
 #include "fns.h"
 
@@ -63,7 +64,7 @@
 	}
 }
 
-static Dev dummy;
+static Usbdev dummy;
 
 struct field {
 	char *s;
@@ -72,6 +73,7 @@
 	"class", &dummy.class,
 	"vid", &dummy.vid,
 	"did", &dummy.did,
+	"csp", &dummy.csp,
 	nil, nil,
 };
 
@@ -218,7 +220,7 @@
 }
 
 Rule *
-rulesmatch(Dev *dev)
+rulesmatch(Usbdev *dev)
 {
 	Rule *r;
 	Cond *c;
--- a/sys/src/cmd/nusb/usbd/usbd.c
+++ b/sys/src/cmd/nusb/usbd/usbd.c
@@ -3,6 +3,7 @@
 #include <thread.h>
 #include <fcall.h>
 #include <9p.h>
+#include "usb.h"
 #include "dat.h"
 #include "fns.h"
 
@@ -56,12 +57,73 @@
 	close(fd);
 }
 
+int
+startdev(Port *p)
+{
+	Rule *r;
+	char buf[14];
+
+	if(p->dev == nil || p->dev->usb == nil){
+		fprint(2, "okay what?\n");
+		return -1;
+	}
+	rlock(&rulelock);
+	r = rulesmatch(p->dev->usb);
+	if(r == nil || r->argv == nil){
+		fprint(2, "no driver for device\n");
+		runlock(&rulelock);
+		return -1;
+	}
+	snprint(buf, sizeof buf, "%d", p->dev->id);
+	r->argv[r->argc] = buf;
+	closedev(p->dev);
+	switch(fork()){
+	case -1:
+		fprint(2, "fork: %r");
+		runlock(&rulelock);
+		return -1;
+	case 0:
+		chdir("/bin");
+		exec(r->argv[0], r->argv);
+		sysfatal("exec: %r");
+	}
+	runlock(&rulelock);
+	return 0;
+}
+
 void
-main()
+main(int argc, char **argv)
 {
+	int fd, i, nd;
+	Dir *d;
+
 	readrules();
 	parserules(rules);
 	luser = getuser();
+	
+	argc--; argv++;
+
+	rfork(RFNOTEG);
+	switch(rfork(RFPROC|RFMEM)){
+	case -1: sysfatal("rfork: %r");
+	case 0: work(); exits(nil);
+	}
+	if(argc == 0){
+		fd = open("/dev/usb", OREAD);
+		if(fd < 0)
+			sysfatal("/dev/usb: %r");
+		nd = dirreadall(fd, &d);
+		close(fd);
+		if(nd < 2)
+			sysfatal("/dev/usb: no hubs");
+		for(i = 0; i < nd; i++)
+			if(strcmp(d[i].name, "ctl") != 0)
+				rendezvous(work, smprint("/dev/usb/%s", d[i].name));
+		free(d);
+	}else
+		for(i = 0; i < argc; i++)
+			rendezvous(work, strdup(argv[i]));
+	rendezvous(work, nil);
 	usbdsrv.tree = alloctree(luser, luser, 0555, nil);
 	usbdb = createfile(usbdsrv.tree->root, "usbdb", luser, 0775, nil);
 	postsharesrv(&usbdsrv, nil, "usb", "usbd", "b");
--