1119 lines
22 KiB
C
1119 lines
22 KiB
C
/* Copyright (c) 20XX 9front
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
/*
|
|
* usb/disk - usb mass storage file server
|
|
*
|
|
* supports only the scsi command interface, not ata.
|
|
*/
|
|
|
|
#include <u.h>
|
|
#include <lib9.h>
|
|
#include <ctype.h>
|
|
#include <9P2000.h>
|
|
#include <thread.h>
|
|
#include <9p.h>
|
|
#include "scsireq.h"
|
|
#include "usb.h"
|
|
#include "ums.h"
|
|
|
|
enum
|
|
{
|
|
Qdir = 0,
|
|
Qctl,
|
|
Qraw,
|
|
Qdata,
|
|
Qpart,
|
|
Qmax = Maxparts,
|
|
};
|
|
|
|
Dev *dev;
|
|
Ums *ums;
|
|
|
|
typedef struct Dirtab Dirtab;
|
|
struct Dirtab
|
|
{
|
|
char *name;
|
|
int mode;
|
|
};
|
|
|
|
uint32_t 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, int64_t start, int64_t end, uint32_t mode)
|
|
{
|
|
Part *p;
|
|
|
|
if(start < 0 || start > end || end > lun->blocks){
|
|
werrstr("bad partition boundaries");
|
|
return -1;
|
|
}
|
|
p = lookpart(lun, name);
|
|
if(p != nil){
|
|
/* adding identical partition is no-op */
|
|
if(p->offset == start && p->length == end - start && p->mode == mode)
|
|
return 0;
|
|
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, int64_t 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(Umsc *lun)
|
|
{
|
|
Part *p, *part;
|
|
Fmt fmt;
|
|
|
|
part = &lun->part[0];
|
|
|
|
fmtstrinit(&fmt);
|
|
fmtprint(&fmt, "dev %s\n", dev->dir);
|
|
fmtprint(&fmt, "lun %zd\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(Umsc *lun, char *msg)
|
|
{
|
|
int64_t start, end;
|
|
char *argv[16];
|
|
int argc;
|
|
|
|
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;
|
|
|
|
#if 0
|
|
static void
|
|
ding(void * _, char *msg)
|
|
{
|
|
if(strstr(msg, "alarm") != nil)
|
|
noted(NCONT);
|
|
noted(NDFLT);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
getmaxlun(void)
|
|
{
|
|
uint8_t 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(void)
|
|
{
|
|
int r;
|
|
|
|
r = Rh2d|Rclass|Riface;
|
|
if(usbcmd(dev, r, Umsreset, 0, 0, nil, 0) < 0){
|
|
fprint(2, "disk: reset: %r\n");
|
|
return -1;
|
|
}
|
|
sleep(100);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
umsrecover(void)
|
|
{
|
|
if(umsreset() < 0)
|
|
return -1;
|
|
if(unstall(dev, ums->epin, Ein) < 0)
|
|
dprint(2, "disk: unstall epin: %r\n");
|
|
|
|
/* do we need this when epin == epout? */
|
|
if(unstall(dev, ums->epout, Eout) < 0)
|
|
dprint(2, "disk: unstall epout: %r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
ispow2(uint64_t 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)
|
|
{
|
|
uint8_t 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 = (uint64_t)GETBELONG(data)<<32 |
|
|
GETBELONG(data + 4);
|
|
}
|
|
}
|
|
lun->blocks++; /* SRcapacity returns LBA of last block */
|
|
lun->capacity = (int64_t)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(void)
|
|
{
|
|
uint8_t i;
|
|
Umsc *lun;
|
|
int some;
|
|
|
|
umsreset();
|
|
ums->maxlun = getmaxlun();
|
|
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;
|
|
}
|
|
|
|
if(SRready(lun) < 0 && SRready(lun) < 0 && SRready(lun) < 0)
|
|
dprint(2, "disk: lun %d not ready\n", i);
|
|
|
|
if((lun->inquiry[0] & 0x1F) == 0){
|
|
SRstart(lun, 1);
|
|
sleep(250);
|
|
}
|
|
|
|
/*
|
|
* 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(dev, "detach");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* called by SR*() commands provided by scuzz's scsireq
|
|
*/
|
|
int32_t
|
|
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(dev, ums->epin, Ein);
|
|
}
|
|
|
|
/* read the transfer's status */
|
|
n = read(ums->epin->dfd, &csw, CswLen);
|
|
if(n <= 0){
|
|
/* n == 0 means "stalled" */
|
|
unstall(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)
|
|
sysfatal("%s: too many errors", dev->dir);
|
|
umsrecover();
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
dattach(Req *req)
|
|
{
|
|
req->fid->qid = (Qid) {0, 0, QTDIR};
|
|
req->ofcall.qid = req->fid->qid;
|
|
respond(req, nil);
|
|
}
|
|
|
|
static char *
|
|
dwalk(Fid *fid, char *name, Qid *qid)
|
|
{
|
|
Umsc *lun;
|
|
Part *p;
|
|
|
|
if((fid->qid.type & QTDIR) == 0)
|
|
return "walk in non-directory";
|
|
if(strcmp(name, "..") == 0){
|
|
fid->qid = (Qid){0, 0, QTDIR};
|
|
*qid = fid->qid;
|
|
return nil;
|
|
}
|
|
if(fid->qid.path == 0){
|
|
for(lun = ums->lun; lun <= ums->lun + ums->maxlun; lun++)
|
|
if(strcmp(lun->name, name) == 0){
|
|
fid->qid.path = (lun - ums->lun + 1) << 16;
|
|
fid->qid.vers = 0;
|
|
fid->qid.type = QTDIR;
|
|
*qid = fid->qid;
|
|
return nil;
|
|
}
|
|
return "does not exist";
|
|
}
|
|
lun = ums->lun + (fid->qid.path >> 16) - 1;
|
|
p = lookpart(lun, name);
|
|
if(p == nil)
|
|
return "does not exist";
|
|
fid->qid.path |= p->id;
|
|
fid->qid.vers = p->vers;
|
|
fid->qid.type = p->mode >> 24;
|
|
*qid = fid->qid;
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
dostat(Umsc *lun, int path, Dir *d)
|
|
{
|
|
Part *p;
|
|
|
|
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 = (int64_t) p->length * lun->lbsize;
|
|
d->name = strdup(p->name);
|
|
d->uid = strdup(getuser());
|
|
d->gid = strdup(d->uid);
|
|
d->muid = strdup(d->uid);
|
|
}
|
|
|
|
static int
|
|
dirgen(int n, Dir* d, void *aux)
|
|
{
|
|
Umsc *lun;
|
|
int i;
|
|
|
|
lun = aux;
|
|
for(i = Qctl; i < Qmax; i++){
|
|
if(lun->part[i].inuse == 0)
|
|
continue;
|
|
if(n-- == 0)
|
|
break;
|
|
}
|
|
if(i == Qmax)
|
|
return -1;
|
|
dostat(lun, i, d);
|
|
d->qid.path |= (lun - ums->lun) << 16;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rootgen(int n, Dir *d, void * _)
|
|
{
|
|
Umsc *lun;
|
|
|
|
if(n > ums->maxlun)
|
|
return -1;
|
|
lun = ums->lun + n;
|
|
d->qid.path = (n + 1) << 16;
|
|
d->qid.type = QTDIR;
|
|
d->mode = 0555 | DMDIR;
|
|
d->name = strdup(lun->name);
|
|
d->uid = strdup(getuser());
|
|
d->gid = strdup(d->uid);
|
|
d->muid = strdup(d->uid);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
dstat(Req *req)
|
|
{
|
|
int path;
|
|
Dir *d;
|
|
Umsc *lun;
|
|
|
|
d = &req->d;
|
|
d->qid = req->fid->qid;
|
|
if(req->fid->qid.path == 0){
|
|
d->name = strdup("");
|
|
d->mode = 0555 | DMDIR;
|
|
d->uid = strdup(getuser());
|
|
d->gid = strdup(d->uid);
|
|
d->muid = strdup(d->uid);
|
|
}else{
|
|
path = req->fid->qid.path & 0xFFFF;
|
|
lun = ums->lun + (req->fid->qid.path >> 16) - 1;
|
|
dostat(lun, path, d);
|
|
}
|
|
respond(req, nil);
|
|
}
|
|
|
|
static void
|
|
dopen(Req *req)
|
|
{
|
|
uint32_t path;
|
|
Umsc *lun;
|
|
|
|
if(req->ofcall.qid.path == 0){
|
|
respond(req, nil);
|
|
return;
|
|
}
|
|
path = req->ofcall.qid.path & 0xFFFF;
|
|
lun = ums->lun + (req->ofcall.qid.path >> 16) - 1;
|
|
switch(path){
|
|
case Qraw:
|
|
srvrelease(req->srv);
|
|
qlock(&lun->ql);
|
|
lun->phase = Pcmd;
|
|
qunlock(&lun->ql);
|
|
srvacquire(req->srv);
|
|
break;
|
|
}
|
|
respond(req, nil);
|
|
}
|
|
|
|
/*
|
|
* 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, int64_t offset)
|
|
{
|
|
int32_t nb, lbsize, lbshift, lbmask;
|
|
uint64_t 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 void
|
|
dread(Req *req)
|
|
{
|
|
int32_t n;
|
|
uint32_t path;
|
|
char buf[64];
|
|
char *s;
|
|
Part *p;
|
|
Umsc *lun;
|
|
Qid q;
|
|
int32_t count;
|
|
void *data;
|
|
int64_t offset;
|
|
Srv *srv;
|
|
|
|
q = req->fid->qid;
|
|
if(q.path == 0){
|
|
dirread9p(req, rootgen, nil);
|
|
respond(req, nil);
|
|
return;
|
|
}
|
|
path = q.path & 0xFFFF;
|
|
lun = ums->lun + (q.path >> 16) - 1;
|
|
count = req->ifcall.count;
|
|
data = req->ofcall.data;
|
|
offset = req->ifcall.offset;
|
|
|
|
switch(path){
|
|
case Qdir:
|
|
dirread9p(req, dirgen, lun);
|
|
respond(req, nil);
|
|
return;
|
|
case Qctl:
|
|
s = ctlstring(lun);
|
|
readstr(req, s);
|
|
free(s);
|
|
respond(req, nil);
|
|
return;
|
|
}
|
|
|
|
srv = req->srv;
|
|
srvrelease(srv);
|
|
qlock(&lun->ql);
|
|
switch(path){
|
|
case Qraw:
|
|
if(lun->lbsize <= 0 && umscapacity(lun) < 0){
|
|
respond(req, "phase error");
|
|
break;
|
|
}
|
|
switch(lun->phase){
|
|
case Pcmd:
|
|
respond(req, "phase error");
|
|
break;
|
|
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 */
|
|
responderror(req);
|
|
}else{
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
}
|
|
break;
|
|
case Pstatus:
|
|
n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
|
|
readbuf(req, buf, n);
|
|
lun->phase = Pcmd;
|
|
respond(req, nil);
|
|
break;
|
|
}
|
|
break;
|
|
case Qdata:
|
|
default:
|
|
p = &lun->part[path];
|
|
if(!p->inuse){
|
|
respond(req, "permission denied");
|
|
break;
|
|
}
|
|
count = setup(lun, p, data, count, offset);
|
|
if (count <= 0){
|
|
responderror(req);
|
|
break;
|
|
}
|
|
n = SRread(lun, lun->bufp, lun->nb * lun->lbsize);
|
|
if(n < 0){
|
|
lun->lbsize = 0; /* medium may have changed */
|
|
responderror(req);
|
|
break;
|
|
} 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);
|
|
}
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
break;
|
|
}
|
|
|
|
qunlock(&lun->ql);
|
|
srvacquire(srv);
|
|
}
|
|
|
|
static void
|
|
dwrite(Req *req)
|
|
{
|
|
int32_t len, ocount;
|
|
uint32_t path;
|
|
uint64_t bno;
|
|
Part *p;
|
|
Umsc *lun;
|
|
char *s;
|
|
int32_t count;
|
|
void *data;
|
|
int64_t offset;
|
|
Srv *srv;
|
|
|
|
lun = ums->lun + (req->fid->qid.path >> 16) - 1;
|
|
path = req->fid->qid.path & 0xFFFF;
|
|
count = req->ifcall.count;
|
|
data = req->ifcall.data;
|
|
offset = req->ifcall.offset;
|
|
|
|
srv = req->srv;
|
|
srvrelease(srv);
|
|
qlock(&lun->ql);
|
|
|
|
switch(path){
|
|
case Qctl:
|
|
s = emallocz(count+1, 1);
|
|
memmove(s, data, count);
|
|
if(s[count-1] == '\n')
|
|
s[count-1] = 0;
|
|
if(ctlparse(lun, s) == -1)
|
|
responderror(req);
|
|
else{
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
}
|
|
free(s);
|
|
break;
|
|
case Qraw:
|
|
if(lun->lbsize <= 0 && umscapacity(lun) < 0){
|
|
respond(req, "phase error");
|
|
break;
|
|
}
|
|
switch(lun->phase){
|
|
case Pcmd:
|
|
if(count != 6 && count != 10 && count != 12 && count != 16){
|
|
respond(req, "bad command length");
|
|
break;
|
|
}
|
|
memmove(lun->rawcmd, data, count);
|
|
lun->cmd.p = lun->rawcmd;
|
|
lun->cmd.count = count;
|
|
lun->cmd.write = 1;
|
|
lun->phase = Pdata;
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
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 */
|
|
responderror(req);
|
|
}else{
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
}
|
|
break;
|
|
case Pstatus:
|
|
lun->phase = Pcmd;
|
|
respond(req, "phase error");
|
|
break;
|
|
}
|
|
break;
|
|
case Qdata:
|
|
default:
|
|
p = &lun->part[path];
|
|
if(!p->inuse){
|
|
respond(req, "permission denied");
|
|
break;
|
|
}
|
|
len = ocount = count;
|
|
count = setup(lun, p, data, count, offset);
|
|
if (count <= 0){
|
|
responderror(req);
|
|
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 */
|
|
responderror(req);
|
|
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 */
|
|
responderror(req);
|
|
break;
|
|
}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;
|
|
}
|
|
req->ofcall.count = count;
|
|
respond(req, nil);
|
|
break;
|
|
}
|
|
|
|
qunlock(&lun->ql);
|
|
srvacquire(srv);
|
|
}
|
|
|
|
int
|
|
findendpoints(Ums *ums)
|
|
{
|
|
Ep *ep;
|
|
Usbdev *ud;
|
|
uint32_t csp, sc;
|
|
int i, epin, epout;
|
|
|
|
epin = epout = -1;
|
|
ud = 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(dev, epin);
|
|
if(ums->epin == nil){
|
|
fprint(2, "disk: openep %d: %r\n", epin);
|
|
return -1;
|
|
}
|
|
if(epout == epin){
|
|
incref(&ums->epin->ref);
|
|
ums->epout = ums->epin;
|
|
}else
|
|
ums->epout = openep(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(dev, "debug 1");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: %s [-d] devid\n", argv0);
|
|
exits("usage");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* some devices like usb modems appear as mass storage
|
|
* for windows driver installation. switch mode here
|
|
* and exit if we detect one so the real driver can
|
|
* attach it.
|
|
*/
|
|
static void
|
|
notreallyums(Dev *dev)
|
|
{
|
|
/* HUAWEI E220 */
|
|
if(dev->usb->vid == 0x12d1 && dev->usb->did == 0x1003){
|
|
usbcmd(dev, Rh2d|Rstd|Rdev, Rsetfeature, Fdevremotewakeup, 0x02, nil, 0);
|
|
exits("mode switch");
|
|
}
|
|
}
|
|
|
|
static Srv diskfs = {
|
|
.attach = dattach,
|
|
.walk1 = dwalk,
|
|
.open = dopen,
|
|
.read = dread,
|
|
.write = dwrite,
|
|
.stat = dstat,
|
|
};
|
|
|
|
void
|
|
main(int argc, char **argv)
|
|
{
|
|
Umsc *lun;
|
|
int i;
|
|
char buf[20];
|
|
|
|
ARGBEGIN{
|
|
case 'D':
|
|
chatty9p++;
|
|
break;
|
|
case 'd':
|
|
scsidebug(diskdebug);
|
|
diskdebug++;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
if(argc != 1)
|
|
usage();
|
|
|
|
dev = getdev(*argv);
|
|
if(dev == nil)
|
|
sysfatal("getdev: %r");
|
|
notreallyums(dev);
|
|
ums = dev->aux = emallocz(sizeof(Ums), 1);
|
|
ums->maxlun = -1;
|
|
dev->free = umsdevfree;
|
|
if(findendpoints(ums) < 0)
|
|
sysfatal("endpoints not found");
|
|
|
|
/*
|
|
* SanDISK 512M gets residues wrong.
|
|
*/
|
|
if(dev->usb->vid == 0x0781 && dev->usb->did == 0x5150)
|
|
ums->wrongresidues = 1;
|
|
|
|
if(umsinit() < 0)
|
|
sysfatal("umsinit: %r\n");
|
|
|
|
for(i = 0; i <= ums->maxlun; i++){
|
|
lun = &ums->lun[i];
|
|
if(ums->maxlun > 0)
|
|
snprint(lun->name, sizeof(lun->name), "sdU%s.%d", dev->hname, i);
|
|
else
|
|
snprint(lun->name, sizeof(lun->name), "sdU%s", dev->hname);
|
|
makeparts(lun);
|
|
}
|
|
snprint(buf, sizeof buf, "%d.disk", dev->id);
|
|
postsharesrv(&diskfs, nil, "usb", buf);
|
|
exits(nil);
|
|
}
|