#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include "usb.h"
#include "usbfs.h"
#include "usbd.h"

static Channel *portc;
static int win;
static int verbose;

int mainstacksize = Stack;
static Hub *hubs;
static int nhubs;
static int mustdump;
static int pollms = Pollms;

static char *dsname[] = { "disabled", "attached", "configed" };

static int
hubfeature(Hub *h, int port, int f, int on)
{
	int cmd;

	if(on)
		cmd = Rsetfeature;
	else
		cmd = Rclearfeature;
	return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
}

/*
 * This may be used to detect overcurrent on the hub
 */
static void
checkhubstatus(Hub *h)
{
	uchar buf[4];
	int sts;

	if(h->isroot)	/* not for root hubs */
		return;
	if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){
		dprint(2, "%s: get hub status: %r\n", h->dev->dir);
		return;
	}
	sts = GET2(buf);
	dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
}

static int
confighub(Hub *h)
{
	int type;
	uchar buf[128];	/* room for extra descriptors */
	int i;
	Usbdev *d;
	DHub *dd;
	Port *pp;
	int nr;
	int nmap;
	uchar *PortPwrCtrlMask;
	int offset;
	int mask;

	d = h->dev->usb;
	for(i = 0; i < nelem(d->ddesc); i++)
		if(d->ddesc[i] == nil)
			break;
		else if(d->ddesc[i]->data.bDescriptorType == Dhub){
			dd = (DHub*)&d->ddesc[i]->data;
			nr = Dhublen;
			goto Config;
		}
	type = Rd2h|Rclass|Rdev;
	nr = usbcmd(h->dev, type, Rgetdesc, Dhub<<8|0, 0, buf, sizeof buf);
	if(nr < Dhublen){
		dprint(2, "%s: %s: getdesc hub: %r\n", argv0, h->dev->dir);
		return -1;
	}
	dd = (DHub*)buf;
Config:
	h->nport = dd->bNbrPorts;
	nmap = 1 + h->nport/8;
	if(nr < 7 + 2*nmap){
		fprint(2, "%s: %s: descr. too small\n", argv0, h->dev->dir);
		return -1;
	}
	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
	h->pwrms = dd->bPwrOn2PwrGood*2;
	if(h->pwrms < Powerdelay)
		h->pwrms = Powerdelay;
	h->maxcurrent = dd->bHubContrCurrent;
	h->pwrmode = dd->wHubCharacteristics[0] & 3;
	h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;
	h->leds = (dd->wHubCharacteristics[0] & (1<<7)) != 0;
	PortPwrCtrlMask = dd->DeviceRemovable + nmap;
	for(i = 1; i <= h->nport; i++){
		pp = &h->port[i];
		offset = i/8;
		mask = 1<<(i%8);
		pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;
		pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
	}
	return 0;
}

static void
configroothub(Hub *h)
{
	Dev *d;
	char buf[128];
	char *p;
	int nr;

	d = h->dev;
	h->nport = 2;
	h->maxpkt = 8;
	seek(d->cfd, 0, 0);
	nr = read(d->cfd, buf, sizeof(buf)-1);
	if(nr < 0)
		goto Done;
	buf[nr] = 0;

	p = strstr(buf, "ports ");
	if(p == nil)
		fprint(2, "%s: %s: no port information\n", argv0, d->dir);
	else
		h->nport = atoi(p+6);
	p = strstr(buf, "maxpkt ");
	if(p == nil)
		fprint(2, "%s: %s: no maxpkt information\n", argv0, d->dir);
	else
		h->maxpkt = atoi(p+7);
Done:
	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
	dprint(2, "%s: %s: ports %d maxpkt %d\n", argv0, d->dir, h->nport, h->maxpkt);
}

Hub*
newhub(char *fn, Dev *d)
{
	Hub *h;
	int i;
	Usbdev *ud;

	h = emallocz(sizeof(Hub), 1);
	h->isroot = (d == nil);
	if(h->isroot){
		h->dev = opendev(fn);
		if(h->dev == nil){
			fprint(2, "%s: opendev: %s: %r", argv0, fn);
			goto Fail;
		}
		if(opendevdata(h->dev, ORDWR) < 0){
			fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);
			goto Fail;
		}
		configroothub(h);	/* never fails */
	}else{
		h->dev = d;
		if(confighub(h) < 0){
			fprint(2, "%s: %s: config: %r\n", argv0, fn);
			goto Fail;
		}
	}
	if(h->dev == nil){
		fprint(2, "%s: opendev: %s: %r\n", argv0, fn);
		goto Fail;
	}
	devctl(h->dev, "hub");
	ud = h->dev->usb;
	if(h->isroot)
		devctl(h->dev, "info roothub csp %#08ux ports %d",
			0x000009, h->nport);
	else{
		devctl(h->dev, "info hub csp %#08ulx ports %d %q %q",
			ud->csp, h->nport, ud->vendor, ud->product);
		for(i = 1; i <= h->nport; i++)
			if(hubfeature(h, i, Fportpower, 1) < 0)
				fprint(2, "%s: %s: power: %r\n", argv0, fn);
		sleep(h->pwrms);
		for(i = 1; i <= h->nport; i++)
			if(h->leds != 0)
				hubfeature(h, i, Fportindicator, 1);
	}
	h->next = hubs;
	hubs = h;
	nhubs++;
	dprint(2, "%s: hub %#p allocated:", argv0, h);
	dprint(2, " ports %d pwrms %d max curr %d pwrm %d cmp %d leds %d\n",
		h->nport, h->pwrms, h->maxcurrent,
		h->pwrmode, h->compound, h->leds);
	incref(h->dev);
	return h;
Fail:
	if(d != nil)
		devctl(d, "detach");
	free(h->port);
	free(h);
	dprint(2, "%s: hub %#p failed to start:", argv0, h);
	return nil;
}

static void portdetach(Hub *h, int p);

/*
 * If during enumeration we get an I/O error the hub is gone or
 * in pretty bad shape. Because of retries of failed usb commands
 * (and the sleeps they include) it can take a while to detach all
 * ports for the hub. This detaches all ports and makes the hub void.
 * The parent hub will detect a detach (probably right now) and
 * close it later.
 */
static void
hubfail(Hub *h)
{
	int i;

	for(i = 1; i <= h->nport; i++)
		portdetach(h, i);
	h->failed = 1;
}

static void
closehub(Hub *h)
{
	Hub **hl;

	dprint(2, "%s: closing hub %#p\n", argv0, h);
	for(hl = &hubs; *hl != nil; hl = &(*hl)->next)
		if(*hl == h)
			break;
	if(*hl == nil)
		sysfatal("closehub: no hub");
	*hl = h->next;
	nhubs--;
	hubfail(h);		/* detach all ports */
	free(h->port);
	assert(h->dev != nil);
	devctl(h->dev, "detach");
	closedev(h->dev);
	free(h);
}

static int
portstatus(Hub *h, int p)
{
	Dev *d;
	uchar buf[4];
	int t;
	int sts;
	int dbg;

	dbg = usbdebug;
	if(dbg != 0 && dbg < 4)
		usbdebug = 1;	/* do not be too chatty */
	d = h->dev;
	t = Rd2h|Rclass|Rother;
	if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)
		sts = -1;
	else
		sts = GET2(buf);
	usbdebug = dbg;
	return sts;
}

static char*
stsstr(int sts)
{
	static char s[80];
	char *e;

	e = s;
	if(sts&PSsuspend)
		*e++ = 'z';
	if(sts&PSreset)
		*e++ = 'r';
	if(sts&PSslow)
		*e++ = 'l';
	if(sts&PShigh)
		*e++ = 'h';
	if(sts&PSchange)
		*e++ = 'c';
	if(sts&PSenable)
		*e++ = 'e';
	if(sts&PSstatuschg)
		*e++ = 's';
	if(sts&PSpresent)
		*e++ = 'p';
	if(e == s)
		*e++ = '-';
	*e = 0;
	return s;
}

static int
getmaxpkt(Dev *d, int islow)
{
	uchar buf[64];	/* More room to try to get device-specific descriptors */
	DDev *dd;

	dd = (DDev*)buf;
	if(islow)
		dd->bMaxPacketSize0 = 8;
	else
		dd->bMaxPacketSize0 = 64;
	if(usbcmd(d, Rd2h|Rstd|Rdev, Rgetdesc, Ddev<<8|0, 0, buf, sizeof(buf)) < 0)
		return -1;
	return dd->bMaxPacketSize0;
}

/*
 * BUG: does not consider max. power avail.
 */
static Dev*
portattach(Hub *h, int p, int sts)
{
	Dev *d;
	Port *pp;
	Dev *nd;
	char fname[80];
	char buf[40];
	char *sp;
	int mp;
	int nr;

	d = h->dev;
	pp = &h->port[p];
	nd = nil;
	pp->state = Pattached;
	dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);
	sleep(Connectdelay);
	if(hubfeature(h, p, Fportenable, 1) < 0)
		dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
	sleep(Enabledelay);
	if(hubfeature(h, p, Fportreset, 1) < 0){
		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	sleep(Resetdelay);
	sts = portstatus(h, p);
	if(sts < 0)
		goto Fail;
	if((sts & PSenable) == 0){
		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
		hubfeature(h, p, Fportenable, 1);
		sts = portstatus(h, p);
		if((sts & PSenable) == 0)
			goto Fail;
	}
	sp = "full";
	if(sts & PSslow)
		sp = "low";
	if(sts & PShigh)
		sp = "high";
	dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts);

	if(devctl(d, "newdev %s %d", sp, p) < 0){
		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	seek(d->cfd, 0, 0);
	nr = read(d->cfd, buf, sizeof(buf)-1);
	if(nr == 0){
		fprint(2, "%s: %s: port %d: newdev: eof\n", argv0, d->dir, p);
		goto Fail;
	}
	if(nr < 0){
		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	buf[nr] = 0;
	snprint(fname, sizeof(fname), "/dev/usb/%s", buf);
	nd = opendev(fname);
	if(nd == nil){
		fprint(2, "%s: %s: port %d: opendev: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	if(usbdebug > 2)
		devctl(nd, "debug 1");
	if(opendevdata(nd, ORDWR) < 0){
		fprint(2, "%s: %s: opendevdata: %r\n", argv0, nd->dir);
		goto Fail;
	}
	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	if(devctl(nd, "address") < 0){
		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
		goto Fail;
	}

	mp=getmaxpkt(nd, strcmp(sp, "low") == 0);
	if(mp < 0){
		dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);
		goto Fail;
	}else{
		dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);
		devctl(nd, "maxpkt %d", mp);
	}
	if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)
		dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",
			argv0, d->dir, p, nd->dir);
	if(configdev(nd) < 0){
		dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	/*
	 * We always set conf #1. BUG.
	 */
	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
		unstall(nd, nd, Eout);
		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
			goto Fail;
	}
	dprint(2, "%s: %U", argv0, nd);
	pp->state = Pconfiged;
	dprint(2, "%s: %s: port %d: configed: %s\n",
			argv0, d->dir, p, nd->dir);
	return pp->dev = nd;
Fail:
	pp->state = Pdisabled;
	pp->sts = 0;
	if(pp->hub != nil)
		pp->hub = nil;	/* hub closed by enumhub */
	hubfeature(h, p, Fportenable, 0);
	if(nd != nil)
		devctl(nd, "detach");
	closedev(nd);
	return nil;
}

static void
portdetach(Hub *h, int p)
{
	Dev *d;
	Port *pp;
	extern void usbfsgone(char*);
	d = h->dev;
	pp = &h->port[p];

	/*
	 * Clear present, so that we detect an attach on reconnects.
	 */
	pp->sts &= ~(PSpresent|PSenable);

	if(pp->state == Pdisabled)
		return;
	pp->state = Pdisabled;
	dprint(2, "%s: %s: port %d: detached\n", argv0, d->dir, p);

	if(pp->hub != nil){
		closehub(pp->hub);
		pp->hub = nil;
	}
	if(pp->devmaskp != nil)
		putdevnb(pp->devmaskp, pp->devnb);
	pp->devmaskp = nil;
	if(pp->dev != nil){
		devctl(pp->dev, "detach");
		usbfsgone(pp->dev->dir);
		closedev(pp->dev);
		pp->dev = nil;
	}
}

/*
 * The next two functions are included to
 * perform a port reset asked for by someone (usually a driver).
 * This must be done while no other device is in using the
 * configuration address and with care to keep the old address.
 * To keep drivers decoupled from usbd they write the reset request
 * to the #u/usb/epN.0/ctl file and then exit.
 * This is unfortunate because usbd must now poll twice as much.
 *
 * An alternative to this reset process would be for the driver to detach
 * the device. The next function could see that, issue a port reset, and
 * then restart the driver once to see if it's a temporary error.
 *
 * The real fix would be to use interrupt endpoints for non-root hubs
 * (would probably make some hubs fail) and add an events file to
 * the kernel to report events to usbd. This is a severe change not
 * yet implemented.
 */
static int
portresetwanted(Hub *h, int p)
{
	char buf[5];
	Port *pp;
	Dev *nd;

	pp = &h->port[p];
	nd = pp->dev;
	if(nd != nil && nd->cfd >= 0 && pread(nd->cfd, buf, 5, 0LL) == 5)
		return strncmp(buf, "reset", 5) == 0;
	else
		return 0;
}

static void
portreset(Hub *h, int p)
{
	int sts;
	Dev *d, *nd;
	Port *pp;

	d = h->dev;
	pp = &h->port[p];
	nd = pp->dev;
	dprint(2, "%s: %s: port %d: resetting\n", argv0, d->dir, p);
	if(hubfeature(h, p, Fportreset, 1) < 0){
		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	sleep(Resetdelay);
	sts = portstatus(h, p);
	if(sts < 0)
		goto Fail;
	if((sts & PSenable) == 0){
		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
		hubfeature(h, p, Fportenable, 1);
		sts = portstatus(h, p);
		if((sts & PSenable) == 0)
			goto Fail;
	}
	nd = pp->dev;
	opendevdata(nd, ORDWR);
	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	if(devctl(nd, "address") < 0){
		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
		goto Fail;
	}
	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
		unstall(nd, nd, Eout);
		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
			goto Fail;
	}
	if(nd->dfd >= 0)
		close(nd->dfd);
	return;
Fail:
	pp->state = Pdisabled;
	pp->sts = 0;
	if(pp->hub != nil)
		pp->hub = nil;	/* hub closed by enumhub */
	hubfeature(h, p, Fportenable, 0);
	if(nd != nil)
		devctl(nd, "detach");
	closedev(nd);
}

static int
portgone(Port *pp, int sts)
{
	if(sts < 0)
		return 1;
	/*
	 * If it was enabled and it's not now then it may be reconnect.
	 * We pretend it's gone and later we'll see it as attached.
	 */
	if((pp->sts & PSenable) != 0 && (sts & PSenable) == 0)
		return 1;
	return (pp->sts & PSpresent) != 0 && (sts & PSpresent) == 0;
}

static int
enumhub(Hub *h, int p)
{
	int sts;
	Dev *d;
	Port *pp;
	int onhubs;

	if(h->failed)
		return 0;
	d = h->dev;
	if(usbdebug > 3)
		fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);

	sts = portstatus(h, p);
	if(sts < 0){
		hubfail(h);		/* avoid delays on detachment */
		return -1;
	}
	pp = &h->port[p];
	onhubs = nhubs;
	if((sts & PSsuspend) != 0){
		if(hubfeature(h, p, Fportenable, 1) < 0)
			dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
		sleep(Enabledelay);
		sts = portstatus(h, p);
		fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
	}
	if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
		if(portattach(h, p, sts) != nil)
			if(startdev(pp) < 0)
				portdetach(h, p);
	}else if(portgone(pp, sts))
		portdetach(h, p);
	else if(portresetwanted(h, p))
		portreset(h, p);
	else if(pp->sts != sts){
		dprint(2, "%s: %s port %d: sts %s %#x ->",
			argv0, d->dir, p, stsstr(pp->sts), pp->sts);
		dprint(2, " %s %#x\n",stsstr(sts), sts);
	}
	pp->sts = sts;
	if(onhubs != nhubs)
		return -1;
	return 0;
}

static void
dump(void)
{
	Hub *h;
	int i;

	mustdump = 0;
	for(h = hubs; h != nil; h = h->next)
		for(i = 1; i <= h->nport; i++)
			fprint(2, "%s: hub %#p %s port %d: %U",
				argv0, h, h->dev->dir, i, h->port[i].dev);
	usbfsdirdump();

}

static void
work(void *a)
{
	Channel *portc;
	char *fn;
	Hub *h;
	int i;

	portc = a;
	threadsetname("work");
	hubs = nil;
	/*
	 * Receive requests for root hubs
	 */
	while((fn = recvp(portc)) != nil){
		dprint(2, "%s: %s starting\n", argv0, fn);
		h = newhub(fn, nil);
		if(h == nil)
			fprint(2, "%s: %s: newhub failed: %r\n", argv0, fn);
		free(fn);
	}
	/*
	 * Enumerate (and acknowledge after first enumeration).
	 * Do NOT perform enumeration concurrently for the same
	 * controller. new devices attached respond to a default
	 * address (0) after reset, thus enumeration has to work
	 * one device at a time at least before addresses have been
	 * assigned.
	 * Do not use hub interrupt endpoint because we
	 * have to poll the root hub(s) in any case.
	 */
	for(;;){
Again:
		for(h = hubs; h != nil; h = h->next)
			for(i = 1; i <= h->nport; i++)
				if(enumhub(h, i) < 0){
					/* changes in hub list; repeat */
					goto Again;
				}
		if(portc != nil){
			sendp(portc, nil);
			portc = nil;
		}
		sleep(pollms);
		if(mustdump)
			dump();
	}
}

static int
cfswalk(Usbfs*, Fid *, char *)
{
	werrstr(Enotfound);
	return -1;
}

static int
cfsopen(Usbfs*, Fid *, int)
{
	return 0;
}

static long
cfsread(Usbfs*, Fid *, void *, long , vlong )
{
	return 0;
}

static void
setdrvargs(char *name, char *args)
{
	Devtab *dt;
	extern Devtab devtab[];

	for(dt = devtab; dt->name != nil; dt++)
		if(strstr(dt->name, name) != nil)
			dt->args = estrdup(args);
}

static void
setdrvauto(char *name, int on)
{
	Devtab *dt;
	extern Devtab devtab[];

	for(dt = devtab; dt->name != nil; dt++)
		if(strstr(dt->name, name) != nil)
			dt->noauto = !on;
}

static long
cfswrite(Usbfs*, Fid *, void *data, long cnt, vlong )
{
	char *cmd, *arg;
	char buf[80];
	char *toks[4];

	if(cnt > sizeof(buf))
		cnt = sizeof(buf) - 1;
	strncpy(buf, data, cnt);
	buf[cnt] = 0;
	if(cnt > 0 && buf[cnt-1] == '\n')
		buf[cnt-1] = 0;
	if(strncmp(buf, "dump", 4) == 0){
		mustdump = 1;
		return cnt;
	}
	if(strncmp(buf, "reset", 5) == 0){
		werrstr("reset not implemented");
		return -1;
	}
	if(strncmp(buf, "exit", 4) == 0){
		threadexitsall(nil);
		return cnt;
	}
	if(tokenize(buf, toks, nelem(toks)) != 2){
		werrstr("usage: auto|debug|diskargs|fsdebug|kbargs|noauto n");
		return -1;
	}
	cmd = toks[0];
	arg = toks[1];
	if(strcmp(cmd, "auto") == 0)
		setdrvauto(arg, 1);
	else if(strcmp(cmd, "debug") == 0)
		usbdebug = atoi(arg);
	else if(strcmp(cmd, "diskargs") == 0)
		setdrvargs("disk", arg);
	else if(strcmp(cmd, "etherargs") == 0)
		setdrvargs("ether", arg);
	else if(strcmp(cmd, "fsdebug") == 0)
		usbfsdebug = atoi(arg);
	else if(strcmp(cmd, "kbargs") == 0)
		setdrvargs("kb", arg);
	else if(strcmp(cmd, "noauto") == 0)
		setdrvauto(arg, 0);
	else{
		werrstr("unknown ctl '%s'", buf);
		return -1;
	}
	fprint(2, "%s: debug %d fsdebug %d\n", argv0, usbdebug, usbfsdebug);
	return cnt;
}

static int
cfsstat(Usbfs* fs, Qid qid, Dir *d)
{
	d->qid = qid;
	d->qid.path |= fs->qid;
	d->qid.type = 0;
	d->qid.vers = 0;
	d->name = "usbdctl";
	d->length = 0;
	d->mode = 0664;
	return 0;
}

static Usbfs ctlfs =
{
	.walk = cfswalk,
	.open = cfsopen,
	.read = cfsread,
	.write = cfswrite,
	.stat = cfsstat
};

static void
getenvint(char *env, int *lp)
{
	char *s;

	s = getenv(env);
	if (s != nil)
		*lp = atoi(s);
	free(s);
}

static void
getenvdrvargs(char *env, char *argname)
{
	char *s;

	s = getenv(env);
	if(s != nil)
		setdrvargs(argname, s);
	free(s);
}

static void
args(void)
{
	getenvint("usbdebug",	&usbdebug);
	getenvint("usbfsdebug",	&usbfsdebug);
	getenvdrvargs("kbargs",    "kb");
	getenvdrvargs("diskargs",  "disk");
	getenvdrvargs("etherargs", "ether");
}

static void
usage(void)
{
	fprint(2, "usage: %s [-Dd] [-s srv] [-m mnt] [dev...]\n", argv0);
	threadexitsall("usage");
}

extern void usbfsexits(int);

void
threadmain(int argc, char **argv)
{
	int fd, i, nd;
	char *err, *mnt, *srv;
	Dir *d;

	srv = "usb";
	mnt = "/dev";
	ARGBEGIN{
	case 'D':
		usbfsdebug++;
		break;
	case 'd':
		usbdebug++;
		break;
	case 's':
		srv = EARGF(usage());
		break;
	case 'i':
		pollms = atoi(EARGF(usage()));
		break;
	case 'm':
		mnt = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND;
	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
		sysfatal("#u: %r");

	args();

	fmtinstall('U', Ufmt);
	quotefmtinstall();
	rfork(RFNOTEG);
	portc = chancreate(sizeof(char *), 0);
	if(portc == nil)
		sysfatal("chancreate");
	proccreate(work, portc, Stack);
	if(argc == 0){
		fd = open("/dev/usb", OREAD);
		if(fd < 0)
			sysfatal("/dev/usb: %r");
		nd = dirreadall(fd, &d);
		close(fd);
		if(nd < 2)
			sysfatal("/dev/usb: no hubs");
		for(i = 0; i < nd; i++)
			if(strcmp(d[i].name, "ctl") != 0)
				sendp(portc, smprint("/dev/usb/%s", d[i].name));
		free(d);
	}else
		for(i = 0; i < argc; i++)
			sendp(portc, strdup(argv[i]));
	sendp(portc, nil);
	err = recvp(portc);
	chanfree(portc);
	usbfsexits(0);
	usbfsinit(srv, mnt, &usbdirfs, MAFTER);
	snprint(ctlfs.name, sizeof(ctlfs.name), "usbdctl");
	usbfsadd(&ctlfs);
	threadexits(err);
}
