/*
* USB device driver framework.
*
* This is in charge of providing access to actual HCIs
* and providing I/O to the various endpoints of devices.
* A separate user program (usbd) is in charge of
* enumerating the bus, setting up endpoints and
* starting devices (also user programs).
*
* The interface provided is a violation of the standard:
* you're welcome.
*
* The interface consists of a root directory with several files
* plus a directory (epN.M) with two files per endpoint.
* A device is represented by its first endpoint, which
* is a control endpoint automatically allocated for each device.
* Device control endpoints may be used to create new endpoints.
* Devices corresponding to hubs may also allocate new devices,
* perhaps also hubs. Initially, a hub device is allocated for
* each controller present, to represent its root hub. Those can
* never be removed.
*
* All endpoints refer to the first endpoint (epN.0) of the device,
* which keeps per-device information, and also to the HCI used
* to reach them. Although all endpoints cache that information.
*
* epN.M/data files permit I/O and are considered DMEXCL.
* epN.M/ctl files provide status info and accept control requests.
*
* Endpoints may be given file names to be listed also at #u,
* for those drivers that have nothing to do after configuring the
* device and its endpoints.
*
* Drivers for different controllers are kept at usb[oue]hci.c
* It's likely we could factor out much from controllers into
* a generic controller driver, the problem is that details
* regarding how to handle toggles, tokens, Tds, etc. will
* get in the way. Thus, code is probably easier the way it is.
*/

#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "io.h"
#include        "../port/error.h"
#include        "../port/usb.h"

typedef struct Hcitype Hcitype;

enum
{
       /* Qid numbers */
       Qdir = 0,               /* #u */
       Qusbdir,                        /* #u/usb */
       Qctl,                   /* #u/usb/ctl - control requests */

       Qep0dir,                        /* #u/usb/ep0.0 - endpoint 0 dir */
       Qep0io,                 /* #u/usb/ep0.0/data - endpoint 0 I/O */
       Qep0ctl,                /* #u/usb/ep0.0/ctl - endpoint 0 ctl. */
       Qep0dummy,              /* give 4 qids to each endpoint */

       Qepdir = 0,             /* (qid-qep0dir)&3 is one of these */
       Qepio,                  /* to identify which file for the endpoint */
       Qepctl,

       /* ... */

       /* Usb ctls. */
       CMdebug = 0,            /* debug on|off */
       CMdump,                 /* dump (data structures for debug) */

       /* Ep. ctls */
       CMnew = 0,              /* new nb ctl|bulk|intr|iso r|w|rw (endpoint) */
       CMnewdev,               /* newdev full|low|high|super portnb (allocate new devices) */
       CMhub,                  /* hub (set the device as a hub) */
       CMspeed,                /* speed full|low|high|no */
       CMmaxpkt,               /* maxpkt size */
       CMntds,                 /* ntds nb (max nb. of tds per µframe) */
       CMclrhalt,              /* clrhalt (halt was cleared on endpoint) */
       CMpollival,             /* pollival interval (interrupt/iso) */
       CMhz,                   /* hz n (samples/sec; iso) */
       CMsamplesz,             /* samplesz n (sample size; iso) */
       CMinfo,                 /* info infostr (ke.ep info for humans) */
       CMdetach,               /* detach (abort I/O forever on this ep). */
       CMaddress,              /* address (address is assigned) */
       CMdebugep,              /* debug n (set/clear debug for this ep) */
       CMname,                 /* name str (show up as #u/name as well) */
       CMtmout,                /* timeout n (activate timeouts for ep) */
       CMsampledelay,          /* maximum delay introduced by buffering (iso) */
       CMpreset,               /* reset the port */
       CMuframes,              /* set uframe mode (iso) */

       /* Hub feature selectors */
       Rportenable     = 1,
       Rportreset      = 4,

};

struct Hcitype
{
       char*   type;
       int     (*reset)(Hci*);
};

#define QID(q)  ((int)(q).path)

static char Edetach[] = "device is detached";
static char Enotconf[] = "endpoint not configured";
char Estalled[] = "endpoint stalled";

static Cmdtab usbctls[] =
{
       {CMdebug,       "debug",        2},
       {CMdump,        "dump",         1},
};

static Cmdtab epctls[] =
{
       {CMnew,         "new",          4},
       {CMnewdev,      "newdev",       3},
       {CMhub,         "hub",          1},
       {CMspeed,       "speed",        2},
       {CMmaxpkt,      "maxpkt",       2},
       {CMntds,        "ntds",         2},
       {CMpollival,    "pollival",     2},
       {CMsamplesz,    "samplesz",     2},
       {CMhz,          "hz",           2},
       {CMinfo,        "info",         0},
       {CMdetach,      "detach",       1},
       {CMaddress,     "address",      1},
       {CMdebugep,     "debug",        2},
       {CMclrhalt,     "clrhalt",      1},
       {CMname,        "name",         2},
       {CMtmout,       "timeout",      2},
       {CMsampledelay, "sampledelay",  2},
       {CMpreset,      "reset",        1},
       {CMuframes,     "uframes",      2},
};

static Dirtab usbdir[] =
{
       "ctl",          {Qctl},         0,      0666,
};

char *usbmodename[] =
{
       [OREAD] "r",
       [OWRITE]        "w",
       [ORDWR] "rw",
};

static char *ttname[] =
{
       [Tnone] "none",
       [Tctl]  "control",
       [Tiso]  "iso",
       [Tintr] "interrupt",
       [Tbulk] "bulk",
};

static char *spname[] =
{
       [Superspeed]    "super",
       [Fullspeed]     "full",
       [Lowspeed]      "low",
       [Highspeed]     "high",
       [Nospeed]       "no",
};

static int      debug;
static Hcitype  hcitypes[Nhcis];
static Hci*     hcis[Nhcis];
static QLock    epslck;         /* add, del, lookup endpoints */
static Ep*      eps[Neps];      /* all endpoints known */
static int      epmax;          /* 1 + last endpoint index used  */
static int      usbidgen;       /* device address generator */

/*
* Is there something like this in a library? should it be?
*/
char*
seprintdata(char *s, char *se, uchar *d, int n)
{
       int i, l;

       s = seprint(s, se, " %#p[%d]: ", d, n);
       l = n;
       if(l > 10)
               l = 10;
       for(i=0; i<l; i++)
               s = seprint(s, se, " %2.2ux", d[i]);
       if(l < n)
               s = seprint(s, se, "...");
       return s;
}

static int
name2speed(char *name)
{
       int i;

       for(i = 0; i < nelem(spname); i++)
               if(strcmp(name, spname[i]) == 0)
                       return i;
       return Nospeed;
}

static int
name2ttype(char *name)
{
       int i;

       for(i = 0; i < nelem(ttname); i++)
               if(strcmp(name, ttname[i]) == 0)
                       return i;
       /* may be a std. USB ep. type */
       i = strtol(name, nil, 0);
       switch(i+1){
       case Tctl:
       case Tiso:
       case Tbulk:
       case Tintr:
               return i+1;
       default:
               return Tnone;
       }
}

static int
name2mode(char *mode)
{
       int i;

       for(i = 0; i < nelem(usbmodename); i++)
               if(strcmp(mode, usbmodename[i]) == 0)
                       return i;
       return -1;
}

static int
qid2epidx(int q)
{
       q = (q-Qep0dir)/4;
       if(q < 0 || q >= epmax || eps[q] == nil)
               return -1;
       return q;
}

static int
isqtype(int q, int type)
{
       if(q < Qep0dir)
               return 0;
       q -= Qep0dir;
       return (q & 3) == type;
}

void
addhcitype(char* t, int (*r)(Hci*))
{
       static int ntype;

       if(ntype == Nhcis)
               panic("too many USB host interface types");
       hcitypes[ntype].type = t;
       hcitypes[ntype].reset = r;
       ntype++;
}

static char*
seprintep(char *s, char *se, Ep *ep, int all)
{
       static char* dsnames[] = { "config", "enabled", "detached", "reset" };
       Udev *d;
       int i;
       int di;

       d = ep->dev;

       qlock(ep);
       if(waserror()){
               qunlock(ep);
               nexterror();
       }
       di = ep->dev->nb;
       if(all)
               s = seprint(s, se, "dev %d ep %d ", di, ep->nb);
       s = seprint(s, se, "%s", dsnames[ep->dev->state]);
       s = seprint(s, se, " %s", ttname[ep->ttype]);
       assert(ep->mode == OREAD || ep->mode == OWRITE || ep->mode == ORDWR);
       s = seprint(s, se, " %s", usbmodename[ep->mode]);
       s = seprint(s, se, " speed %s", spname[d->speed]);
       s = seprint(s, se, " maxpkt %ld", ep->maxpkt);
       s = seprint(s, se, " ntds %d", ep->ntds);
       s = seprint(s, se, " pollival %ld", ep->pollival);
       s = seprint(s, se, " samplesz %ld", ep->samplesz);
       s = seprint(s, se, " hz %ld", ep->hz);
       s = seprint(s, se, " uframes %d", ep->uframes);
       s = seprint(s, se, " hub %d", ep->dev->hub);
       s = seprint(s, se, " port %d", ep->dev->port);
       s = seprint(s, se, " rootport %d", ep->dev->rootport);
       s = seprint(s, se, " addr %d", ep->dev->addr);
       if(ep->inuse)
               s = seprint(s, se, " busy");
       else
               s = seprint(s, se, " idle");
       if(all){
               s = seprint(s, se, " load %uld", ep->load);
               s = seprint(s, se, " ref %ld addr %#p", ep->ref, ep);
               s = seprint(s, se, " idx %d", ep->idx);
               if(ep->name != nil)
                       s = seprint(s, se, " name '%s'", ep->name);
               if(ep->tmout != 0)
                       s = seprint(s, se, " tmout");
               if(ep == ep->ep0){
                       s = seprint(s, se, " ctlrno %#x", ep->hp->ctlrno);
                       s = seprint(s, se, " eps:");
                       for(i = 0; i < nelem(d->eps); i++)
                               if(d->eps[i] != nil)
                                       s = seprint(s, se, " ep%d.%d", di, i);
               }
       }
       if(ep->info != nil)
               s = seprint(s, se, "\n%s %s\n", ep->info, ep->hp->type);
       else
               s = seprint(s, se, "\n");
       qunlock(ep);
       poperror();
       return s;
}

static Ep*
epalloc(Hci *hp)
{
       Ep *ep;
       int i;

       ep = smalloc(sizeof(Ep));
       ep->ref = 1;
       qlock(&epslck);
       for(i = 0; i < Neps; i++)
               if(eps[i] == nil)
                       break;
       if(i == Neps){
               qunlock(&epslck);
               free(ep);
               print("usb: bug: too few endpoints.\n");
               return nil;
       }
       ep->idx = i;
       if(epmax <= i)
               epmax = i+1;
       eps[i] = ep;
       ep->hp = hp;
       ep->maxpkt = 8;
       ep->ntds = 1;
       ep->uframes = ep->samplesz = ep->pollival = ep->hz = 0; /* make them void */
       qunlock(&epslck);
       return ep;
}

static Ep*
getep(int i)
{
       Ep *ep;

       if(i < 0 || i >= epmax || eps[i] == nil)
               return nil;
       qlock(&epslck);
       ep = eps[i];
       if(ep != nil)
               incref(ep);
       qunlock(&epslck);
       return ep;
}

static void
putep(Ep *ep)
{
       Udev *d;

       if(ep == nil || decref(ep) > 0)
               return;
       d = ep->dev;
       deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
       qlock(&epslck);
       eps[ep->idx] = nil;
       if(ep->idx == epmax-1)
               epmax--;
       if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
               usbidgen--;
       qunlock(&epslck);
       if(d != nil){
               qlock(ep->ep0);
               d->eps[ep->nb] = nil;
               qunlock(ep->ep0);
       }
       if(ep->ep0 != ep){
               putep(ep->ep0);
               ep->ep0 = nil;
       } else if(d != nil){
               if(d->free != nil)
                       (*d->free)(d->aux);
               free(d);
       }
       free(ep->info);
       free(ep->name);
       free(ep);
}

static void
dumpeps(void)
{
       int i;
       static char buf[512];
       char *s;
       char *e;
       Ep *ep;

       print("usb dump eps: epmax %d Neps %d (ref=1+ for dump):\n", epmax, Neps);
       for(i = 0; i < epmax; i++){
               s = buf;
               e = buf+sizeof(buf);
               ep = getep(i);
               if(ep != nil){
                       if(waserror()){
                               putep(ep);
                               nexterror();
                       }
                       s = seprint(s, e, "ep%d.%d ", ep->dev->nb, ep->nb);
                       seprintep(s, e, ep, 1);
                       print("%s", buf);
                       if(ep->hp->seprintep != nil){
                               ep->hp->seprintep(buf, e, ep);
                               print("%s", buf);
                       }
                       poperror();
                       putep(ep);
               }
       }
       print("usb dump hcis:\n");
       for(i = 0; i < Nhcis; i++)
               if(hcis[i] != nil && hcis[i]->dump != nil)
                       hcis[i]->dump(hcis[i]);
}

static int
newusbid(Hci *)
{
       int id;

       qlock(&epslck);
       id = ++usbidgen;
       if(id >= 0x7F)
               print("#u: too many device addresses; reuse them more\n");
       qunlock(&epslck);
       return id;
}

/*
* Create endpoint 0 for a new device
*/
static Ep*
newdev(Hci *hp, int ishub, int isroot)
{
       Ep *ep;
       Udev *d;

       ep = epalloc(hp);
       d = ep->dev = smalloc(sizeof(Udev));
       d->nb = newusbid(hp);
       d->addr = 0;
       d->eps[0] = ep;
       ep->nb = 0;
       ep->toggle[0] = ep->toggle[1] = 0;
       d->ishub = ishub;
       d->isroot = isroot;
       d->rootport = 0;
       d->routestr = 0;
       d->depth = -1;
       d->speed = Fullspeed;
       d->state = Dconfig;             /* address not yet set */
       ep->dev = d;
       ep->ep0 = ep;                   /* no ref counted here */
       ep->ttype = Tctl;
       ep->tmout = Xfertmout;
       ep->mode = ORDWR;
       dprint("newdev %#p ep%d.%d %#p\n", d, d->nb, ep->nb, ep);
       return ep;
}

/*
* Create a new endpoint for the device
* accessed via the given endpoint 0.
*/
static Ep*
newdevep(Ep *ep, int i, int tt, int mode)
{
       Ep *nep;
       Udev *d;

       d = ep->dev;
       if(d->eps[i] != nil)
               error("endpoint already in use");
       nep = epalloc(ep->hp);
       incref(ep);
       d->eps[i] = nep;
       nep->nb = i;
       nep->toggle[0] = nep->toggle[1] = 0;
       nep->ep0 = ep;
       nep->dev = ep->dev;
       nep->mode = mode;
       nep->ttype = tt;
       nep->debug = ep->debug;
       /* set defaults */
       switch(tt){
       case Tctl:
               nep->tmout = Xfertmout;
               break;
       case Tintr:
               nep->pollival = 10;
               break;
       case Tiso:
               nep->tmout = Xfertmout;
               nep->pollival = 10;
               nep->samplesz = 4;
               nep->hz = 44100;
               nep->uframes = 0;
               break;
       }
       deprint("newdevep ep%d.%d %#p\n", d->nb, nep->nb, nep);
       return ep;
}

static int
epdataperm(int mode)
{

       switch(mode){
       case OREAD:
               return 0440|DMEXCL;
               break;
       case OWRITE:
               return 0220|DMEXCL;
               break;
       default:
               return 0660|DMEXCL;
       }
}

static int
usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
{
       Qid q;
       Dirtab *dir;
       int perm;
       char *se;
       Ep *ep;
       int nb;
       int mode;

       if(0)ddprint("usbgen q %#x s %d...", QID(c->qid), s);
       if(s == DEVDOTDOT){
               if(QID(c->qid) <= Qusbdir){
                       mkqid(&q, Qdir, 0, QTDIR);
                       devdir(c, q, "#u", 0, eve, 0555, dp);
               }else{
                       mkqid(&q, Qusbdir, 0, QTDIR);
                       devdir(c, q, "usb", 0, eve, 0555, dp);
               }
               if(0)ddprint("ok\n");
               return 1;
       }

       switch(QID(c->qid)){
       case Qdir:                              /* list #u */
               if(s == 0){
                       mkqid(&q, Qusbdir, 0, QTDIR);
                       devdir(c, q, "usb", 0, eve, 0555, dp);
                       if(0)ddprint("ok\n");
                       return 1;
               }
               s--;
               if(s < 0 || s >= epmax)
                       goto Fail;
               ep = getep(s);
               if(ep == nil || ep->name == nil){
                       if(ep != nil)
                               putep(ep);
                       if(0)ddprint("skip\n");
                       return 0;
               }
               if(waserror()){
                       putep(ep);
                       nexterror();
               }
               mkqid(&q, Qep0io+s*4, 0, QTFILE);
               devdir(c, q, ep->name, 0, eve, epdataperm(ep->mode), dp);
               putep(ep);
               poperror();
               if(0)ddprint("ok\n");
               return 1;

       case Qusbdir:                           /* list #u/usb */
       Usbdir:
               if(s < nelem(usbdir)){
                       dir = &usbdir[s];
                       mkqid(&q, dir->qid.path, 0, QTFILE);
                       devdir(c, q, dir->name, dir->length, eve, dir->perm, dp);
                       if(0)ddprint("ok\n");
                       return 1;
               }
               s -= nelem(usbdir);
               if(s < 0 || s >= epmax)
                       goto Fail;
               ep = getep(s);
               if(ep == nil){
                       if(0)ddprint("skip\n");
                       return 0;
               }
               if(waserror()){
                       putep(ep);
                       nexterror();
               }
               se = up->genbuf+sizeof(up->genbuf);
               seprint(up->genbuf, se, "ep%d.%d", ep->dev->nb, ep->nb);
               mkqid(&q, Qep0dir+4*s, 0, QTDIR);
               putep(ep);
               poperror();
               devdir(c, q, up->genbuf, 0, eve, 0775, dp);
               if(0)ddprint("ok\n");
               return 1;

       case Qctl:
               s = 0;
               goto Usbdir;

       default:                                /* list #u/usb/epN.M */
               nb = qid2epidx(QID(c->qid));
               ep = getep(nb);
               if(ep == nil)
                       goto Fail;
               mode = ep->mode;
               putep(ep);
               if(isqtype(QID(c->qid), Qepdir)){
               Epdir:
                       switch(s){
                       case 0:
                               mkqid(&q, Qep0io+nb*4, 0, QTFILE);
                               perm = epdataperm(mode);
                               devdir(c, q, "data", 0, eve, perm, dp);
                               break;
                       case 1:
                               mkqid(&q, Qep0ctl+nb*4, 0, QTFILE);
                               devdir(c, q, "ctl", 0, eve, 0664, dp);
                               break;
                       default:
                               goto Fail;
                       }
               }else if(isqtype(QID(c->qid), Qepctl)){
                       s = 1;
                       goto Epdir;
               }else{
                       s = 0;
                       goto Epdir;
               }
               if(0)ddprint("ok\n");
               return 1;
       }
Fail:
       if(0)ddprint("fail\n");
       return -1;
}

static Hci*
hciprobe(int cardno, int ctlrno)
{
       Hci *hp;
       char *type;
       static int epnb = 1;    /* guess the endpoint nb. for the controller */

       ddprint("hciprobe %d %d\n", cardno, ctlrno);
       hp = smalloc(sizeof(Hci));
       hp->ctlrno = ctlrno;
       hp->tbdf = BUSUNKNOWN;

       if(cardno < 0){
               if(isaconfig("usb", ctlrno, hp) == 0){
                       free(hp);
                       return nil;
               }
               for(cardno = 0; cardno < Nhcis; cardno++){
                       if(hcitypes[cardno].type == nil)
                               break;
                       type = hp->type;
                       if(type==nil || *type==0)
                               type = "uhci";
                       if(cistrcmp(hcitypes[cardno].type, type) == 0)
                               break;
               }
       }

       if(cardno >= Nhcis || hcitypes[cardno].type == nil){
               free(hp);
               return nil;
       }
       dprint("%s...", hcitypes[cardno].type);
       if(hcitypes[cardno].reset(hp) < 0){
               free(hp);
               return nil;
       }

       /*
        * modern machines have too many usb controllers to list on
        * the console.
        */
       dprint("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n",
               epnb, hcitypes[cardno].type, hp->port, hp->irq);
       epnb++;
       return hp;
}

static void
usbreset(void)
{
       int cardno, ctlrno;
       Hci *hp;

       if(getconf("*nousbprobe"))
               return;
       dprint("usbreset\n");

       for(ctlrno = 0; ctlrno < Nhcis; ctlrno++)
               if((hp = hciprobe(-1, ctlrno)) != nil)
                       hcis[ctlrno] = hp;
       cardno = ctlrno = 0;
       while(cardno < Nhcis && ctlrno < Nhcis && hcitypes[cardno].type != nil)
               if(hcis[ctlrno] != nil)
                       ctlrno++;
               else{
                       hp = hciprobe(cardno, ctlrno);
                       if(hp == nil)
                               cardno++;
                       hcis[ctlrno++] = hp;
               }
       if(hcis[Nhcis-1] != nil)
               print("usbreset: bug: Nhcis (%d) too small\n", Nhcis);
}

static int
numbits(uint n)
{
       int c = 0;
       while(n != 0){
               c++;
               n = (n-1) & n;
       }
       return c;
}

static void
usbinit(void)
{
       Hci *hp;
       int ctlrno;
       Ep *d;
       char info[40];

       dprint("usbinit\n");
       for(ctlrno = 0; ctlrno < Nhcis; ctlrno++){
               hp = hcis[ctlrno];
               if(hp != nil){
                       int n;

                       if(hp->init != nil){
                               if(waserror()){
                                       print("usbinit: %s: %s\n", hp->type, up->errstr);
                                       continue;
                               }
                               hp->init(hp);
                               poperror();
                       }

                       hp->superspeed &= (1<<hp->nports)-1;
                       n = hp->nports - numbits(hp->superspeed);
                       if(n > 0){
                               d = newdev(hp, 1, 1);           /* new LS/FS/HS root hub */
                               d->maxpkt = 64;
                               if(hp->highspeed != 0)
                                       d->dev->speed = Highspeed;
                               d->dev->state = Denabled;       /* although addr == 0 */
                               snprint(info, sizeof(info), "roothub ports %d", n);
                               kstrdup(&d->info, info);
                       }
                       n = numbits(hp->superspeed);
                       if(n > 0){
                               d = newdev(hp, 1, 1);           /* new SS root hub */
                               d->maxpkt = 512;
                               d->dev->speed = Superspeed;
                               d->dev->state = Denabled;       /* although addr == 0 */
                               snprint(info, sizeof(info), "roothub ports %d", n);
                               kstrdup(&d->info, info);
                       }
               }
       }
}

static Chan*
usbattach(char *spec)
{
       return devattach(L'u', spec);
}

static Walkqid*
usbwalk(Chan *c, Chan *nc, char **name, int nname)
{
       return devwalk(c, nc, name, nname, nil, 0, usbgen);
}

static int
usbstat(Chan *c, uchar *db, int n)
{
       return devstat(c, db, n, nil, 0, usbgen);
}

/*
* µs for the given transfer, for bandwidth allocation.
* This is a very rough worst case for what 5.11.3
* of the usb 2.0 spec says.
* Also, we are using maxpkt and not actual transfer sizes.
* Only when we are sure we
* are not exceeding b/w might we consider adjusting it.
*/
static ulong
usbload(int speed, int maxpkt)
{
       enum{ Hostns = 1000, Hubns = 333 };
       ulong l;
       ulong bs;

       l = 0;
       bs = 10UL * maxpkt;
       switch(speed){
       case Highspeed:
               l = 55*8*2 + 2 * (3 + bs) + Hostns;
               break;
       case Fullspeed:
               l = 9107 + 84 * (4 + bs) + Hostns;
               break;
       case Lowspeed:
               l = 64107 + 2 * Hubns + 667 * (3 + bs) + Hostns;
               break;
       default:
               print("usbload: bad speed %d\n", speed);
               /* let it run */
       }
       return l / 1000UL;      /* in µs */
}

static Chan*
usbopen(Chan *c, int omode)
{
       int q;
       Ep *ep;
       int mode;

       mode = openmode(omode);
       q = QID(c->qid);

       if(q >= Qep0dir && qid2epidx(q) < 0)
               error(Eio);
       if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
               return devopen(c, omode, nil, 0, usbgen);

       ep = getep(qid2epidx(q));
       if(ep == nil)
               error(Eio);
       deprint("usbopen q %#x fid %d omode %d\n", q, c->fid, mode);
       if(waserror()){
               putep(ep);
               nexterror();
       }
       qlock(ep);
       if(ep->inuse){
               qunlock(ep);
               error(Einuse);
       }
       ep->inuse = 1;
       qunlock(ep);
       if(waserror()){
               ep->inuse = 0;
               nexterror();
       }
       if(mode != OREAD && ep->mode == OREAD)
               error(Eperm);
       if(mode != OWRITE && ep->mode == OWRITE)
               error(Eperm);
       if(ep->ttype == Tnone)
               error(Enotconf);
       ep->clrhalt = 0;
       ep->rhrepl = -1;
       if(ep->load == 0 && ep->dev->speed != Superspeed)
               ep->load = usbload(ep->dev->speed, ep->maxpkt);
       ep->hp->epopen(ep);

       poperror();     /* ep->inuse */
       poperror();     /* don't putep(): ref kept for fid using the ep. */

       c->mode = mode;
       c->flag |= COPEN;
       c->offset = 0;
       c->aux = nil;   /* paranoia */
       return c;
}

static void
epclose(Ep *ep)
{
       qlock(ep);
       if(waserror()){
               qunlock(ep);
               nexterror();
       }
       if(ep->inuse){
               ep->hp->epclose(ep);
               ep->inuse = 0;
       }
       qunlock(ep);
       poperror();
}

static void
usbclose(Chan *c)
{
       int q;
       Ep *ep;

       q = QID(c->qid);
       if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
               return;

       ep = getep(qid2epidx(q));
       if(ep == nil)
               return;
       deprint("usbclose q %#x fid %d ref %ld\n", q, c->fid, ep->ref);
       if(waserror()){
               putep(ep);
               nexterror();
       }
       if(c->flag & COPEN){
               free(c->aux);
               c->aux = nil;
               epclose(ep);
               putep(ep);      /* release ref kept since usbopen */
               c->flag &= ~COPEN;
       }
       poperror();
       putep(ep);
}

static long
ctlread(Chan *c, void *a, long n, vlong offset)
{
       int q;
       char *s;
       char *us;
       char *se;
       Ep *ep;
       int i;

       q = QID(c->qid);
       us = s = smalloc(READSTR);
       se = s + READSTR;
       if(waserror()){
               free(us);
               nexterror();
       }
       if(q == Qctl)
               for(i = 0; i < epmax; i++){
                       ep = getep(i);
                       if(ep != nil){
                               if(waserror()){
                                       putep(ep);
                                       nexterror();
                               }
                               s = seprint(s, se, "ep%d.%d ", ep->dev->nb, ep->nb);
                               s = seprintep(s, se, ep, 0);
                               poperror();
                       }
                       putep(ep);
               }
       else{
               ep = getep(qid2epidx(q));
               if(ep == nil)
                       error(Eio);
               if(waserror()){
                       putep(ep);
                       nexterror();
               }
               if(c->aux != nil){
                       /* After a new endpoint request we read
                        * the new endpoint name back.
                        */
                       strecpy(s, se, c->aux);
                       free(c->aux);
                       c->aux = nil;
               }else
                       seprintep(s, se, ep, 0);
               poperror();
               putep(ep);
       }
       n = readstr(offset, a, n, us);
       poperror();
       free(us);
       return n;
}

/*
* Fake root hub emulation.
*/
static long
rhubread(Ep *ep, void *a, long n)
{
       uchar b[8];

       if(ep->dev->isroot == 0 || ep->nb != 0 || n < 2 || ep->rhrepl == -1)
               return -1;

       b[0] = ep->rhrepl;
       b[1] = ep->rhrepl>>8;
       b[2] = ep->rhrepl>>16;
       b[3] = ep->rhrepl>>24;
       b[4] = ep->rhrepl>>32;
       b[5] = ep->rhrepl>>40;
       b[6] = ep->rhrepl>>48;
       b[7] = ep->rhrepl>>56;

       ep->rhrepl = -1;

       if(n > sizeof(b))
               n = sizeof(b);
       memmove(a, b, n);

       return n;
}

static int
rootport(Ep *ep, int port)
{
       Hci *hp;
       Udev *hub;
       uint mask;
       int rootport;

       hp = ep->hp;
       hub = ep->dev;
       if(!hub->isroot)
               return hub->rootport;

       mask = hp->superspeed;
       if(hub->speed != Superspeed)
               mask = (1<<hp->nports)-1 & ~mask;

       for(rootport = 1; mask != 0; rootport++){
               if(mask & 1){
                       if(--port == 0)
                               return rootport;
               }
               mask >>= 1;
       }

       return 0;
}

static long
rhubwrite(Ep *ep, void *a, long n)
{
       uchar *s;
       int cmd;
       int feature;
       int port;
       Hci *hp;

       if(ep->dev == nil || ep->dev->isroot == 0 || ep->nb != 0)
               return -1;
       if(n != Rsetuplen)
               error("root hub is a toy hub");
       ep->rhrepl = -1;
       s = a;
       if(s[Rtype] != (Rh2d|Rclass|Rother) && s[Rtype] != (Rd2h|Rclass|Rother))
               error("root hub is a toy hub");
       hp = ep->hp;
       cmd = s[Rreq];
       feature = GET2(s+Rvalue);
       port = rootport(ep, GET2(s+Rindex));
       if(port == 0)
               error("bad hub port number");
       switch(feature){
       case Rportenable:
               ep->rhrepl = hp->portenable(hp, port, cmd == Rsetfeature);
               break;
       case Rportreset:
               ep->rhrepl = hp->portreset(hp, port, cmd == Rsetfeature);
               break;
       case Rgetstatus:
               ep->rhrepl = hp->portstatus(hp, port);
               break;
       default:
               ep->rhrepl = 0;
       }
       return n;
}

static long
usbread(Chan *c, void *a, long n, vlong offset)
{
       int q;
       Ep *ep;
       int nr;

       q = QID(c->qid);

       if(c->qid.type == QTDIR)
               return devdirread(c, a, n, nil, 0, usbgen);

       if(q == Qctl || isqtype(q, Qepctl))
               return ctlread(c, a, n, offset);

       ep = getep(qid2epidx(q));
       if(ep == nil)
               error(Eio);
       if(waserror()){
               putep(ep);
               nexterror();
       }
       if(ep->dev->state == Ddetach)
               error(Edetach);
       if(ep->mode == OWRITE || ep->inuse == 0)
               error(Ebadusefd);
       switch(ep->ttype){
       case Tnone:
               error(Enotconf);
       case Tctl:
               nr = rhubread(ep, a, n);
               if(nr >= 0){
                       n = nr;
                       break;
               }
               /* else fall */
       default:
               ddeprint("\nusbread q %#x fid %d cnt %ld off %lld\n",q,c->fid,n,offset);
               n = ep->hp->epread(ep, a, n);
               break;
       }
       poperror();
       putep(ep);
       return n;
}

static void
setmaxpkt(Ep *ep, char* s)
{
       long spp, max;  /* samples per packet */

       if(ep->dev->speed == Fullspeed)
               spp = (ep->hz * ep->pollival + 999) / 1000;
       else
               spp = (ep->hz * ep->pollival * ep->ntds + 7999) / 8000;
       ep->maxpkt = spp * ep->samplesz;
       deprint("usb: %s: setmaxpkt: hz %ld poll %ld"
               " ntds %d %s speed -> spp %ld maxpkt %ld\n", s,
               ep->hz, ep->pollival, ep->ntds, spname[ep->dev->speed],
               spp, ep->maxpkt);
       switch(ep->dev->speed){
       case Fullspeed:
               max = 1024;
               break;
       case Highspeed:
               max = 3*1024;
               break;
       case Superspeed:
               max = 48*1024;
               break;
       default:
               return;
       }
       if(ep->maxpkt*ep->ntds > max){
               print("usb: %s: maxpkt %ld > %ld for %s, truncating\n",
                       s, ep->maxpkt*ep->ntds, max, spname[ep->dev->speed]);
               ep->maxpkt = max/ep->ntds;
       }
}

/*
* Many endpoint ctls. simply update the portable representation
* of the endpoint. The actual controller driver will look
* at them to setup the endpoints as dictated.
*/
static long
epctl(Ep *ep, Chan *c, void *a, long n)
{
       int i, l, mode, nb, tt;
       char *b, *s;
       Cmdbuf *cb;
       Cmdtab *ct;
       Ep *nep;
       Udev *d;
       static char *Info = "info ";

       d = ep->dev;

       cb = parsecmd(a, n);
       if(waserror()){
               free(cb);
               nexterror();
       }
       ct = lookupcmd(cb, epctls, nelem(epctls));
       i = ct->index;
       if(i == CMnew || i == CMspeed || i == CMhub || i == CMpreset)
               if(ep != ep->ep0)
                       error("allowed only on a setup endpoint");
       if(i != CMclrhalt && i != CMdetach && i != CMdebugep && i != CMname)
               if(ep != ep->ep0 && ep->inuse != 0)
                       error("must configure before using");
       switch(i){
       case CMnew:
               deprint("usb epctl %s\n", cb->f[0]);
               nb = strtol(cb->f[1], nil, 0);
               if(nb < 0 || nb >= Ndeveps)
                       error("bad endpoint number");
               tt = name2ttype(cb->f[2]);
               if(tt == Tnone)
                       error("unknown endpoint type");
               mode = name2mode(cb->f[3]);
               if(mode < 0)
                       error("unknown i/o mode");
               newdevep(ep, nb, tt, mode);
               break;
       case CMnewdev:
               deprint("usb epctl %s\n", cb->f[0]);
               if(ep != ep->ep0 || d->ishub == 0)
                       error("not a hub setup endpoint");
               l = name2speed(cb->f[1]);
               if(l == Nospeed)
                       error("speed must be full|low|high|super");
               if(l != d->speed && (l == Superspeed || d->speed == Superspeed))
                       error("wrong speed for superspeed hub/device");
               nep = newdev(ep->hp, 0, 0);
               nep->dev->speed = l;
               if(l == Superspeed)
                       nep->maxpkt = 512;
               else if(l != Lowspeed)
                       nep->maxpkt = 64;       /* assume full speed */
               nep->dev->hub = d->addr;
               nep->dev->port = atoi(cb->f[2]);
               nep->dev->depth = d->depth+1;
               nep->dev->rootport = rootport(ep, nep->dev->port);
               nep->dev->routestr = d->routestr | (((nep->dev->port&15) << 4*nep->dev->depth) >> 4);
               /* next read request will read
                * the name for the new endpoint
                */
               l = sizeof(up->genbuf);
               snprint(up->genbuf, l, "ep%d.%d", nep->dev->nb, nep->nb);
               kstrdup(&c->aux, up->genbuf);
               break;
       case CMhub:
               deprint("usb epctl %s\n", cb->f[0]);
               d->ishub = 1;
               break;
       case CMspeed:
               l = name2speed(cb->f[1]);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(l == Nospeed)
                       error("speed must be full|low|high|super");
               if(l != d->speed && (l == Superspeed || d->speed == Superspeed))
                       error("cannot change speed on superspeed device");
               qlock(ep->ep0);
               d->speed = l;
               qunlock(ep->ep0);
               break;
       case CMmaxpkt:
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(l < 1 || l > 1024)
                       error("maxpkt not in [1:1024]");
               qlock(ep);
               ep->maxpkt = l;
               qunlock(ep);
               break;
       case CMntds:
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(l < 1 || l > 3)
                       error("ntds not in [1:3]");
               qlock(ep);
               ep->ntds = l;
               qunlock(ep);
               break;
       case CMpollival:
               if(ep->ttype != Tintr && ep->ttype != Tiso)
                       error("not an intr or iso endpoint");
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(ep->dev->speed == Fullspeed || ep->dev->speed == Lowspeed){
                       if(l < 1 || l > 255)
                               error("pollival not in [1:255]");
               } else {
                       if(l < 1 || l > 16)
                               error("pollival power not in [1:16]");
                       l = 1 << l-1;
               }
               qlock(ep);
               ep->pollival = l;
               if(ep->ttype == Tiso)
                       setmaxpkt(ep, "pollival");
               qunlock(ep);
               break;
       case CMsamplesz:
               if(ep->ttype != Tiso)
                       error("not an iso endpoint");
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(l <= 0 || l > 8)
                       error("samplesz not in [1:8]");
               qlock(ep);
               ep->samplesz = l;
               setmaxpkt(ep, "samplesz");
               qunlock(ep);
               break;
       case CMhz:
               if(ep->ttype != Tiso)
                       error("not an iso endpoint");
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb epctl %s %d\n", cb->f[0], l);
               if(l <= 0 || l > 100000)
                       error("hz not in [1:100000]");
               qlock(ep);
               ep->hz = l;
               setmaxpkt(ep, "hz");
               qunlock(ep);
               break;
       case CMuframes:
               if(ep->ttype != Tiso)
                       error("not an iso endpoint");
               l = strtoul(cb->f[1], nil, 0);
               deprint("usb uframes %s %d\n", cb->f[0], l);
               if(l != 0 && l != 1)
                       error("uframes not in [0:1]");
               qlock(ep);
               ep->uframes = l;
               qunlock(ep);
               break;
       case CMclrhalt:
               qlock(ep);
               deprint("usb epctl %s\n", cb->f[0]);
               ep->clrhalt = 1;
               qunlock(ep);
               break;
       case CMinfo:
               deprint("usb epctl %s\n", cb->f[0]);
               l = strlen(Info);
               s = a;
               if(n < l+2 || strncmp(Info, s, l) != 0)
                       error(Ebadctl);
               if(n > 1024)
                       n = 1024;
               b = smalloc(n);
               memmove(b, s+l, n-l);
               b[n-l] = 0;
               if(b[n-l-1] == '\n')
                       b[n-l-1] = 0;
               qlock(ep);
               free(ep->info);
               ep->info = b;
               qunlock(ep);
               break;
       case CMaddress:
               deprint("usb epctl %s\n", cb->f[0]);
               if(ep->dev->addr == 0)
                       ep->dev->addr = ep->dev->nb;
               ep->dev->state = Denabled;
               break;
       case CMdetach:
               if(ep->dev->isroot != 0)
                       error("can't detach a root hub");
               deprint("usb epctl %s ep%d.%d\n",
                       cb->f[0], ep->dev->nb, ep->nb);
               ep->dev->state = Ddetach;
               /* Release file system ref. for its endpoints */
               for(i = 0; i < nelem(ep->dev->eps); i++)
                       putep(ep->dev->eps[i]);
               break;
       case CMdebugep:
               if(strcmp(cb->f[1], "on") == 0)
                       ep->debug = 1;
               else if(strcmp(cb->f[1], "off") == 0)
                       ep->debug = 0;
               else
                       ep->debug = strtoul(cb->f[1], nil, 0);
               print("usb: ep%d.%d debug %d\n",
                       ep->dev->nb, ep->nb, ep->debug);
               break;
       case CMname:
               deprint("usb epctl %s %s\n", cb->f[0], cb->f[1]);
               validname(cb->f[1], 0);
               kstrdup(&ep->name, cb->f[1]);
               break;
       case CMtmout:
               deprint("usb epctl %s\n", cb->f[0]);
               if(ep->ttype == Tiso || ep->ttype == Tctl)
                       error("ctl ignored for this endpoint type");
               ep->tmout = strtoul(cb->f[1], nil, 0);
               if(ep->tmout != 0 && ep->tmout < Xfertmout)
                       ep->tmout = Xfertmout;
               break;
       case CMsampledelay:
               if(ep->ttype != Tiso)
                       error("ctl ignored for this endpoint type");
               ep->sampledelay = strtoul(cb->f[1], nil, 0);
               break;
       case CMpreset:
               deprint("usb epctl %s\n", cb->f[0]);
               if(ep->ttype != Tctl)
                       error("not a control endpoint");
               if(ep->dev->state != Denabled)
                       error("forbidden on devices not enabled");
               ep->dev->state = Dreset;
               break;
       default:
               panic("usb: unknown epctl %d", ct->index);
       }
       free(cb);
       poperror();
       return n;
}

static long
usbctl(void *a, long n)
{
       Cmdtab *ct;
       Cmdbuf *cb;
       Ep *ep;
       int i;

       cb = parsecmd(a, n);
       if(waserror()){
               free(cb);
               nexterror();
       }
       ct = lookupcmd(cb, usbctls, nelem(usbctls));
       dprint("usb ctl %s\n", cb->f[0]);
       switch(ct->index){
       case CMdebug:
               if(strcmp(cb->f[1], "on") == 0)
                       debug = 1;
               else if(strcmp(cb->f[1], "off") == 0)
                       debug = 0;
               else
                       debug = strtol(cb->f[1], nil, 0);
               print("usb: debug %d\n", debug);
               for(i = 0; i < epmax; i++)
                       if((ep = getep(i)) != nil){
                               if(ep->hp->debug != nil)
                                       ep->hp->debug(ep->hp, debug);
                               putep(ep);
                       }
               break;
       case CMdump:
               dumpeps();
               break;
       }
       free(cb);
       poperror();
       return n;
}

static long
ctlwrite(Chan *c, void *a, long n)
{
       int q;
       Ep *ep;

       q = QID(c->qid);
       if(q == Qctl)
               return usbctl(a, n);

       ep = getep(qid2epidx(q));
       if(ep == nil)
               error(Eio);
       if(waserror()){
               putep(ep);
               nexterror();
       }
       if(ep->dev->state == Ddetach)
               error(Edetach);
       if(isqtype(q, Qepctl) && c->aux != nil){
               /* Be sure we don't keep a cloned ep name */
               free(c->aux);
               c->aux = nil;
               error("read, not write, expected");
       }
       n = epctl(ep, c, a, n);
       putep(ep);
       poperror();
       return n;
}

static long
usbwrite(Chan *c, void *a, long n, vlong off)
{
       int nr, q;
       Ep *ep;

       if(c->qid.type == QTDIR)
               error(Eisdir);

       q = QID(c->qid);

       if(q == Qctl || isqtype(q, Qepctl))
               return ctlwrite(c, a, n);

       ep = getep(qid2epidx(q));
       if(ep == nil)
               error(Eio);
       if(waserror()){
               putep(ep);
               nexterror();
       }
       if(ep->dev->state == Ddetach)
               error(Edetach);
       if(ep->mode == OREAD || ep->inuse == 0)
               error(Ebadusefd);

       switch(ep->ttype){
       case Tnone:
               error(Enotconf);
       case Tctl:
               nr = rhubwrite(ep, a, n);
               if(nr >= 0){
                       n = nr;
                       break;
               }
               /* else fall */
       default:
               ddeprint("\nusbwrite q %#x fid %d cnt %ld off %lld\n",q, c->fid, n, off);
               ep->hp->epwrite(ep, a, n);
       }
       putep(ep);
       poperror();
       return n;
}

void
usbshutdown(void)
{
       Hci *hp;
       int i;

       for(i = 0; i < Nhcis; i++){
               hp = hcis[i];
               if(hp == nil)
                       continue;
               if(hp->shutdown == nil)
                       print("#u: no shutdown function for %s\n", hp->type);
               else
                       hp->shutdown(hp);
       }
}

Dev usbdevtab = {
       L'u',
       "usb",

       usbreset,
       usbinit,
       usbshutdown,
       usbattach,
       usbwalk,
       usbstat,
       usbopen,
       devcreate,
       usbclose,
       usbread,
       devbread,
       usbwrite,
       devbwrite,
       devremove,
       devwstat,
};