#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include "usb.h"
#include "dat.h"
#include "fns.h"

enum {
       Qroot,
       Qusbevent,
       Qmax
};

char *names[] = {
       "",
       "usbevent",
};

static char Enonexist[] = "does not exist";

typedef struct Event Event;

struct Event {
       Dev *dev;       /* the device producing the event,
                          dev->aux points to Fid processing the event */
       char *data;
       int len;
       Event *link;
       int ref;        /* number of readers which will read this one
                          the next time they'll read */
       int prev;       /* number of events pointing to this one with
                          their link pointers */
};

static Event *evlast;
static Req *reqfirst, *reqlast;
static QLock evlock;

static void
addreader(Req *req)
{
       req->aux = nil;
       if(reqfirst == nil)
               reqfirst = req;
       else
               reqlast->aux = req;
       reqlast = req;
}

static void
fulfill(Req *req, Event *e)
{
       int n;

       n = e->len;
       if(n > req->ifcall.count)
               n = req->ifcall.count;
       memmove(req->ofcall.data, e->data, n);
       req->ofcall.count = n;
}

static void
initevent(void)
{
       evlast = emallocz(sizeof(Event), 1);
}

static Event*
putevent(Event *e)
{
       Event *ee;

       ee = e->link;
       if(e->ref || e->prev)
               return ee;
       ee->prev--;
       closedev(e->dev);
       free(e->data);
       free(e);
       return ee;
}

static void
procreqs(void)
{
       Req *r, *p, *x;
       Event *e;
       Fid *f;

Loop:
       for(p = nil, r = reqfirst; r != nil; p = r, r = x){
               x = r->aux;
               f = r->fid;
               e = f->aux;
               if(e == evlast)
                       continue;
               if(e->dev->aux == f){
                       e->dev->aux = nil;      /* release device */
                       e->ref--;
                       e = putevent(e);
                       e->ref++;
                       f->aux = e;
                       goto Loop;
               }
               if(e->dev->aux == nil){
                       e->dev->aux = f;        /* claim device */
                       if(x == nil)
                               reqlast = p;
                       if(p == nil)
                               reqfirst = x;
                       else
                               p->aux = x;
                       r->aux = nil;
                       fulfill(r, e);
                       respond(r, nil);
                       goto Loop;
               }
       }
}

static void
pushevent(Dev *d, char *data)
{
       Event *e;

       qlock(&evlock);
       e = evlast;
       evlast = emallocz(sizeof(Event), 1);
       incref(d);
       e->dev = d;
       e->data = data;
       e->len = strlen(data);
       e->link = evlast;
       evlast->prev++;
       procreqs();
       putevent(e);
       qunlock(&evlock);
}

static int
dirgen(int n, Dir *d, void *)
{
       if(n >= Qmax - 1)
               return -1;
       d->qid.path = n + 1;
       d->qid.vers = 0;
       if(n >= 0){
               d->qid.type = 0;
               d->mode = 0444;
       }else{
               d->qid.type = QTDIR;
               d->mode = 0555 | DMDIR;
       }
       d->uid = estrdup9p(getuser());
       d->gid = estrdup9p(d->uid);
       d->muid = estrdup9p(d->uid);
       d->name = estrdup9p(names[n+1]);
       d->atime = d->mtime = time(0);
       d->length = 0;
       return 0;
}

static void
usbdattach(Req *req)
{
       req->fid->qid = (Qid) {Qroot, 0, QTDIR};
       req->ofcall.qid = req->fid->qid;
       respond(req, nil);
}

static char *
usbdwalk(Fid *fid, char *name, Qid *qid)
{
       int i;

       if(fid->qid.path != Qroot)
               return "not a directory";
       if(strcmp(name, "..") == 0){
               *qid = fid->qid;
               return nil;
       }
       for(i = Qroot+1; i < Qmax; i++)
               if(strcmp(name, names[i]) == 0){
                       fid->qid = (Qid) {i, 0, 0};
                       *qid = fid->qid;
                       return nil;
               }
       return Enonexist;
}

static void
usbdread(Req *req)
{
       switch((long)req->fid->qid.path){
       case Qroot:
               dirread9p(req, dirgen, nil);
               respond(req, nil);
               break;
       case Qusbevent:
               qlock(&evlock);
               addreader(req);
               procreqs();
               qunlock(&evlock);
               break;
       default:
               respond(req, Enonexist);
               break;
       }
}

static void
usbdstat(Req *req)
{
       if(dirgen(req->fid->qid.path - 1, &req->d, nil) < 0)
               respond(req, Enonexist);
       else
               respond(req, nil);
}

static char *
formatdev(Dev *d, int type)
{
       Usbdev *u = d->usb;
       return smprint("%s %d %.4x %.4x %.6lx %s\n",
               type ? "detach" : "attach",
               d->id, u->vid, u->did, u->csp,
               d->hname != nil ? d->hname : "");
}

static void
enumerate(Event **l)
{
       extern Hub *hubs;

       Event *e;
       Hub *h;
       Port *p;
       Dev *d;
       int i;

       for(h = hubs; h != nil; h = h->next){
               for(i = 1; i <= h->nport; i++){
                       p = &h->port[i];
                       d = p->dev;
                       if(d == nil || d->usb == nil || p->hub != nil)
                               continue;
                       e = emallocz(sizeof(Event), 1);
                       incref(d);
                       e->dev = d;
                       e->data = formatdev(d, 0);
                       e->len = strlen(e->data);
                       e->prev = 1;
                       *l = e;
                       l = &e->link;

               }
       }
       *l = evlast;
       evlast->prev++;
}

static void
usbdopen(Req *req)
{
       extern QLock hublock;

       if(req->fid->qid.path == Qusbevent){
               Event *e;

               qlock(&hublock);
               qlock(&evlock);

               enumerate(&e);
               e->prev--;
               e->ref++;
               req->fid->aux = e;

               qunlock(&evlock);
               qunlock(&hublock);
       }
       respond(req, nil);
}

static void
usbddestroyfid(Fid *fid)
{
       if(fid->qid.path == Qusbevent){
               Event *e;

               qlock(&evlock);
               e = fid->aux;
               if(e != nil){
                       fid->aux = nil;
                       if(e->dev != nil && e->dev->aux == fid){
                               e->dev->aux = nil;      /* release device */
                               procreqs();
                       }
                       e->ref--;
                       while(e->ref == 0 && e->prev == 0 && e != evlast)
                               e = putevent(e);
               }
               qunlock(&evlock);
       }
}

static void
usbdflush(Req *req)
{
       Req *r, *p, *x;

       qlock(&evlock);
       for(p = nil, r = reqfirst; r != nil; p = r, r = x){
               x = r->aux;
               if(r == req->oldreq){
                       if(x == nil)
                               reqlast = p;
                       if(p == nil)
                               reqfirst = x;
                       else
                               p->aux = x;
                       r->aux = nil;
                       respond(r, "interrupted");
                       break;
               }
       }
       qunlock(&evlock);
       respond(req, nil);
}

static void
usbdstart(Srv*)
{
       switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
       case -1: sysfatal("rfork: %r");
       case 0: work(); exits(nil);
       }
}

static void
usbdend(Srv*)
{
       postnote(PNGROUP, getpid(), "shutdown");
}

Srv usbdsrv = {
       .start = usbdstart,
       .end = usbdend,
       .attach = usbdattach,
       .walk1 = usbdwalk,
       .read = usbdread,
       .stat = usbdstat,
       .open = usbdopen,
       .flush = usbdflush,
       .destroyfid = usbddestroyfid,
};

static void
assignhname(Dev *dev)
{
       extern Hub *hubs;
       char buf[64];
       Usbdev *ud;
       Hub *h;
       int i;

       ud = dev->usb;

       /* build string of device unique stuff */
       snprint(buf, sizeof(buf), "%.4x%.4x%.4x%.6lx%s",
               ud->vid, ud->did, ud->dno, ud->csp, ud->serial);

       hname(buf);

       /* check for collisions */
       for(h = hubs; h != nil; h = h->next){
               for(i = 1; i <= h->nport; i++){
                       if(h->port[i].dev == nil)
                               continue;
                       if(h->port[i].dev->hname == nil || h->port[i].dev == dev)
                               continue;
                       if(strcmp(h->port[i].dev->hname, buf) == 0){
                               dev->hname = smprint("%s%d", buf, dev->id);
                               return;
                       }
               }
       }
       dev->hname = strdup(buf);
}

int
attachdev(Port *p)
{
       Dev *d = p->dev;
       int id;

       if(d->usb->class == Clhub){
               /*
                * Hubs are handled directly by this process avoiding
                * concurrent operation so that at most one device
                * has the config address in use.
                * We cancel kernel debug for these eps. too chatty.
                */
               if((p->hub = newhub(d->dir, d)) == nil)
                       return -1;
               return 0;
       }

       /* create all endpoint files for default conf #1 */
       for(id=1; id<nelem(d->usb->ep); id++){
               Ep *ep = d->usb->ep[id];
               if(ep != nil && ep->conf != nil && ep->conf->cval == 1){
                       Dev *epd = openep(d, id);
                       if(epd != nil)
                               closedev(epd);
               }
       }

       /* close control endpoint so driver can open it */
       close(d->dfd);
       d->dfd = -1;

       /* assign stable name based on device descriptor */
       assignhname(d);

       /* set device info for ctl file */
       devctl(d, "info %s csp %#08lux vid %#.4ux did %#.4ux %q %q %s",
               classname(Class(d->usb->csp)), d->usb->csp, d->usb->vid, d->usb->did,
               d->usb->vendor, d->usb->product, d->hname);

       pushevent(d, formatdev(d, 0));
       return 0;
}

void
detachdev(Port *p)
{
       Dev *d = p->dev;

       if(d->usb->class == Clhub)
               return;
       pushevent(d, formatdev(d, 1));
}

/*
* we create /env/usbbusy on startup and once all devices have been
* enumerated and readers have consumed all the events, we remove the
* file so nusbrc can continue.
*/
static int busyfd = -1;

void
checkidle(void)
{
       if(busyfd < 0 || reqlast == nil || evlast == nil || evlast->prev > 0)
               return;

       close(busyfd);
       busyfd = -1;
}

void
main(int argc, char **argv)
{
       int fd, i, nd;
       char *fn;
       Dir *d;

       ARGBEGIN {
       case 'D':
               chatty9p++;
               break;
       case 'd':
               usbdebug++;
               break;
       } ARGEND;

       quotefmtinstall();
       fmtinstall('U', Ufmt);
       initevent();

       hubs = nil;
       if(argc == 0){
               if((fd = open("/dev/usb", OREAD)) < 0)
                       sysfatal("/dev/usb: %r");
               nd = dirreadall(fd, &d);
               close(fd);
               for(i = 0; i < nd; i++){
                       if(strcmp(d[i].name, "ctl") != 0){
                               fn = smprint("/dev/usb/%s", d[i].name);
                               newhub(fn, nil);
                               free(fn);
                       }
               }
               free(d);
       }else {
               for(i = 0; i < argc; i++)
                       newhub(argv[i], nil);
       }

       if(hubs == nil)
               sysfatal("no hubs");

       busyfd = create("/env/usbbusy", ORCLOSE, 0600);
       postsharesrv(&usbdsrv, nil, "usb", "usbd");
       exits(nil);
}