/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * usb/disk - usb mass storage file server * * supports only the scsi command interface, not ata. */ #include <u.h>#include <libc.h>#include <ctype.h>#include <fcall.h>#include <thread.h>#include <disk.h>#include "scsireq.h"#include "usb.h"#include "usbfs.h"#include "ums.h" enum{ Qdir = 0, Qctl, Qraw, Qdata, Qmax,}; typedef struct Dirtab Dirtab;struct Dirtab{ char *name; int mode;}; static Dirtab dirtab[] ={ [Qdir] "/", DMDIR|0555, [Qctl] "ctl", 0664, /* nothing secret here */ [Qraw] "raw", 0640, [Qdata] "data", 0640,}; /* * These are used by scuzz scsireq */int exabyte, force6bytecmds; int diskdebug; static voidding(void *, char *msg){ if(strstr(msg, "alarm") != nil) noted(NCONT); noted(NDFLT);} static intgetmaxlun(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 intumsreset(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 intumsrecover(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 voidumsfatal(Ums *ums){ int i; devctl(ums->dev, "detach"); for(i = 0; i < ums->maxlun; i++) usbfsdel(&ums->lun[i].fs);} static intispow2(uvlong ul){ return (ul & (ul - 1)) == 0;} /* * return smallest power of 2 >= n */static intlog2(int n){ int i; for(i = 0; (1 << i) < n; i++) ; return i;} static intumscapacity(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; if(diskdebug) fprint(2, "disk: logical block size %lud, # blocks %llud\n", lun->lbsize, lun->blocks); return 0;} static intumsinit(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 */longumsrequest(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) fprint(2, "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 intdwalk(Usbfs *fs, Fid *fid, char *name){ int i; Qid qid; qid = fid->qid; if((qid.type & QTDIR) == 0){ werrstr("walk in non-directory"); return -1; } if(strcmp(name, "..") == 0) return 0; for(i = 1; i < nelem(dirtab); i++) if(strcmp(name, dirtab[i].name) == 0){ qid.path = i | fs->qid; qid.vers = 0; qid.type = dirtab[i].mode >> 24; fid->qid = qid; return 0; } werrstr(Enotfound); return -1;} static voiddostat(Usbfs *fs, int path, Dir *d){ Dirtab *t; Umsc *lun; t = &dirtab[path]; d->qid.path = path; d->qid.type = t->mode >> 24; d->mode = t->mode; d->name = t->name; lun = fs->aux; if(path == Qdata) d->length = lun->capacity; else d->length = 0;} static intdirgen(Usbfs *fs, Qid, int i, Dir *d, void*){ i++; /* skip dir */ if(i >= Qmax) return -1; else{ dostat(fs, i, d); d->qid.path |= fs->qid; return 0; }} static intdstat(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 intdopen(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 intsetup(Umsc *lun, 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 > lun->blocks) /* past end of device? */ nb = lun->blocks - bno; if(nb * lbsize > Maxiosize) nb = Maxiosize / lbsize; lun->nb = nb; if(bno >= lun->blocks || nb == 0) return 0; 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 confusing dossrv?? * * ctl reads must match the format documented in sd(3) exactly * to interoperate with the rest of the system. */static longdread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset){ long n; ulong path; char buf[1024]; char *s, *e; 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: /* * Some usb disks need an extra opportunity to divulge their * capacity (e.g. M-Systems/SanDisk 1GB flash drive). */ if(lun->lbsize <= 0) umscapacity(lun); Lines 1–500 of 814. Load more
/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * usb/disk - usb mass storage file server * * supports only the scsi command interface, not ata. */ #include <u.h> #include <libc.h> #include <ctype.h> #include <fcall.h> #include <thread.h> #include <disk.h> #include "scsireq.h" #include "usb.h" #include "usbfs.h" #include "ums.h" enum { Qdir = 0, Qctl, Qraw, Qdata, Qmax, }; typedef struct Dirtab Dirtab; struct Dirtab { char *name; int mode; }; static Dirtab dirtab[] = { [Qdir] "/", DMDIR|0555, [Qctl] "ctl", 0664, /* nothing secret here */ [Qraw] "raw", 0640, [Qdata] "data", 0640, }; /* * 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; 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) fprint(2, "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) { int i; Qid qid; qid = fid->qid; if((qid.type & QTDIR) == 0){ werrstr("walk in non-directory"); return -1; } if(strcmp(name, "..") == 0) return 0; for(i = 1; i < nelem(dirtab); i++) if(strcmp(name, dirtab[i].name) == 0){ qid.path = i | fs->qid; qid.vers = 0; qid.type = dirtab[i].mode >> 24; fid->qid = qid; return 0; } werrstr(Enotfound); return -1; } static void dostat(Usbfs *fs, int path, Dir *d) { Dirtab *t; Umsc *lun; t = &dirtab[path]; d->qid.path = path; d->qid.type = t->mode >> 24; d->mode = t->mode; d->name = t->name; lun = fs->aux; if(path == Qdata) d->length = lun->capacity; else d->length = 0; } static int dirgen(Usbfs *fs, Qid, int i, Dir *d, void*) { i++; /* skip dir */ if(i >= Qmax) return -1; else{ 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, 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 > lun->blocks) /* past end of device? */ nb = lun->blocks - bno; if(nb * lbsize > Maxiosize) nb = Maxiosize / lbsize; lun->nb = nb; if(bno >= lun->blocks || nb == 0) return 0; 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 confusing dossrv?? * * ctl reads must match the format documented in sd(3) exactly * to interoperate with the rest of the system. */ static long dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset) { long n; ulong path; char buf[1024]; char *s, *e; 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: /* * Some usb disks need an extra opportunity to divulge their * capacity (e.g. M-Systems/SanDisk 1GB flash drive). */ if(lun->lbsize <= 0) umscapacity(lun);