diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/usb/lib/dev.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/usb/lib/dev.c')
-rwxr-xr-x | sys/src/cmd/usb/lib/dev.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/sys/src/cmd/usb/lib/dev.c b/sys/src/cmd/usb/lib/dev.c new file mode 100755 index 000000000..675cc6908 --- /dev/null +++ b/sys/src/cmd/usb/lib/dev.c @@ -0,0 +1,493 @@ +#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); +} |