#include #include #include #include #include #include <9p.h> #include #include "scsireq.h" #include "usb.h" enum { Qdir = 0, Qctl, Qraw, Qdata, CMreset = 1, Pcmd = 0, Pdata, Pstatus, }; typedef struct Dirtab Dirtab; struct Dirtab { char *name; Qid qid; int mode; }; Dirtab dirtab[] = { ".", {Qdir,0,QTDIR}, DMDIR|0555, "ctl", {Qctl}, 0640, "raw", {Qraw}, 0640, "data", {Qdata}, 0640, }; Cmdtab cmdtab[] = { CMreset, "reset", 1, }; typedef struct Ums Ums; struct Ums { ScsiReq; ulong blocks; vlong capacity; int fd2; int setupfd; int ctlfd; uchar rawcmd[10]; uchar phase; uchar epin; uchar epout; }; Ums ums; long starttime; char *owner; static int noreset; /* flag: if true, drive freaks out if reset */ extern int debug; /* * USB transparent SCSI devices */ typedef struct Cbw Cbw; // command block wrapper struct Cbw { char signature[4]; // "USBC" long tag; long datalen; uchar flags; uchar lun; uchar len; char command[16]; }; typedef struct Csw Csw; // command status wrapper struct Csw { char signature[4]; // "USBS" long tag; long dataresidue; uchar status; }; enum { CbwLen = 31, CbwDataIn = 0x80, CbwDataOut = 0x00, CswLen = 13, CswOk = 0x00, CswFailed = 0x01, CswPhaseErr = 0x02, }; static void unstall(Ums *ums, int ep) { uchar req[16]; int n; n = snprint((char*)req, sizeof req, "unstall %d", ep&0xF); if (write(ums->ctlfd, req, n) != n) fprint(2, "ctl write failed\n"); n = snprint((char*)req, sizeof req, "data %d 0", ep&0xF); if (write(ums->ctlfd, req, n) != n) fprint(2, "ctl write failed\n"); req[0] = 2; // RH2D | Rstandard | Rendpt req[1] = 1; // CLEAR_FEATURE req[2] = req[3] = 0; // ENDPOINT_HALT req[4] = ep; req[5] = 0; // endpoint req[6] = req[7] = 0; // count if (write(ums->setupfd, req, 8) != 8) fprint(2, "setup write failed\n"); } static void umsreset(Ums *ums) { uchar req[8]; req[0] = (1<<5)|1; // RH2D | Rclass | Rinterface req[1] = 0xFF; // bulk-only reset req[2] = req[3] = 0; // value req[4] = req[5] = 0; // index = interface number? req[6] = req[7] = 0; // count /* * umsreset freaks out some thumb drives (e.g., geek squad * a.k.a. san disk sdcz2 cruzer mini flash drive), and once * they freak out, they're unusable until you remove them and * plug them in again, so it seems to be impossible to recover * from a reset in software. */ if (!noreset && write(ums->setupfd, req, 8) != 8) fprint(2, "umsreset setup write failed\n"); unstall(ums, ums->epin|0x80); unstall(ums, ums->epout); } long umsrequest(void *u, ScsiPtr *cmd, ScsiPtr *data, int *status) { Cbw cbw; Csw csw; static int seq = 0; int n; Ums *ums; ums = u; memcpy(cbw.signature, "USBC", 4); cbw.tag = ++seq; cbw.datalen = data->count; cbw.flags = data->write ? CbwDataOut : CbwDataIn; cbw.lun = ums->lun; cbw.len = cmd->count; memcpy(cbw.command, cmd->p, cmd->count); memset(cbw.command+cmd->count, 0, sizeof(cbw.command)-cmd->count); if (debug) { fprint(2, "cmd:"); for (n = 0; n < cbw.len; n++) fprint(2, " %2.2x", cbw.command[n]&0xFF); fprint(2, " datalen: %ld\n", cbw.datalen); } if(write(ums->fd2, &cbw, CbwLen) != CbwLen){ fprint(2, "usbscsi: write cmd: %r\n"); goto reset; } if(data->count != 0) { if(data->write) n = write(ums->fd2, data->p, data->count); else n = read(ums->fd, data->p, data->count); if(n == -1){ fprint(2, "usbscsi: data %sput: %r\n", data->write? "out" : "in"); if(data->write) unstall(ums, ums->epout); else unstall(ums, ums->epin|0x80); } } n = read(ums->fd, &csw, CswLen); if(n == -1){ unstall(ums, ums->epin|0x80); n = read(ums->fd, &csw, CswLen); } if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){ fprint(2, "usbscsi: read status: %r\n"); goto reset; } if(csw.tag != cbw.tag) { fprint(2, "usbscsi: status tag mismatch\n"); goto reset; } if(csw.status >= CswPhaseErr){ fprint(2, "usbscsi: phase error\n"); goto reset; } if (debug) { fprint(2, "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]&0xFF); fprint(2, "\n"); } } if(csw.status == CswOk) *status = STok; else *status = STcheck; return data->count - csw.dataresidue; reset: umsreset(ums); *status = STharderr; return -1; } int umsinit(Ums *ums, char *usbdir, int epin, int epout, int lun) { uchar data[8]; char fin[128]; memset(ums, 0, sizeof(*ums)); snprint(fin, sizeof fin, "%s/ctl", usbdir); if ((ums->ctlfd = open(fin, OWRITE)) == -1) return -1; if (epin == epout) { if (fprint(ums->ctlfd, "ep %d bulk rw 64 16", epin) < 0) return -1; } else { if (fprint(ums->ctlfd, "ep %d bulk r 64 16", epin) < 0) return -1; if (fprint(ums->ctlfd, "ep %d bulk w 64 16", epout) < 0) return -1; } snprint(fin, sizeof fin, "%s/ep%ddata", usbdir, epin); if((ums->fd = open(fin, OREAD)) == -1) return -1; snprint(fin, sizeof fin, "%s/ep%ddata", usbdir, epout); if((ums->fd2 = open(fin, OWRITE)) == -1) return -1; snprint(fin, sizeof fin, "%s/setup", usbdir); if ((ums->setupfd = open(fin, ORDWR)) == -1) return -1; ums->flags = Fopen|Fusb|Frw10; ums->epin = epin; ums->epout = epout; ums->ums = ums; // to get from ScsiReq to enclosing Ums ums->lun = lun; umsreset(ums); if (SRinquiry(ums) == -1) return -1; SRstart(ums, 1); if (SRrcapacity(ums, data) == -1 && SRrcapacity(ums, data) == -1) return -1; ums->lbsize = (data[4]<<24)|(data[5]<<16)|(data[6]<<8)|data[7]; ums->blocks = (data[0]<<24)|(data[1]<<16)|(data[2]<<8)|data[3]; ums->blocks++; // SRcapacity returns LBA of last block ums->capacity = (vlong)ums->blocks * ums->lbsize; return 0; } void rattach(Req *r) { r->ofcall.qid = dirtab[Qdir].qid; r->fid->qid = r->ofcall.qid; respond(r, nil); } char* rwalk1(Fid *fid, char *name, Qid *qid) { int i; for (i = 0; i < nelem(dirtab); i++) { if (!strcmp(name, dirtab[i].name)) { *qid = dirtab[i].qid; fid->qid = *qid; return 0; } } return "file does not exist"; } void dostat(int n, Dir *dir) { Dirtab *d; d = &dirtab[n]; dir->qid = d->qid; dir->mode = d->mode; dir->atime = dir->mtime = starttime; dir->name = estrdup9p(d->name); dir->uid = estrdup9p(owner); dir->gid = estrdup9p(owner); if (n == Qdata) dir->length = ums.capacity; } int dirgen(int n, Dir *dir, void*) { if (++n >= nelem(dirtab)) return -1; dostat(n, dir); return 0; } void rstat(Req *r) { dostat((long)r->fid->qid.path, &r->d); respond(r, nil); } void ropen(Req *r) { switch ((long)r->fid->qid.path ){ case Qraw: ums.phase = Pcmd; break; } respond(r, nil); } void rread(Req *r) { char buf[128]; int n; char *p; int bno, nb, len, offset; switch ((long)r->fid->qid.path) { case Qdir: dirread9p(r, dirgen, 0); break; case Qctl: n = 0; if (ums.flags&Finqok) n = snprint(buf, sizeof buf, "inquiry %.48s\n", (char*)ums.inquiry+8); n += snprint(buf+n, sizeof buf-n, "geometry %ld %ld\n", ums.blocks, ums.lbsize); readbuf(r, buf, n); break; case Qraw: switch (ums.phase) { case Pcmd: respond(r, "phase error"); return; case Pdata: ums.data.p = (uchar*)r->ofcall.data; ums.data.count = r->ifcall.count; ums.data.write = 0; n = umsrequest(&ums, &ums.cmd, &ums.data, &ums.status); ums.phase = Pstatus; if (n == -1) { respond(r, "IO error"); return; } r->ofcall.count = n; break; case Pstatus: n = sprint(buf, "%11.0ud ", ums.status); if (r->ifcall.count < n) n = r->ifcall.count; memmove(r->ofcall.data, buf, n); r->ofcall.count = n; ums.phase = Pcmd; break; } break; case Qdata: bno = r->ifcall.offset/ums.lbsize; nb = (r->ifcall.offset+r->ifcall.count+ums.lbsize-1)/ums.lbsize - bno; if (bno+nb > ums.blocks) nb = ums.blocks - bno; if (bno >= ums.blocks || nb == 0) { r->ofcall.count = 0; break; } if (nb*ums.lbsize > MaxIOsize) nb = MaxIOsize / ums.lbsize; p = malloc(nb * ums.lbsize); if (p == 0) { respond(r, "no mem"); return; } ums.offset = r->ifcall.offset / ums.lbsize; n = SRread(&ums, p, nb*ums.lbsize); if (n == -1) { free(p); respond(r, "IO error"); return; } len = r->ifcall.count; offset = r->ifcall.offset % ums.lbsize; if (offset+len > n) len = n - offset; r->ofcall.count = len; memmove(r->ofcall.data, p+offset, len); free(p); break; } respond(r, nil); } void rwrite(Req *r) { int n; char *p; int bno, nb, len, offset; Cmdbuf *cb; Cmdtab *ct; n = r->ifcall.count; r->ofcall.count = 0; switch ((long)r->fid->qid.path) { case Qctl: cb = parsecmd(r->ifcall.data, n); ct = lookupcmd(cb, cmdtab, nelem(cmdtab)); if (ct == 0) { respondcmderror(r, cb, "%r"); return; } switch (ct->index) { case CMreset: umsreset(&ums); } break; case Qraw: n = r->ifcall.count; switch (ums.phase) { case Pcmd: if ( n != 6 && n != 10) { respond(r, "bad command length"); return; } memmove(ums.rawcmd, r->ifcall.data, n); ums.cmd.p = ums.rawcmd; ums.cmd.count = n; ums.cmd.write = 1; ums.phase = Pdata; break; case Pdata: ums.data.p = (uchar*)r->ifcall.data; ums.data.count = n; ums.data.write = 1; n = umsrequest(&ums, &ums.cmd, &ums.data, &ums.status); ums.phase = Pstatus; if (n == -1) { respond(r, "IO error"); return; } break; case Pstatus: ums.phase = Pcmd; respond(r, "phase error"); return; } break; case Qdata: bno = r->ifcall.offset/ums.lbsize; nb = (r->ifcall.offset+r->ifcall.count+ums.lbsize-1)/ums.lbsize - bno; if (bno+nb > ums.blocks) nb = ums.blocks - bno; if (bno >= ums.blocks || nb == 0) { r->ofcall.count = 0; break; } if (nb*ums.lbsize > MaxIOsize) nb = MaxIOsize / ums.lbsize; p = malloc(nb * ums.lbsize); if (p == 0) { respond(r, "no mem"); return; } offset = r->ifcall.offset % ums.lbsize; len = r->ifcall.count; if (offset || (len%ums.lbsize)) { ums.offset = r->ifcall.offset / ums.lbsize; n = SRread(&ums, p, nb*ums.lbsize); if (n == -1) { free(p); respond(r, "IO error"); return; } if (offset+len > n) len = n - offset; } memmove(p+offset, r->ifcall.data, len); ums.offset = r->ifcall.offset / ums.lbsize; n = SRwrite(&ums, p, nb*ums.lbsize); if (n == -1) { free(p); respond(r, "IO error"); return; } if (offset+len > n) len = n - offset; r->ofcall.count = len; free(p); break; } r->ofcall.count = n; respond(r, nil); } Srv usbssrv = { .attach = rattach, .walk1 = rwalk1, .open = ropen, .read = rread, .write = rwrite, .stat = rstat, }; int findendpoints(Device *d, int *epin, int *epout) { Endpt *ep; ulong csp; int i, addr; *epin = *epout = -1; if (d->nconf < 1) return -1; for(i=0; inconf; i++) { if (d->config[i] == nil) d->config[i] = mallocz(sizeof(*d->config[i]),1); loadconfig(d, i); } for(i = 0; i < Nendpt; i++){ if ((ep = d->ep[i]) == nil) continue; csp = ep->csp; if(!(Class(csp) == CL_STORAGE && Proto(csp) == 0x50)) continue; if(ep->type == Ebulk) { addr = ep->addr; if (addr&0x80) { if(*epin == -1) *epin = addr&0xF; } else { if(*epout == -1) *epout = addr; } } } if (*epin == -1 || *epout == -1) return -1; return 0; } void (*dprinter[])(Device *, int, ulong, void *b, int n) = { [STRING] pstring, [DEVICE] pdevice, }; void usage(void) { fprint(2, "Usage: %s [-rsD] [-m mountpoint] [-l lun] [ctrno id]\n", argv0); exits("Usage"); } void main(int argc, char **argv) { char *srvname = nil; char *mntname = "/n/ums"; char *p; int stdio = 0; int epin, epout; int ctlrno = 0, id = 0; int lun = 0; Biobuf *f; Device *d; char buf[64]; extern int debug; ARGBEGIN{ case 'd': debug = Dbginfo; break; case 's': ++stdio; break; case 'm': mntname = ARGF(); break; case 'D': ++chatty9p; break; case 'r': ++noreset; break; case 'l': lun = atoi(EARGF(usage())); break; default: usage(); }ARGEND if (argc == 0) { for (ctlrno = 0; ctlrno < 16; ctlrno++) { for (id = 1; id < 128; id++) { sprint(buf, "/dev/usb%d/%d/status", ctlrno, id); f = Bopen(buf, OREAD); if (f == 0) break; while ((p = Brdline(f, '\n')) != 0) { p[Blinelen(f)-1] = '\0'; if (strstr(p, "0x500508") != 0 || strstr(p, "0x500608") != 0) { Bterm(f); goto found; } } Bterm(f); } } sysfatal("No usb storage device found"); } else if (argc == 2 && isdigit(argv[0][0]) && isdigit(argv[1][0])) { ctlrno = atoi(argv[0]); id = atoi(argv[1]); } else usage(); found: d = opendev(ctlrno, id); if (describedevice(d) < 0) sysfatal("%r"); if (findendpoints(d, &epin, &epout) < 0) sysfatal("bad usb configuration"); sprint(buf, "/dev/usb%d/%d", ctlrno, id); if (umsinit(&ums, buf, epin, epout, lun) < 0) sysfatal("device error"); starttime = time(0); owner = getuser(); if (stdio) srv(&usbssrv); else postmountsrv(&usbssrv, srvname, mntname, 0); exits(0); }