Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/usb-disk-fixes/disk.c.orig

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * usb/disk - usb mass storage file server
 * BUG: supports only the scsi command interface.
 * BUG: This should use /dev/sdfile to
 * use the kernel ether device code.
 */

#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.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",	0444,
	[Qraw]	"raw",	0640,
	[Qdata]	"data",	0640,
};

/*
 * These are used by scuzz scsireq
 */
int exabyte, force6bytecmds;
long maxiosize = MaxIOsize;

static int diskdebug;

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
		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
umscapacity(Umsc *lun)
{
	uchar data[32];

	lun->blocks = 0;
	lun->capacity = 0;
	lun->lbsize = 0;
	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;
	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)
			continue;
		if(lun->inquiry[0] != 0x00){
			/* not a disk */
			fprint(2, "%s: lun %d is not a disk (type %#02x)\n",
				argv0, 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){
		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;
	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("%s: umsrequest: bad cmd count: %ld\n", argv0, 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);
	}
	if(write(ums->epout->dfd, &cbw, CbwLen) != CbwLen){
		fprint(2, "disk: cmd: %r\n");
		goto Fail;
	}
	if(data->count != 0){
		if(data->write)
			n = write(ums->epout->dfd, data->p, data->count);
		else{
			memset(data->p, data->count, 0);
			n = read(ums->epin->dfd, data->p, data->count);
		}
		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);
	}
	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(diskdebug){
		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]);
			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;
}

/*
 * 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 bno, nb, len, off, n;
	ulong path;
	char buf[1024], *p;
	char *s;
	char *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:
		e = buf + sizeof(buf);
		s = seprint(buf, e, "%s lun %ld: ", fs->dev->dir, lun - &ums->lun[0]);
		if(lun->flags & Finqok)
			s = seprint(s, e, "inquiry %s ", lun->inq);
		if(lun->blocks > 0)
			s = seprint(s, e, "geometry %llud %ld", lun->blocks,
				lun->lbsize);
		s = seprint(s, e, "\n");
		count = usbreadbuf(data, count, offset, buf, s - buf);
		break;
	case Qraw:
		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
			qunlock(ums);
			return -1;
		}
		switch(lun->phase){
		case Pcmd:
			qunlock(ums);
			werrstr("phase error");
			return -1;
		case Pdata:
			lun->data.p = (uchar*)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 */
				qunlock(ums);
				return -1;
			}
			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:
		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
			qunlock(ums);
			return -1;
		}
		bno = offset / lun->lbsize;
		nb = (offset + count + lun->lbsize - 1) / lun->lbsize - bno;
		if(bno + nb > lun->blocks)
			nb = lun->blocks - bno;
		if(bno >= lun->blocks || nb == 0){
			count = 0;
			break;
		}
		if(nb * lun->lbsize > maxiosize)
			nb = maxiosize / lun->lbsize;
		p = emallocz(nb * lun->lbsize, 0);	/* could use a static buffer */
		lun->offset = offset / lun->lbsize;
		n = SRread(lun, p, nb * lun->lbsize);
		if(n < 0){
			free(p);
			lun->lbsize = 0;	/* medium may have changed */
			qunlock(ums);
			return -1;
		}
		len = count;
		off = offset % lun->lbsize;
		if(off + len > n)
			len = n - off;
		count = len;
		memmove(data, p + off, len);
		free(p);
		break;
	}
	qunlock(ums);
	return count;
}

static long
dwrite(Usbfs *fs, Fid *fid, void *buf, long count, vlong offset)
{
	int bno, nb, len, off;
	ulong path;
	char *p;
	Ums *ums;
	Umsc *lun;
	char *data;

	ums = fs->dev->aux;
	lun = fs->aux;
	path = fid->qid.path & ~fs->qid;
	data = buf;
	qlock(ums);
	switch(path){
	default:
		qunlock(ums);
		werrstr(Eperm);
		return -1;
	case Qraw:
		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
			qunlock(ums);
			return -1;
		}
		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 = (uchar*)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 */
				qunlock(ums);
				return -1;
			}
			break;
		case Pstatus:
			lun->phase = Pcmd;
			qunlock(ums);
			werrstr("phase error");
			return -1;
		}
		break;
	case Qdata:
		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
			qunlock(ums);
			return -1;
		}
		bno = offset / lun->lbsize;
		nb = (offset + count + lun->lbsize-1) / lun->lbsize - bno;
		if(bno + nb > lun->blocks)
			nb = lun->blocks - bno;
		if(bno >= lun->blocks || nb == 0){
			count = 0;
			break;
		}
		if(nb * lun->lbsize > maxiosize)
			nb = maxiosize / lun->lbsize;
		p = emallocz(nb * lun->lbsize, 0);
		off = offset % lun->lbsize;
		len = count;
		if(off || (len % lun->lbsize) != 0){
			lun->offset = offset / lun->lbsize;
			count = SRread(lun, p, nb * lun->lbsize);
			if(count < 0){
				free(p);
				lun->lbsize = 0;	/* medium may have changed */
				qunlock(ums);
				return -1;
			}
			if(off + len > count)
				len = count - off;
		}
		memmove(p+off, data, len);
		lun->offset = offset / lun->lbsize;
		count = SRwrite(lun, p, nb * lun->lbsize);
		if(count < 0){
			free(p);
			lun->lbsize = 0;	/* medium may have changed */
			qunlock(ums);
			return -1;
		}
		if(off+len > count)
			len = count - off;
		count = len;
		free(p);
		break;
	}
	qunlock(ums);
	return count;
}

int
findendpoints(Ums *ums)
{
	Ep *ep;
	Usbdev *ud;
	ulong csp;
	ulong sc;
	int i;
	int 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);

	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]");
	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 Usbfs 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;

	ARGBEGIN{
	case 'd':
		scsidebug(diskdebug);
		diskdebug++;
		break;
	default:
		return usage();
	}ARGEND
	if(argc != 0)
		return usage();

	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;
	}
	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", dev->id, i);
		lun->fs.dev = dev;
		incref(dev);
		lun->fs.aux = lun;
		usbfsadd(&lun->fs);
	}
	closedev(dev);
	return 0;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].