shithub: purgatorio

ref: 94443daf8e248e65afc8d3f17f26efea22748b51
dir: /appl/cmd/usb/usbd.b/

View raw version
implement Usbd;

include "sys.m";
	sys: Sys;
include "draw.m";
include "string.m";
	str: String;
include "lock.m";
	lock: Lock;
	Semaphore: import lock;
include "arg.m";
	arg: Arg;

include "usb.m";
	usb: Usb;
	Device, Configuration, Endpt: import Usb;
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

Detached, Attached, Enabled, Assigned, Configured: con (iota);

Usbd: module
{
	init: fn(nil: ref Draw->Context, args: list of string);
};

Hub: adt {
	nport, pwrmode, compound, pwrms, maxcurrent, removable, pwrctl: int;
	ports: cyclic ref DDevice;
};
	
DDevice: adt {
	port: int;
	pids: list of int;
	parent: cyclic ref DDevice;
	next: cyclic ref DDevice;
	cfd, setupfd, rawfd: ref Sys->FD;
	id: int;
	ls: int;
	state: int;
	ep: array of ref Endpt;
	config: array of ref Usb->Configuration;
	hub: Hub;
	mod: UsbDriver;
	d: ref Device;
};

Line: adt {
	level: int;
	command: string;
	value: int;
	svalue: string;
};

ENUMERATE_POLL_INTERVAL: con 1000;
FAILED_ENUMERATE_RETRY_INTERVAL: con 10000;

verbose: int;
debug: int;
stderr: ref Sys->FD;

usbportfd: ref Sys->FD;
usbctlfd: ref Sys->FD;
usbctl0: ref Sys->FD;
usbsetup0: ref Sys->FD;

usbbase: string;

configsema, setupsema, treesema: ref Semaphore;


# UHCI style status which is returned by the driver.
UHCIstatus_Suspend: con 1 << 12;
UHCIstatus_PortReset: con 1 << 9;
UHCIstatus_SlowDevice: con 1 << 8;
UHCIstatus_ResumeDetect: con 1 << 6;
UHCIstatus_PortEnableChange: con 1 << 3;   
UHCIstatus_PortEnable: con 1 << 2;
UHCIstatus_ConnectStatusChange: con 1 << 1;	
UHCIstatus_DevicePresent: con 1 << 0;

obt()
{
#	sys->fprint(stderr, "%d waiting\n", sys->pctl(0, nil));
	setupsema.obtain();
#	sys->fprint(stderr, "%d got\n", sys->pctl(0, nil));
}

rel()
{
#	sys->fprint(stderr, "%d releasing\n", sys->pctl(0, nil));
	setupsema.release();
}

hubid(hub: ref DDevice): int
{
	if (hub == nil)
		return 0;
	return hub.id;
}

hubfeature(d: ref DDevice, p: int, feature: int, on: int): int
{
	rtyp: int;
	if (p == 0)
		rtyp = Usb->Rclass;
	else
		rtyp = Usb->Rclass | Usb->Rother;
	obt();
	rv := usb->setclear_feature(d.setupfd, rtyp, feature, p, on);
	rel();
	return rv;
}

portpower(hub: ref DDevice, port: int, on: int)
{
	if (verbose)
		sys->fprint(stderr, "portpower %d/%d %d\n", hubid(hub), port, on);
	if (hub == nil)
		return;
	if (port)
		hubfeature(hub, port, Usb->PORT_POWER, on);
}

countrootports(): int
{
	sys->seek(usbportfd, big 0, Sys->SEEKSTART);
	buf := array [256] of byte;
	n := sys->read(usbportfd, buf, len buf);
	if (n <= 0) {
		sys->fprint(stderr, "usbd: countrootports: error reading root port status\n");
		exit;
	}
	(nv, nil) := sys->tokenize(string buf[0: n], "\n");
	if (nv < 1) {
		sys->fprint(stderr, "usbd: countrootports: strange root port status\n");
		exit;
	}
	return nv;
}

portstatus(hub: ref DDevice, port: int): int
{
	rv: int;
#	setupsema.obtain();
	obt();
	if (hub == nil) {
		sys->seek(usbportfd, big 0, Sys->SEEKSTART);
		buf := array [256] of byte;
		n := sys->read(usbportfd, buf, len buf);
		if (n < 1) {
			sys->fprint(stderr, "usbd: portstatus: read error\n");
			rel();
			return 0;
		}
		(nil, l) := sys->tokenize(string buf[0: n], "\n");
		for(; l != nil; l = tl l){
			(nv, f) := sys->tokenize(hd l, " ");
			if(nv < 2){
				sys->fprint(stderr, "usbd: portstatus: odd status line\n");
				rel();
				return 0;
			}
			if(int hd f == port){
				(rv, nil) = usb->strtol(hd tl f, 16);
				# the status change bits are not used so mask them off
				rv &= 16rffff;
				break;
			}
		}
		if (l == nil) {
			sys->fprint(stderr, "usbd: portstatus: no status for port %d\n", port);
			rel();
			return 0;
		}
	}
	else
		rv = usb->get_status(hub.setupfd, port);
#	setupsema.release();
	rel();
	if (rv < 0)
		return 0;
	return rv;
}

portenable(hub: ref DDevice, port: int, enable: int)
{
	if (verbose)
		sys->fprint(stderr, "portenable %d/%d %d\n", hubid(hub), port, enable);
	if (hub == nil) {
		if (enable)
			sys->fprint(usbctlfd, "enable %d", port);
		else
			sys->fprint(usbctlfd, "disable %d", port);
		return;
	}
	if (port)
		hubfeature(hub, port, Usb->PORT_ENABLE, enable);
}

portreset(hub: ref DDevice, port: int)
{
	if (verbose)
		sys->fprint(stderr, "portreset %d/%d\n", hubid(hub), port);
	if (hub == nil) {
		if(0)sys->fprint(usbctlfd, "reset %d", port);
		for (i := 0; i < 4; ++i) {
	  		sys->sleep(20);			# min 10 milli second reset recovery.
	  		s := portstatus(hub, port);
	  		if ((s & UHCIstatus_PortReset) == 0)		# only leave when reset is finished.
				break;
		}
		return;
	}
	if (port)
		hubfeature(hub, port, Usb->PORT_RESET, 1);
	return;
}

devspeed(d: ref DDevice)
{
	sys->fprint(d.cfd, "speed %d", !d.ls);
	if (debug) {
		s: string;
		if (d.ls)
			s = "low";
		else
			s = "high";
		sys->fprint(stderr, "%d: set speed %s\n", d.id, s);
	}
}

devmaxpkt0(d: ref DDevice, size: int)
{
	sys->fprint(d.cfd, "maxpkt 0 %d", size);
	if (debug)
		sys->fprint(stderr, "%d: set maxpkt0 %d\n", d.id, size);
}

closedev(d: ref DDevice)
{
	d.cfd = usbctl0;
	d.rawfd = nil;
	d.setupfd = usbsetup0;
}

openusb(f: string, mode: int): ref Sys->FD
{
	fd := sys->open(usbbase + f, mode);
	if (fd == nil) {
		sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + f);
		raise "fail:open";
	}
	return fd;
}

opendevf(id: int, f: string, mode: int): ref Sys->FD
{
	fd := sys->open(usbbase + string id + "/" + f, mode);
	if (fd == nil) {
		sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + string id + "/" + f);
		exit;
	}
	return fd;
}

kill(pid: int): int
{
	if (debug)
		sys->print("killing %d\n", pid);
	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
	if (fd == nil) {
		sys->print("kill: open failed\n");
		return -1;
	}
	if (sys->write(fd, array of byte "kill", 4) != 4) {
		sys->print("kill: write failed\n");
		return -1;
	}
	return 0;
}

rdetach(d: ref DDevice)
{
	if (d.mod != nil) {
		d.mod->shutdown();
		d.mod = nil;
	}
	while (d.pids != nil) {
		if (verbose)
			sys->fprint(stderr, "kill %d\n", hd d.pids);
		kill(hd d.pids);
		d.pids = tl d.pids;
	}
	if (d.parent != nil) {
		last, hp: ref DDevice;
		last = nil;
		hp = d.parent.hub.ports;
		while (hp != nil && hp != d)
			hp = hp.next;
		if (last != nil)
			last.next = d.next;
		else
			d.parent.hub.ports = d.next;
	}
	if (d.hub.ports != nil) {
		for (c := d.hub.ports; c != nil; c = c.next) {
			c.parent = nil;
			rdetach(c);
		}
	}
	d.state = Detached;
	if (sys->fprint(d.cfd, "detach") < 0)
		sys->fprint(stderr, "detach failed\n");
	d.cfd = nil;
	d.rawfd = nil;
	d.setupfd = nil;
}

detach(d: ref DDevice)
{
	configsema.obtain();
	treesema.obtain();
	obt();
#	setupsema.obtain();

	if (verbose)
		sys->fprint(stderr, "detach %d\n", d.id);
	rdetach(d);
	if (verbose)
		sys->fprint(stderr, "detach %d done\n", d.id);
#	setupsema.release();
	rel();
	treesema.release();
	configsema.release();
}

readnum(fd: ref Sys->FD): int
{
	buf := array [16] of byte;
	n := sys->read(fd, buf, len buf);
	if (n <= 0)
		return -1;
	(rv , nil) := usb->strtol(string buf[0: n], 0);
	return rv;
}

setaddress(d: ref DDevice): int
{
	if (d.state == Assigned)
		return d.id;
	closedev(d);
	d.id = 0;
	d.cfd = openusb("new", Sys->ORDWR);
	id := readnum(d.cfd);
	if (id <= 0) {
		if (debug)
			sys->fprint(stderr, "usbd: usb/new ID: %r\n");
		d.cfd = nil;
		return -1;
	}
#	setupsema.obtain();
	obt();
	if (usb->set_address(d.setupfd, id) < 0) {
#		setupsema.release();
		rel();
		return -1;
	}
#	setupsema.release();
	rel();
	d.id = id;
	d.state = Assigned;
	return id;
}

#optstring(d: ref DDevice, langids: list of int, desc: string, index: int)
#{
#	if (index) {
#		buf := array [256] of byte;
#		while (langids != nil) {
#			nr := usb->get_descriptor(d.setupfd, Usb->Rstandard, Usb->STRING, index, hd langids, buf);
#			if (nr > 2) {
#				sys->fprint(stderr, "%s: ", desc);
#				usbdump->desc(d, -1, buf[0: nr]);
#			}
#			langids = tl langids;
#		}
#	}
#}

langid(d: ref DDevice): (list of int)
{
	l: list of int;
	buf := array [256] of byte;
	nr := usb->get_standard_descriptor(d.setupfd, Usb->STRING, 0, buf);
	if (nr < 4)
		return nil;
	if (nr & 1)
		nr--;
	l = nil;
	for (i := nr - 2; i >= 2; i -= 2)
		l = usb->get2(buf[i:]) :: l;
	return l;
}

describedevice(d: ref DDevice): int
{
	obt();
	devmaxpkt0(d, 64);				# guess 64 byte max packet to avoid overrun on read
	for (x := 0; x < 3; x++) {			# retry 3 times
		d.d = usb->get_parsed_device_descriptor(d.setupfd);
		if (d.d != nil)
			break;
		sys->sleep(200);			# tolerate out of spec. devices
	}

	if (d.d == nil) {
		rel();
		return -1;
	}

	if (d.d.maxpkt0 != 64) {
		devmaxpkt0(d, d.d.maxpkt0);
		d.d = usb->get_parsed_device_descriptor(d.setupfd);
		if (d.d == nil) {
			rel();
			return -1;
		}
	}

	rel();

	if (verbose) {
		sys->fprint(stderr, "usb %x.%x", d.d.usbmajor, d.d.usbminor);
		sys->fprint(stderr, " class %d subclass %d proto %d [%s] max0 %d",
			d.d.class, d.d.subclass, d.d.proto,
			usb->sclass(d.d.class, d.d.subclass, d.d.proto), d.d.maxpkt0);
		sys->fprint(stderr, " vendor 0x%.4x product 0x%.4x rel %x.%x",
			d.d.vid, d.d.did, d.d.relmajor, d.d.relminor);
		sys->fprint(stderr, " nconf %d", d.d.nconf);
		sys->fprint(stderr, "\n");
		obt();
		l := langid(d);
		if (l != nil) {
			l2 := l;
			sys->fprint(stderr, "langids [");
			while (l2 != nil) {
				sys->fprint(stderr, " %d", hd l2);
				l2 = tl l2;
			}
			sys->fprint(stderr, "]\n");
		}
#		optstring(d, l, "manufacturer", int buf[14]);
#		optstring(d, l, "product", int buf[15]);
#		optstring(d, l, "serial number", int buf[16]);
		rel();
	}
	return 0;
}

describehub(d: ref DDevice): int
{
	b := array [256] of byte;
#	setupsema.obtain();
	obt();
	nr := usb->get_class_descriptor(d.setupfd, 0, 0, b);
	if (nr < Usb->DHUBLEN) {
#		setupsema.release();
		rel();
		sys->fprint(stderr, "usbd: error reading hub descriptor: got %d of %d\n", nr, Usb->DHUBLEN);
		return -1;
	}
#	setupsema.release();
	rel();
	if (verbose)
		sys->fprint(stderr, "nport %d charac 0x%.4ux pwr %dms current %dmA remov 0x%.2ux pwrctl 0x%.2ux",
			int b[2], usb->get2(b[3:]), int b[5] * 2, int b[6] * 2, int b[7], int b[8]);
	d.hub.nport = int b[2];
	d.hub.pwrms = int b[5] * 2;
	d.hub.maxcurrent = int b[6] * 2;
	char := usb->get2(b[3:]);
	d.hub.pwrmode = char & 3;
	d.hub.compound = (char & 4) != 0;
	d.hub.removable = int b[7];
	d.hub.pwrctl = int b[8];
	return 0;
}

loadconfig(d: ref DDevice, n: int): int
{
	obt();
	d.config[n] = usb->get_parsed_configuration_descriptor(d.setupfd, n);
	if (d.config[n] == nil) {
		rel();
		sys->fprint(stderr, "usbd: error reading configuration descriptor\n");
		return -1;
	}
	rel();
	if (verbose)
		usb->dump_configuration(stderr, d.config[n]);
	return 0;
}

#setdevclass(d: ref DDevice, n: int)
#{
#	dd := d.config[n];
#	if (dd != nil)
#		sys->fprint(d.cfd, "class %d %d %d %d %d", d.d.nconf, n, dd.class, dd.subclass, dd.proto);
#}

setconfig(d: ref DDevice, n: int): int
{
	obt();
	rv := usb->set_configuration(d.setupfd, n);
	rel();
	if (rv < 0)
		return -1;
	d.state = Configured;
	return 0;
}

configure(hub: ref DDevice, port: int): ref DDevice
{
	configsema.obtain();
	portreset(hub, port);
	sys->sleep(300);				# long sleep necessary for strange hardware....
#	sys->sleep(20);
	s := portstatus(hub, port);
	s = portstatus(hub, port);

	if (debug)
		sys->fprint(stderr, "port %d status 0x%ux\n", port, s);

	if ((s & UHCIstatus_DevicePresent) == 0) {
		configsema.release();
		return nil;
	}

	if ((s & UHCIstatus_PortEnable) == 0) {
		if (debug)
			sys->fprint(stderr, "hack: re-enabling port %d\n", port);
		portenable(hub, port, 1);
		s = portstatus(hub, port);
		if (debug)
			sys->fprint(stderr, "port %d status now 0x%.ux\n", port, s);
	}

	d := ref DDevice;
	d.port = port;
	d.cfd = usbctl0;
	d.setupfd = usbsetup0;
	d.id = 0;
	if (hub == nil)
		d.ls = (s & UHCIstatus_SlowDevice) != 0;
	else
		d.ls = (s & (1 << 9)) != 0;
	d.state = Enabled;
	devspeed(d);
	if (describedevice(d) < 0) {
		portenable(hub, port, 0);
		configsema.release();
		return nil;
	}
	if (setaddress(d) < 0) {
		portenable(hub, port, 0);
		configsema.release();
		return nil;
	}
	d.setupfd = opendevf(d.id, "setup", Sys->ORDWR);
	d.cfd = opendevf(d.id, "ctl", Sys->ORDWR);
	devspeed(d);
	devmaxpkt0(d, d.d.maxpkt0);
	d.config = array [d.d.nconf] of ref Configuration;
	for (i := 0; i < d.d.nconf; i++) {
		loadconfig(d, i);
#		setdevclass(d, i);
	}
	if (hub != nil) {
		treesema.obtain();
		d.parent = hub;
		d.next = hub.hub.ports;
		hub.hub.ports = d;
		treesema.release();
	}
	configsema.release();
	return d;
}

enumerate(hub: ref DDevice, port: int)
{
	if (hub != nil)
		hub.pids = sys->pctl(0, nil) :: hub.pids;
	reenumerate := 0;
	for (;;) {
		if (verbose)
			sys->fprint(stderr, "enumerate: starting\n");
		if ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0) {
			if (verbose)
				sys->fprint(stderr, "%d: port %d empty\n", hubid(hub), port);
			do {
				sys->sleep(ENUMERATE_POLL_INTERVAL);
			} while ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0);
		}
		if (verbose)
			sys->fprint(stderr, "%d: port %d attached\n", hubid(hub), port);
		# Δt3 (TATTDB) guarantee 100ms after attach detected
		sys->sleep(200);
		d := configure(hub, port);
		if (d == nil) {
			if (verbose)
				sys->fprint(stderr, "%d: can't configure port %d\n", hubid(hub), port);
		}
		else if (d.d.class == Usb->CL_HUB) {
			i: int;
			if (setconfig(d, 1) < 0) {
				if (verbose)
					sys->fprint(stderr, "%d: can't set configuration for hub on port %d\n", hubid(hub), port);
				detach(d);
				d = nil;
			}
			else if (describehub(d) < 0) {
				if (verbose)
					sys->fprint(stderr, "%d: failed to describe hub on port %d\n", hubid(hub), port);
				detach(d);
				d = nil;
			}
			else {
				for (i = 1; i <= d.hub.nport; i++)
					portpower(d, i, 1);
				sys->sleep(d.hub.pwrms);
				for (i = 1; i <= d.hub.nport; i++)
					spawn enumerate(d, i);
			}
		}
		else if (d.d.nconf >= 1 && (path := searchdriverdatabase(d.d, d.config[0])) != nil) {
			d.mod = load UsbDriver path;
			if (d.mod == nil)
				sys->fprint(stderr, "usbd: failed to load %s\n", path);
			else {
				rv := d.mod->init(usb, d.setupfd, d.cfd, d.d, d.config, usbbase + string d.id + "/");
				if (rv == -11) {
					sys->fprint(stderr, "usbd: %s: reenumerate\n", path);
					d.mod = nil;
					reenumerate = 1;
				}	
				else if (rv < 0) {
					sys->fprint(stderr, "usbd: %s:init failed\n", path);
					d.mod = nil;
				}
				else if (verbose)
					sys->fprint(stderr, "%s running\n", path);
			}
		}
		else if (setconfig(d, 1) < 0) {
			if (verbose)
				sys->fprint(stderr, "%d: can't set configuration for port %d\n", hubid(hub), port);
			detach(d);
			d = nil;
		}
		if (!reenumerate) {
			if (d != nil) {
				# wait for it to be unplugged
				while (portstatus(hub, port) & UHCIstatus_DevicePresent)
					sys->sleep(ENUMERATE_POLL_INTERVAL);
			}
			else {
				# wait a bit and prod it again
				if (portstatus(hub, port) & UHCIstatus_DevicePresent)
					sys->sleep(FAILED_ENUMERATE_RETRY_INTERVAL);
			}
		}
		if (d != nil) {
			detach(d);
			d = nil;
		}
		reenumerate = 0;
	}
}

lines: array of Line;

searchdriverdatabase(d: ref Device, conf: ref Configuration): string
{
	backtracking := 0;
	level := 0;
	for (i := 0; i < len lines; i++) {
		if (verbose > 1)
			sys->fprint(stderr, "search line %d: lvl %d cmd %s val %d (back %d lvl %d)\n",
				i, lines[i].level, lines[i].command, lines[i].value, backtracking, level);
		if (backtracking) {
			if (lines[i].level > level)
				continue;
			backtracking = 0;
		}
		if (lines[i].level != level) {
			level = 0;
			backtracking = 1;
		}
		case lines[i].command {
		"class" =>
			if (d.class != 0) {
				if (lines[i].value != d.class)
					backtracking = 1;
			}
			else if (lines[i].value != (hd conf.iface[0].altiface).class)
				backtracking = 1;
		"subclass" =>
			if (d.class != 0) {
				if (lines[i].value != d.subclass)
					backtracking = 1;
			}
			else if (lines[i].value != (hd conf.iface[0].altiface).subclass)
				backtracking = 1;
		"proto" =>
			if (d.class != 0) {
				if (lines[i].value != d.proto)
					backtracking = 1;
			}
			else if (lines[i].value != (hd conf.iface[0].altiface).proto)
				backtracking = 1;
		"vendor" =>
			if (lines[i].value != d.vid)
				backtracking  =1;
		"product" =>
			if (lines[i].value != d.did)
				backtracking  =1;
		"load" =>
			return lines[i].svalue;
		* =>
			continue;
		}
		if (!backtracking)
			level++;
	}
	return nil;
}

loaddriverdatabase()
{
	newlines: array of Line;

	if (bufio == nil)
		bufio = load Bufio Bufio->PATH;

	iob := bufio->open(Usb->DATABASEPATH, Sys->OREAD);
	if (iob == nil) {
		sys->fprint(stderr, "usbd: couldn't open %s: %r\n", Usb->DATABASEPATH);
		return;
	}
	lines = array[100] of Line;
	lc := 0;
	while ((line := iob.gets('\n')) != nil) {
		if (line[0] == '#')
			continue;
		level := 0;
		while (line[0] == '\t') {
			level++;
			line = line[1:];
		}
		(n, l) := sys->tokenize(line[0: len line - 1], "\t ");
		if (n != 2)
			continue;
		if (lc >= len lines) {
			newlines = array [len lines * 2] of Line;
			newlines[0:] = lines[0: len lines];
			lines = newlines;
		}
		lines[lc].level = level;
		lines[lc].command = hd l;
		case hd l {
		"class" or "subclass" or "proto" or "vendor" or "product" =>
			(lines[lc].value, nil) = usb->strtol(hd tl l, 0);
		"load" =>
			lines[lc].svalue = hd tl l;
		* =>
			continue;
		}
		lc++;
	}
	if (verbose)
		sys->fprint(stderr, "usbd: loaded %d lines\n", lc);
	newlines = array [lc] of Line;
	newlines[0:] = lines[0 : lc];
	lines = newlines;
}

init(nil: ref Draw->Context, args: list of string)
{
	usbbase = "/dev/usbh/";
	sys = load Sys Sys->PATH;
	str = load String String->PATH;

	lock = load Lock Lock->PATH;
	lock->init();

	usb = load Usb Usb->PATH;
	usb->init();

	arg = load Arg Arg->PATH;

	stderr = sys->fildes(2);

	verbose = 0;
	debug = 0;

	arg->init(args);
	arg->setusage("usbd [-dv] [-i interface]");
	while ((c := arg->opt()) != 0)
		case c {
		'v' => verbose = 1;
		'd' => debug = 1;
		'i' => usbbase = arg->earg() + "/";
		* => arg->usage();
		}
	args = arg->argv();

	usbportfd = openusb("port", Sys->OREAD);
	usbctlfd = sys->open(usbbase + "ctl", Sys->OWRITE);
	if(usbctlfd == nil)
		usbctlfd = openusb("port", Sys->OWRITE);
	usbctl0 = opendevf(0, "ctl", Sys->ORDWR);
	usbsetup0 = opendevf(0, "setup", Sys->ORDWR);
	setupsema = Semaphore.new();
	configsema = Semaphore.new();
	treesema = Semaphore.new();
	loaddriverdatabase();
	ports := countrootports();
	if (verbose)
		sys->print("%d root ports found\n", ports);
	for (p := 2; p <= ports; p++)
		spawn enumerate(nil, p);
	if (p >= 1)
		enumerate(nil, 1);
}