code: 9ferno

ref: 44ce0097b612a1fefd754065bdf8d9d2e5ef60c8
dir: /appl/lib/usb/usbmass.b/

View raw version
#
# Copyright © 2001 Vita Nuova Holdings Limited.
#
implement UsbDriver;

include "sys.m";
	sys: Sys;
include "usb.m";
	usb: Usb;
	Endpt, RD2H, RH2D: import Usb;

ENDPOINT_STALL: con 0;	# TO DO: should be in usb.m

readerpid: int;
setupfd, ctlfd: ref Sys->FD;
infd, outfd: ref Sys->FD;
inep, outep: ref Endpt;
cbwseq := 0;
capacity: big;
debug := 0;

lun: int;
blocksize: int;

kill(pid: int)
{
	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
	if (fd != nil)
		sys->fprint(fd, "kill");
}

reader(pidc: chan of int, fileio: ref Sys->FileIO)
{
	pid := sys->pctl(0, nil);
	pidc <-= pid;
	for(;;) alt{
	(offset, count, nil, rc) := <-fileio.read =>
		if (rc != nil) {
			if (int (offset%big blocksize) || count%blocksize) {
				rc <- = (nil, "unaligned read");
				continue;
			}
			offset /= big blocksize;
			count /= blocksize;
			buf := array [count * blocksize] of byte;
			if (scsiread10(lun, offset, count, buf) < 0) {
				scsirequestsense(lun);
				rc <- = (nil, "read error");
				continue;
			}
			rc <- = (buf, nil);
		}
	(offset, data, nil, wc) := <-fileio.write =>
		if(wc != nil){
			count := len data;
			if(int (offset%big blocksize) || count%blocksize){
				wc <-= (0, "unaligned write");
				continue;
			}
			offset /= big blocksize;
			count /= blocksize;
			if(scsiwrite10(lun, offset, count, data) < 0){
				scsirequestsense(lun);
				wc <-= (0, "write error");
				continue;
			}
			wc <-= (len data, nil);
		}
	}
	readerpid = -1;
}

massstoragereset(): int
{
	if (usb->setup(setupfd, Usb->RH2D | Usb->Rclass | Usb->Rinterface, 255, 0, 0, nil, nil) < 0) {
		sys->print("usbmass: storagereset failed\n");
		return -1;
	}
	return 0;
}
	
getmaxlun(): int
{
	buf := array[1] of byte;
	if (usb->setup(setupfd, Usb->RD2H | Usb->Rclass | Usb->Rinterface, 254, 0, 0, nil, buf) < 0) {
		sys->print("usbmass: getmaxlun failed\n");
		return -1;
	}
	return int buf[0];
}

#
# CBW:
#	sig[4]="USBC" tag[4] datalen[4] flags[1] lun[1] len[1] cmd[len]
#
sendcbw(dtl: int, outdir: int, lun: int, cmd: array of byte): int
{
	cbw := array [31] of byte;
	cbw[0] = byte 'U';
	cbw[1] = byte 'S';
	cbw[2] = byte 'B';
	cbw[3] = byte 'C';
	usb->put4(cbw[4:], ++cbwseq);
	usb->put4(cbw[8:], dtl);
	if (outdir)
		cbw[12] = byte RH2D;
	else
		cbw[12] = byte RD2H;
	cbw[13] = byte lun;
	cbw[14] = byte len cmd;
	cbw[15:] = cmd;
	rv := sys->write(outfd, cbw, len cbw);
	if (rv < 0) {
		sys->print("sendcbw: failed: %r\n");
		return -1;
	}
	if (rv != len cbw) {
		sys->print("sendcbw: truncated send\n");
		return -1;
	}
	return 0;
}

#
# CSW:
#	sig[4]="USBS" tag[4] residue[4] status[1]
#

recvcsw(tag: int): (int, int)
{
	if(debug)
		sys->print("recvcsw\n");
	buf := array [13] of byte;
	if (sys->read(infd, buf, len buf) != len buf) {
		sys->print("recvcsw: read failed: %r\n");
		return (-1, -1);
	}
	if (usb->get4(buf) != (('S'<<24)|('B'<<16)|('S'<<8)|'U')) {
		sys->print("recvcsw: signature wrong\n");
		return (-1, -1);
	}
	recvtag := usb->get4(buf[4:]);
	if (recvtag != tag) {
		sys->print("recvcsw: tag does not match: sent %d recved %d\n", tag, recvtag);
		return (-1, -1);
	}
	residue := usb->get4(buf[8:]);
	status := int buf[12];
	if(debug)
		sys->print("recvcsw: residue %d status %d\n", residue, status);
	return (residue, status);
}

unstall(ep: ref Endpt)
{
	if(debug)
		sys->print("unstalling bulk %x\n", ep.addr);
	x := ep.addr & 16rF;
	sys->fprint(ctlfd, "unstall %d", x);
	sys->fprint(ctlfd, "data %d 0", x);
	if (usb->setclear_feature(setupfd, Usb->Rendpt, ENDPOINT_STALL, ep.addr, 0) < 0) {
		sys->print("unstall: clear_feature() failed: %r\n");
		return;
	}
}

warnfprint(fd: ref Sys->FD, s: string)
{
	if (sys->fprint(fd, "%s", s) != len s)
		sys->print("warning: writing %s failed: %r\n", s);
}

bulkread(lun: int, cmd: array of byte, buf: array of byte, dump: int): int
{
	if (sendcbw(len buf, 0, lun, cmd) < 0)
		return -1;
	got := 0;
	if (buf != nil) {
		while (got < len buf) {
			rv := sys->read(infd, buf[got:], len buf - got);
			if (rv < 0) {
				sys->print("bulkread: read failed: %r\n");
				break;
			}
			if(debug)
				sys->print("read %d\n", rv);
			got += rv;
			break;
		}
		if (dump) {
			for (i := 0; i < got; i++)
				sys->print("%.2ux", int buf[i]);
			sys->print("\n");
		}
		if (got == 0)
			unstall(inep);
	}
	(residue, status) := recvcsw(cbwseq);
	if (residue < 0) {
		unstall(inep);
		(residue, status) = recvcsw(cbwseq);
		if (residue < 0)
			return -1;
	}
	if (status != 0)
		return -1;
	return got;
}

bulkwrite(lun: int, cmd: array of byte, buf: array of byte): int
{
	if (sendcbw(len buf, 1, lun, cmd) < 0)
		return -1;
	got := 0;
	if (buf != nil) {
		while (got < len buf) {
			rv := sys->write(outfd, buf[got:], len buf - got);
			if (rv < 0) {
				sys->print("bulkwrite: write failed: %r\n");
				break;
			}
			if(debug)
				sys->print("write %d\n", rv);
			got += rv;
			break;
		}
		if (got == 0)
			unstall(outep);
	}
	(residue, status) := recvcsw(cbwseq);
	if (residue < 0) {
		unstall(inep);
		(residue, status) = recvcsw(cbwseq);
		if (residue < 0)
			return -1;
	}
	if (status != 0)
		return -1;
	return got;
}

scsiinquiry(lun: int): int
{
	buf := array [36] of byte;	# don't use 255, many devices can't cope
	cmd := array [6] of byte;
	cmd[0] = byte 16r12;
	cmd[1] = 	byte (lun << 5);
	cmd[2] = byte 0;
	cmd[3] = byte 0;
	cmd[4] = byte len buf;
	cmd[5]  = byte 0;
	got := bulkread(lun, cmd, buf, 0);
	if (got < 0)
		return -1;
	if (got < 36) {
		sys->print("scsiinquiry: too little data\n");
		return -1;
	}
	t := int buf[0] & 16r1f;
	if(debug)
		sys->print("scsiinquiry: type %d/%s\n", t, string buf[8:35]);
	if (t != 0)
		return -1;
	return 0;
}

scsireadcapacity(lun: int): int
{
#	warnfprint(ctlfd, "debug 0 1");
#	warnfprint(ctlfd, "debug 1 1");
#	warnfprint(ctlfd, "debug 2 1");
	buf := array [8] of byte;
	cmd := array [10] of byte;
	cmd[0] = byte 16r25;
	cmd[1] = 	byte (lun << 5);
	cmd[2] = byte 0;
	cmd[3] = byte 0;
	cmd[4] = byte 0;
	cmd[5]  = byte 0;
	cmd[6]  = byte 0;
	cmd[7]  = byte 0;
	cmd[8]  = byte 0;
	cmd[9] = byte 0;
	got := bulkread(lun, cmd, buf, 0);
	if (got < 0)
		return -1;
	if (got != len buf) {
		sys->print("scsireadcapacity: returned data not right size\n");
		return -1;
	}
	blocksize = usb->bigget4(buf[4:]);
	lba := big usb->bigget4(buf[0:]) & 16rFFFFFFFF;
	capacity = big blocksize * (lba+big 1);
	if(debug)
		sys->print("block size %d lba %bd cap %bd\n", blocksize, lba, capacity);
	return 0;
}

scsirequestsense(lun: int): int
{
#	warnfprint(ctlfd, "debug 0 1");
#	warnfprint(ctlfd, "debug 1 1");
#	warnfprint(ctlfd, "debug 2 1");
	buf := array [18] of byte;
	cmd := array [6] of byte;
	cmd[0] = byte 16r03;
	cmd[1] = 	byte (lun << 5);
	cmd[2] = byte 0;
	cmd[3] = byte 0;
	cmd[4] = byte len buf;
	cmd[5]  = byte 0;
	got := bulkread(lun, cmd, buf, 1);
	if (got < 0)
		return -1;
	return 0;
}

scsiread10(lun: int, offset: big, count: int, buf: array of byte): int
{
	cmd := array [10] of byte;
	cmd[0] = byte 16r28;
	cmd[1] = byte (lun << 5);
	usb->bigput4(cmd[2:], int offset); # TODO potential bug truncating big to int
	cmd[6] = byte 0;
	usb->bigput2(cmd[7:], count);
	cmd[9] = byte 0;
	got := bulkread(lun, cmd, buf, 0);
	if (got < 0)
		return -1;
	return 0;
}

scsiwrite10(lun: int, offset: big, count: int, buf: array of byte): int
{
	cmd := array [10] of byte;
	cmd[0] = byte 16r2A;
	cmd[1] = byte (lun << 5);
	usb->bigput4(cmd[2:], int offset); # TODO potential bug truncating big to int
	cmd[6] = byte 0;
	usb->bigput2(cmd[7:], count);
	cmd[9] = byte 0;
	got := bulkwrite(lun, cmd, buf);
	if (got < 0)
		return -1;
	return 0;
}

scsistartunit(lun: int, start: int): int
{
#	warnfprint(ctlfd, "debug 0 1");
#	warnfprint(ctlfd, "debug 1 1");
#	warnfprint(ctlfd, "debug 2 1");
	cmd := array [6] of byte;
	cmd[0] = byte 16r1b;
	cmd[1] = byte (lun << 5);
	cmd[2] = byte 0;
	cmd[3] = byte 0;
	cmd[4] = byte (start & 1);
	cmd[5]  = byte 0;
	got := bulkread(lun, cmd, nil, 0);
	if (got < 0)
		return -1;
	return 0;
}

init(usbmod: Usb, psetupfd, pctlfd: ref Sys->FD,
	nil: ref Usb->Device,
	conf: array of ref Usb->Configuration, path: string): int
{
	usb = usbmod;
	setupfd = psetupfd;
	ctlfd = pctlfd;

	sys = load Sys Sys->PATH;
	rv := usb->set_configuration(setupfd, conf[0].id);
	if (rv < 0)
		return rv;
	rv = massstoragereset();
	if (rv < 0)
		return rv;
	maxlun := getmaxlun();
	if (maxlun < 0)
		return maxlun;
	lun = 0;
	if(debug)
		sys->print("maxlun %d\n", maxlun);
	inep = outep = nil;
	epts := (hd conf[0].iface[0].altiface).ep;
	for(i := 0; i < len epts; i++)
		if(epts[i].etype == Usb->Ebulk){
			if(epts[i].d2h){
				if(inep == nil)
					inep = epts[i];
			}else{
				if(outep == nil)
					outep = epts[i];
			}
		}
	if(inep == nil || outep == nil){
		sys->print("can't find endpoints\n");
		return -1;
	}
	isrw := (inep.addr & 16rF) == (outep.addr & 16rF);
	if(!isrw){
		infd = openep(path, inep, Sys->OREAD);
		if(infd == nil)
			return -1;
		outfd = openep(path, outep, Sys->OWRITE);
		if(outfd == nil)
			return -1;
	}else{
		infd = outfd = openep(path, inep, Sys->ORDWR);
		if(infd == nil)
			return -1;
	}
	if (scsiinquiry(0) < 0)
		return -1;
	scsistartunit(lun, 1);
	if (scsireadcapacity(0) < 0) {
		scsirequestsense(0);
		if (scsireadcapacity(0) < 0)
			return -1;
	}
	fileio := sys->file2chan("/chan", "usbdisk");
	if (fileio == nil) {
		sys->print("file2chan failed: %r\n");
		return -1;
	}
	setlength("/chan/usbdisk", capacity);
#	warnfprint(ctlfd, "debug 0 1");
#	warnfprint(ctlfd, "debug 1 1");
#	warnfprint(ctlfd, "debug 2 1");
	pidc := chan of int;
	spawn reader(pidc, fileio);
	readerpid = <- pidc;
	return 0;
}

shutdown()
{
	if (readerpid >= 0)
		kill(readerpid);
}

openep(path: string, ep: ref Endpt, mode: int): ref Sys->FD
{
	if(debug)
		sys->print("ep %x maxpkt %d interval %d\n", ep.addr, ep.maxpkt, ep.interval);
	ms: string;
	case mode {
	Sys->OREAD => ms = "r";
	Sys->OWRITE => ms = "w";
	* => ms = "rw";
	}
	if(sys->fprint(ctlfd, "ep %d bulk %s %d 16", ep.addr&16rF, ms, ep.maxpkt) < 0)
		return nil;
	return sys->open(sys->sprint("%s/ep%ddata", path, ep.addr&16rF), mode);
}

setlength(f: string, size: big)
{
	d := sys->nulldir;
	d.length = size;
	sys->wstat(f, d);	# ignore errors since it fails on older kernels
}