Plan 9 from Bell Labs’s /usr/web/sources/contrib/miller/usb/storage/usbsfs.c

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


#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <bio.h>
#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; i<d->nconf; 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);
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.