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 +#include +#include +#include +#include +#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 @@ +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 < +#include + +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 <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 +#include +#include +#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, ""); + 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< +#include +#include +#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<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 @@ + +#include +#include +#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 "/" + * 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 +#include +#include +#include +#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, "\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 @@ + +#include +#include +#include +#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 @@ +#include +#include +#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<= 0) + *maskp &= ~(1ULL<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 #include #include +#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 #include #include <9p.h> +#include "usb.h" #include "dat.h" #include "fns.h" @@ -56,12 +57,73 @@ readrules(void) close(fd); } -void -main() +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(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");