Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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);
}