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 "usb.h"

/*
 * epN.M -> N
 */
static int
nameid(char *s)
{
        char *r;
        char nm[20];

        r = strrchr(s, 'p');
        if(r == nil)
                return -1;
        strecpy(nm, nm+sizeof(nm), r+1);
        r = strchr(nm, '.');
        if(r == nil)
                return -1;
        *r = 0;
        return atoi(nm);
}

Dev*
openep(Dev *d, int id)
{
        char *mode;     /* How many modes? */
        Ep *ep;
        Altc *ac;
        Dev *epd;
        Usbdev *ud;
        char name[40];

        if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
                return nil;
        if(d->cfd < 0 || d->usb == nil){
                werrstr("device not configured");
                return nil;
        }
        ud = d->usb;
        if(id < 0 || id >= nelem(ud->ep) || ud->ep[id] == nil){
                werrstr("bad enpoint number");
                return nil;
        }
        ep = ud->ep[id];
        mode = "rw";
        if(ep->dir == Ein)
                mode = "r";
        if(ep->dir == Eout)
                mode = "w";
        snprint(name, sizeof(name), "/dev/usb/ep%d.%d", d->id, id);
        if(access(name, AEXIST) == 0){
                dprint(2, "%s: %s already exists; trying to open\n", argv0, name);
                epd = opendev(name);
                if(epd != nil)
                        epd->maxpkt = ep->maxpkt;       /* guess */
                return epd;
        }
        if(devctl(d, "new %d %d %s", id, ep->type, mode) < 0){
                dprint(2, "%s: %s: new: %r\n", argv0, d->dir);
                return nil;
        }
        epd = opendev(name);
        if(epd == nil)
                return nil;
        epd->id = id;
        if(devctl(epd, "maxpkt %d", ep->maxpkt) < 0)
                fprint(2, "%s: %s: openep: maxpkt: %r\n", argv0, epd->dir);
        else
                dprint(2, "%s: %s: maxpkt %d\n", argv0, epd->dir, ep->maxpkt);
        epd->maxpkt = ep->maxpkt;
        ac = ep->iface->altc[0];
        if(ep->ntds > 1 && devctl(epd, "ntds %d", ep->ntds) < 0)
                fprint(2, "%s: %s: openep: ntds: %r\n", argv0, epd->dir);
        else
                dprint(2, "%s: %s: ntds %d\n", argv0, epd->dir, ep->ntds);

        /*
         * For iso endpoints and high speed interrupt endpoints the pollival is
         * actually 2ⁿ and not n.
         * The kernel usb driver must take that into account.
         * It's simpler this way.
         */

        if(ac != nil && (ep->type == Eintr || ep->type == Eiso) && ac->interval != 0)
                if(devctl(epd, "pollival %d", ac->interval) < 0)
                        fprint(2, "%s: %s: openep: pollival: %r\n", argv0, epd->dir);
        return epd;
}

Dev*
opendev(char *fn)
{
        Dev *d;
        int l;

        if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
                return nil;
        d = emallocz(sizeof(Dev), 1);
        incref(d);

        l = strlen(fn);
        d->dfd = -1;
        /*
         * +30 to allocate extra size to concat "/<epfilename>"
         * we should probably remove that feature from the manual
         * and from the code after checking out that nobody relies on
         * that.
         */
        d->dir = emallocz(l + 30, 0);
        strcpy(d->dir, fn);
        strcpy(d->dir+l, "/ctl");
        d->cfd = open(d->dir, ORDWR|OCEXEC);
        d->dir[l] = 0;
        d->id = nameid(fn);
        if(d->cfd < 0){
                werrstr("can't open endpoint %s: %r", d->dir);
                free(d->dir);
                free(d);
                return nil;
        }
        dprint(2, "%s: opendev %#p %s\n", argv0, d, fn);
        return d;
}

int
opendevdata(Dev *d, int mode)
{
        char buf[80]; /* more than enough for a usb path */

        seprint(buf, buf+sizeof(buf), "%s/data", d->dir);
        d->dfd = open(buf, mode|OCEXEC);
        return d->dfd;
}

enum
{
        /*
         * Max device conf is also limited by max control request size as
         * limited by Maxctllen in the kernel usb.h (both limits are arbitrary).
         */
        Maxdevconf = 4 * 1024,  /* asking for 16K kills Newsham's disk */
};

int
loaddevconf(Dev *d, int n)
{
        uchar *buf;
        int nr;
        int type;

        if(n >= nelem(d->usb->conf)){
                werrstr("loaddevconf: bug: out of configurations in device");
                fprint(2, "%s: %r\n", argv0);
                return -1;
        }
        buf = emallocz(Maxdevconf, 0);
        type = Rd2h|Rstd|Rdev;
        nr = usbcmd(d, type, Rgetdesc, Dconf<<8|n, 0, buf, Maxdevconf);
        if(nr < Dconflen){
                free(buf);
                return -1;
        }
        if(d->usb->conf[n] == nil)
                d->usb->conf[n] = emallocz(sizeof(Conf), 1);
        nr = parseconf(d->usb, d->usb->conf[n], buf, nr);
        free(buf);
        return nr;
}

Ep*
mkep(Usbdev *d, int id)
{
        Ep *ep;

        d->ep[id] = ep = emallocz(sizeof(Ep), 1);
        ep->id = id;
        return ep;
}

static char*
mkstr(uchar *b, int n)
{
        Rune r;
        char *us;
        char *s;
        char *e;

        if(n <= 2 || (n & 1) != 0)
                return strdup("none");
        n = (n - 2)/2;
        b += 2;
        us = s = emallocz(n*UTFmax+1, 0);
        e = s + n*UTFmax+1;
        for(; --n >= 0; b += 2){
                r = GET2(b);
                s = seprint(s, e, "%C", r);
        }
        return us;
}

char*
loaddevstr(Dev *d, int sid)
{
        uchar buf[128];
        int type;
        int nr;

        if(sid == 0)
                return estrdup("none");
        type = Rd2h|Rstd|Rdev;
        nr=usbcmd(d, type, Rgetdesc, Dstr<<8|sid, 0, buf, sizeof(buf));
        return mkstr(buf, nr);
}

int
loaddevdesc(Dev *d)
{
        uchar buf[Ddevlen+255];
        int nr;
        int type;
        Ep *ep0;

        type = Rd2h|Rstd|Rdev;
        nr = sizeof(buf);
        memset(buf, 0, Ddevlen);
        if((nr=usbcmd(d, type, Rgetdesc, Ddev<<8|0, 0, buf, nr)) < 0)
                return -1;
        /*
         * Several hubs are returning descriptors of 17 bytes, not 18.
         * We accept them and leave number of configurations as zero.
         * (a get configuration descriptor also fails for them!)
         */
        if(nr < Ddevlen){
                print("%s: %s: warning: device with short descriptor\n",
                        argv0, d->dir);
                if(nr < Ddevlen-1){
                        werrstr("short device descriptor (%d bytes)", nr);
                        return -1;
                }
        }
        d->usb = emallocz(sizeof(Usbdev), 1);
        ep0 = mkep(d->usb, 0);
        ep0->dir = Eboth;
        ep0->type = Econtrol;
        ep0->maxpkt = d->maxpkt = 8;            /* a default */
        nr = parsedev(d, buf, nr);
        if(nr >= 0){
                d->usb->vendor = loaddevstr(d, d->usb->vsid);
                if(strcmp(d->usb->vendor, "none") != 0){
                        d->usb->product = loaddevstr(d, d->usb->psid);
                        d->usb->serial = loaddevstr(d, d->usb->ssid);
                }
        }
        return nr;
}

int
configdev(Dev *d)
{
        int i;

        if(d->dfd < 0)
                opendevdata(d, ORDWR);
        if(loaddevdesc(d) < 0)
                return -1;
        for(i = 0; i < d->usb->nconf; i++)
                if(loaddevconf(d, i) < 0)
                        return -1;
        return 0;
}

static void
closeconf(Conf *c)
{
        int i;
        int a;

        if(c == nil)
                return;
        for(i = 0; i < nelem(c->iface); i++)
                if(c->iface[i] != nil){
                        for(a = 0; a < nelem(c->iface[i]->altc); a++)
                                free(c->iface[i]->altc[a]);
                        free(c->iface[i]);
                }
        free(c);
}

void
closedev(Dev *d)
{
        int i;
        Usbdev *ud;

        if(d==nil || decref(d) != 0)
                return;
        dprint(2, "%s: closedev %#p %s\n", argv0, d, d->dir);
        if(d->free != nil)
                d->free(d->aux);
        if(d->cfd >= 0)
                close(d->cfd);
        if(d->dfd >= 0)
                close(d->dfd);
        d->cfd = d->dfd = -1;
        free(d->dir);
        d->dir = nil;
        ud = d->usb;
        d->usb = nil;
        if(ud != nil){
                free(ud->vendor);
                free(ud->product);
                free(ud->serial);
                for(i = 0; i < nelem(ud->ep); i++)
                        free(ud->ep[i]);
                for(i = 0; i < nelem(ud->ddesc); i++)
                        free(ud->ddesc[i]);

                for(i = 0; i < nelem(ud->conf); i++)
                        closeconf(ud->conf[i]);
                free(ud);
        }
        free(d);
}

static char*
reqstr(int type, int req)
{
        char *s;
        static char* ds[] = { "dev", "if", "ep", "oth" };
        static char buf[40];

        if(type&Rd2h)
                s = seprint(buf, buf+sizeof(buf), "d2h");
        else
                s = seprint(buf, buf+sizeof(buf), "h2d");
        if(type&Rclass)
                s = seprint(s, buf+sizeof(buf), "|cls");
        else if(type&Rvendor)
                s = seprint(s, buf+sizeof(buf), "|vnd");
        else
                s = seprint(s, buf+sizeof(buf), "|std");
        s = seprint(s, buf+sizeof(buf), "|%s", ds[type&3]);

        switch(req){
        case Rgetstatus: s = seprint(s, buf+sizeof(buf), " getsts"); break;
        case Rclearfeature: s = seprint(s, buf+sizeof(buf), " clrfeat"); break;
        case Rsetfeature: s = seprint(s, buf+sizeof(buf), " setfeat"); break;
        case Rsetaddress: s = seprint(s, buf+sizeof(buf), " setaddr"); break;
        case Rgetdesc: s = seprint(s, buf+sizeof(buf), " getdesc"); break;
        case Rsetdesc: s = seprint(s, buf+sizeof(buf), " setdesc"); break;
        case Rgetconf: s = seprint(s, buf+sizeof(buf), " getcnf"); break;
        case Rsetconf: s = seprint(s, buf+sizeof(buf), " setcnf"); break;
        case Rgetiface: s = seprint(s, buf+sizeof(buf), " getif"); break;
        case Rsetiface: s = seprint(s, buf+sizeof(buf), " setif"); break;
        }
        USED(s);
        return buf;
}

static int
cmdreq(Dev *d, int type, int req, int value, int index, uchar *data, int count)
{
        int ndata, n;
        uchar *wp;
        uchar buf[8];
        char *hd, *rs;

        assert(d != nil);
        if(data == nil){
                wp = buf;
                ndata = 0;
        }else{
                ndata = count;
                wp = emallocz(8+ndata, 0);
        }
        wp[0] = type;
        wp[1] = req;
        PUT2(wp+2, value);
        PUT2(wp+4, index);
        PUT2(wp+6, count);
        if(data != nil)
                memmove(wp+8, data, ndata);
        if(usbdebug>2){
                hd = hexstr(wp, ndata+8);
                rs = reqstr(type, req);
                fprint(2, "%s: %s val %d|%d idx %d cnt %d out[%d] %s\n",
                        d->dir, rs, value>>8, value&0xFF,
                        index, count, ndata+8, hd);
                free(hd);
        }
        n = write(d->dfd, wp, 8+ndata);
        if(wp != buf)
                free(wp);
        if(n < 0)
                return -1;
        if(n != 8+ndata){
                dprint(2, "%s: cmd: short write: %d\n", argv0, n);
                return -1;
        }
        return n;
}

static int
cmdrep(Dev *d, void *buf, int nb)
{
        char *hd;

        nb = read(d->dfd, buf, nb);
        if(nb >0 && usbdebug > 2){
                hd = hexstr(buf, nb);
                fprint(2, "%s: in[%d] %s\n", d->dir, nb, hd);
                free(hd);
        }
        return nb;
}

int
usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count)
{
        int i, r, nerr;
        char err[64];

        /*
         * Some devices do not respond to commands some times.
         * Others even report errors but later work just fine. Retry.
         */
        r = -1;
        *err = 0;
        for(i = nerr = 0; i < Uctries; i++){
                if(type & Rd2h)
                        r = cmdreq(d, type, req, value, index, nil, count);
                else
                        r = cmdreq(d, type, req, value, index, data, count);
                if(r > 0){
                        if((type & Rd2h) == 0)
                                break;
                        r = cmdrep(d, data, count);
                        if(r > 0)
                                break;
                        if(r == 0)
                                werrstr("no data from device");
                }
                nerr++;
                if(*err == 0)
                        rerrstr(err, sizeof(err));
                sleep(Ucdelay);
        }
        if(r > 0 && i >= 2)
                /* let the user know the device is not in good shape */
                fprint(2, "%s: usbcmd: %s: required %d attempts (%s)\n",
                        argv0, d->dir, i, err);
        return r;
}

int
unstall(Dev *dev, Dev *ep, int dir)
{
        int r;

        if(dir == Ein)
                dir = 0x80;
        else
                dir = 0;
        r = Rh2d|Rstd|Rep;
        if(usbcmd(dev, r, Rclearfeature, Fhalt, ep->id|dir, nil, 0)<0){
                werrstr("unstall: %s: %r", ep->dir);
                return -1;
        }
        if(devctl(ep, "clrhalt") < 0){
                werrstr("clrhalt: %s: %r", ep->dir);
                return -1;
        }
        return 0;
}

/*
 * To be sure it uses a single write.
 */
int
devctl(Dev *dev, char *fmt, ...)
{
        char buf[128];
        va_list arg;
        char *e;

        va_start(arg, fmt);
        e = vseprint(buf, buf+sizeof(buf), fmt, arg);
        va_end(arg);
        return write(dev->cfd, buf, e-buf);
}