summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraiju <aiju@phicode.de>2011-07-27 20:07:30 +0200
committeraiju <aiju@phicode.de>2011-07-27 20:07:30 +0200
commit05d09f086fdb76206ab5ff0e26a1e1bc2f34b6a3 (patch)
treec62905a4a8467110b426843a683136a835c8be14
parent5181f2e576dc6b92dfa7dcaa2f60397b931fbc4a (diff)
downloadplan9front-05d09f086fdb76206ab5ff0e26a1e1bc2f34b6a3.tar.xz
nusb: improved
-rw-r--r--lib/usbdb4
-rw-r--r--sys/src/cmd/nusb/disk/disk.c982
-rw-r--r--sys/src/cmd/nusb/disk/mkfile24
-rwxr-xr-xsys/src/cmd/nusb/disk/mkscsierrs32
-rw-r--r--sys/src/cmd/nusb/disk/scsireq.c986
-rw-r--r--sys/src/cmd/nusb/disk/scsireq.h238
-rw-r--r--sys/src/cmd/nusb/disk/ums.h124
-rw-r--r--sys/src/cmd/nusb/kb/hid.h65
-rw-r--r--sys/src/cmd/nusb/kb/kb.c595
-rw-r--r--sys/src/cmd/nusb/kb/mkfile20
-rw-r--r--sys/src/cmd/nusb/lib/dev.c512
-rw-r--r--sys/src/cmd/nusb/lib/dump.c176
-rw-r--r--sys/src/cmd/nusb/lib/mkfile24
-rw-r--r--sys/src/cmd/nusb/lib/parse.c270
-rw-r--r--sys/src/cmd/nusb/lib/usb.h361
-rw-r--r--sys/src/cmd/nusb/mkfile4
-rw-r--r--sys/src/cmd/nusb/usbd/dat.h112
-rw-r--r--sys/src/cmd/nusb/usbd/fns.h4
-rw-r--r--sys/src/cmd/nusb/usbd/hub.c690
-rw-r--r--sys/src/cmd/nusb/usbd/mkfile3
-rw-r--r--sys/src/cmd/nusb/usbd/rules.c6
-rw-r--r--sys/src/cmd/nusb/usbd/usbd.c64
22 files changed, 5287 insertions, 9 deletions
diff --git a/lib/usbdb b/lib/usbdb
index dee888877..54b3c0c2e 100644
--- a/lib/usbdb
+++ b/lib/usbdb
@@ -1,3 +1,7 @@
+nusb/kb
+ csp=0x010103
+ csp=0x020103
+
nusb/disk
class=8
diff --git a/sys/src/cmd/nusb/disk/disk.c b/sys/src/cmd/nusb/disk/disk.c
new file mode 100644
index 000000000..de69bc8b6
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/disk.c
@@ -0,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;
+}
diff --git a/sys/src/cmd/nusb/disk/mkfile b/sys/src/cmd/nusb/disk/mkfile
new file mode 100644
index 000000000..e23c72851
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/mkfile
@@ -0,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
+
diff --git a/sys/src/cmd/nusb/disk/mkscsierrs b/sys/src/cmd/nusb/disk/mkscsierrs
new file mode 100755
index 000000000..a7cc32e5d
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/mkscsierrs
@@ -0,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
diff --git a/sys/src/cmd/nusb/disk/scsireq.c b/sys/src/cmd/nusb/disk/scsireq.c
new file mode 100644
index 000000000..f9994e286
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/scsireq.c
@@ -0,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;
+}
diff --git a/sys/src/cmd/nusb/disk/scsireq.h b/sys/src/cmd/nusb/disk/scsireq.h
new file mode 100644
index 000000000..0fbd2b938
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/scsireq.h
@@ -0,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);
diff --git a/sys/src/cmd/nusb/disk/ums.h b/sys/src/cmd/nusb/disk/ums.h
new file mode 100644
index 000000000..9598b9270
--- /dev/null
+++ b/sys/src/cmd/nusb/disk/ums.h
@@ -0,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**);
diff --git a/sys/src/cmd/nusb/kb/hid.h b/sys/src/cmd/nusb/kb/hid.h
new file mode 100644
index 000000000..dba2b027d
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/hid.h
@@ -0,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[]);
diff --git a/sys/src/cmd/nusb/kb/kb.c b/sys/src/cmd/nusb/kb/kb.c
new file mode 100644
index 000000000..0f8dbb808
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/kb.c
@@ -0,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);
+}
diff --git a/sys/src/cmd/nusb/kb/mkfile b/sys/src/cmd/nusb/kb/mkfile
new file mode 100644
index 000000000..a437aaa5b
--- /dev/null
+++ b/sys/src/cmd/nusb/kb/mkfile
@@ -0,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
+
diff --git a/sys/src/cmd/nusb/lib/dev.c b/sys/src/cmd/nusb/lib/dev.c
new file mode 100644
index 000000000..2748dd4a2
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/dev.c
@@ -0,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;
+}
diff --git a/sys/src/cmd/nusb/lib/dump.c b/sys/src/cmd/nusb/lib/dump.c
new file mode 100644
index 000000000..8ba766b62
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/dump.c
@@ -0,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;
+}
+
diff --git a/sys/src/cmd/nusb/lib/mkfile b/sys/src/cmd/nusb/lib/mkfile
new file mode 100644
index 000000000..705ba00a3
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/mkfile
@@ -0,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]
diff --git a/sys/src/cmd/nusb/lib/parse.c b/sys/src/cmd/nusb/lib/parse.c
new file mode 100644
index 000000000..642c427a2
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/parse.c
@@ -0,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;
+}
diff --git a/sys/src/cmd/nusb/lib/usb.h b/sys/src/cmd/nusb/lib/usb.h
new file mode 100644
index 000000000..adba943ec
--- /dev/null
+++ b/sys/src/cmd/nusb/lib/usb.h
@@ -0,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 */
+
+
diff --git a/sys/src/cmd/nusb/mkfile b/sys/src/cmd/nusb/mkfile
index 6b08a281a..523fd3feb 100644
--- 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 @@ install installall safeinstall safeinstallall:V:
for (i in $DIRS) @{
cd $i && mk $target
}
- cp probe /$objtype/bin/usb/probe
update:V:
update $UPDATEFLAGS $UPDATE
diff --git a/sys/src/cmd/nusb/usbd/dat.h b/sys/src/cmd/nusb/usbd/dat.h
index 7e77af421..d87b25c62 100644
--- 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 @@ struct Cond {
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 */
};
diff --git a/sys/src/cmd/nusb/usbd/fns.h b/sys/src/cmd/nusb/usbd/fns.h
index cca197621..3879ae8bc 100644
--- 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);
diff --git a/sys/src/cmd/nusb/usbd/hub.c b/sys/src/cmd/nusb/usbd/hub.c
new file mode 100644
index 000000000..3c80c3c4e
--- /dev/null
+++ b/sys/src/cmd/nusb/usbd/hub.c
@@ -0,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();
+ }
+}
diff --git a/sys/src/cmd/nusb/usbd/mkfile b/sys/src/cmd/nusb/usbd/mkfile
index 5eee54232..b0b3c5050 100644
--- 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 @@ HFILES=\
TARG=usbd
BIN=/$objtype/bin/nusb
+LIB=../lib/usb.a$O
</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
diff --git a/sys/src/cmd/nusb/usbd/rules.c b/sys/src/cmd/nusb/usbd/rules.c
index 33bb56f35..eae988fd3 100644
--- 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 @@ parsesh(int *argc, char ***argv)
}
}
-static Dev dummy;
+static Usbdev dummy;
struct field {
char *s;
@@ -72,6 +73,7 @@ struct field {
"class", &dummy.class,
"vid", &dummy.vid,
"did", &dummy.did,
+ "csp", &dummy.csp,
nil, nil,
};
@@ -218,7 +220,7 @@ parserules(char *s)
}
Rule *
-rulesmatch(Dev *dev)
+rulesmatch(Usbdev *dev)
{
Rule *r;
Cond *c;
diff --git a/sys/src/cmd/nusb/usbd/usbd.c b/sys/src/cmd/nusb/usbd/usbd.c
index 54d91d575..ee5ed2075 100644
--- 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 @@ readrules(void)
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");