diff options
author | aiju <aiju@phicode.de> | 2011-07-30 19:14:18 +0200 |
---|---|---|
committer | aiju <aiju@phicode.de> | 2011-07-30 19:14:18 +0200 |
commit | 9f4184892cb4d95c39064906d2c7630f30352215 (patch) | |
tree | 0f4ab2102f86d3f93bdd684c5cd0fcf116673ef2 /sys/src/cmd/nusb/serial/serial.c | |
parent | 2ba1b4c476c986ae788e3b0869fc799e5516a2c2 (diff) |
added nusb/serial
Diffstat (limited to 'sys/src/cmd/nusb/serial/serial.c')
-rw-r--r-- | sys/src/cmd/nusb/serial/serial.c | 876 |
1 files changed, 876 insertions, 0 deletions
diff --git a/sys/src/cmd/nusb/serial/serial.c b/sys/src/cmd/nusb/serial/serial.c new file mode 100644 index 000000000..4a000325a --- /dev/null +++ b/sys/src/cmd/nusb/serial/serial.c @@ -0,0 +1,876 @@ +/* + * This part takes care of locking except for initialization and + * other threads created by the hw dep. drivers. + */ + +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <thread.h> +#include <fcall.h> +#include <9p.h> +#include "usb.h" +#include "serial.h" +#include "prolific.h" +#include "ucons.h" +#include "ftdi.h" + +int serialdebug; +static int sdebug; + +Serialport **ports; +int nports; + +static void +serialfatal(Serial *ser) +{ + Serialport *p; + int i; + + dsprint(2, "serial: fatal error, detaching\n"); + devctl(ser->dev, "detach"); + + for(i = 0; i < ser->nifcs; i++){ + p = &ser->p[i]; + if(p->w4data != nil) + chanclose(p->w4data); + if(p->gotdata != nil) + chanclose(p->gotdata); + if(p->readc) + chanclose(p->readc); + } +} + +/* I sleep with the lock... only way to drain in general */ +static void +serialdrain(Serialport *p) +{ + Serial *ser; + uint baud, pipesize; + + ser = p->s; + baud = p->baud; + + if(p->baud == ~0) + return; + if(ser->maxwtrans < 256) + pipesize = 256; + else + pipesize = ser->maxwtrans; + /* wait for the at least 256-byte pipe to clear */ + sleep(10 + pipesize/((1 + baud)*1000)); + if(ser->clearpipes != nil) + ser->clearpipes(p); +} + +int +serialreset(Serial *ser) +{ + Serialport *p; + int i, res; + + res = 0; + /* cmd for reset */ + for(i = 0; i < ser->nifcs; i++){ + p = &ser->p[i]; + serialdrain(p); + } + if(ser->reset != nil) + res = ser->reset(ser, nil); + return res; +} + +/* call this if something goes wrong, must be qlocked */ +int +serialrecover(Serial *ser, Serialport *p, Dev *ep, char *err) +{ + if(p != nil) + dprint(2, "serial[%d], %s: %s, level %d\n", p->interfc, + p->name, err, ser->recover); + else + dprint(2, "serial[%s], global error, level %d\n", + ser->p[0].name, ser->recover); + ser->recover++; + if(strstr(err, "detached") != nil) + return -1; + if(ser->recover < 3){ + if(p != nil){ + if(ep != nil){ + if(ep == p->epintr) + unstall(ser->dev, p->epintr, Ein); + if(ep == p->epin) + unstall(ser->dev, p->epin, Ein); + if(ep == p->epout) + unstall(ser->dev, p->epout, Eout); + return 0; + } + + if(p->epintr != nil) + unstall(ser->dev, p->epintr, Ein); + if(p->epin != nil) + unstall(ser->dev, p->epin, Ein); + if(p->epout != nil) + unstall(ser->dev, p->epout, Eout); + } + return 0; + } + if(ser->recover > 4 && ser->recover < 8) + serialfatal(ser); + if(ser->recover > 8){ + ser->reset(ser, p); + return 0; + } + if(serialreset(ser) < 0) + return -1; + return 0; +} + +static int +serialctl(Serialport *p, char *cmd) +{ + Serial *ser; + int c, i, n, nf, nop, nw, par, drain, set, lines; + char *f[16]; + uchar x; + + ser = p->s; + drain = set = lines = 0; + nf = tokenize(cmd, f, nelem(f)); + for(i = 0; i < nf; i++){ + if(strncmp(f[i], "break", 5) == 0){ + if(ser->setbreak != nil) + ser->setbreak(p, 1); + continue; + } + + nop = 0; + n = atoi(f[i]+1); + c = *f[i]; + if (isascii(c) && isupper(c)) + c = tolower(c); + switch(c){ + case 'b': + drain++; + p->baud = n; + set++; + break; + case 'c': + p->dcd = n; + // lines++; + ++nop; + break; + case 'd': + p->dtr = n; + lines++; + break; + case 'e': + p->dsr = n; + // lines++; + ++nop; + break; + case 'f': /* flush the pipes */ + drain++; + break; + case 'h': /* hangup?? */ + p->rts = p->dtr = 0; + lines++; + fprint(2, "serial: %c, unsure ctl\n", c); + break; + case 'i': + ++nop; + break; + case 'k': + drain++; + ser->setbreak(p, 1); + sleep(n); + ser->setbreak(p, 0); + break; + case 'l': + drain++; + p->bits = n; + set++; + break; + case 'm': + drain++; + if(ser->modemctl != nil) + ser->modemctl(p, n); + if(n == 0) + p->cts = 0; + break; + case 'n': + p->blocked = n; + ++nop; + break; + case 'p': /* extended... */ + if(strlen(f[i]) != 2) + return -1; + drain++; + par = f[i][1]; + if(par == 'n') + p->parity = 0; + else if(par == 'o') + p->parity = 1; + else if(par == 'e') + p->parity = 2; + else if(par == 'm') /* mark parity */ + p->parity = 3; + else if(par == 's') /* space parity */ + p->parity = 4; + else + return -1; + set++; + break; + case 'q': + // drain++; + p->limit = n; + ++nop; + break; + case 'r': + drain++; + p->rts = n; + lines++; + break; + case 's': + drain++; + p->stop = n; + set++; + break; + case 'w': + /* ?? how do I put this */ + p->timer = n * 100000LL; + ++nop; + break; + case 'x': + if(n == 0) + x = CTLS; + else + x = CTLQ; + if(ser->wait4write != nil) + nw = ser->wait4write(p, &x, 1); + else + nw = write(p->epout->dfd, &x, 1); + if(nw != 1){ + serialrecover(ser, p, p->epout, ""); + return -1; + } + break; + } + /* + * don't print. the condition is harmless and the print + * splatters all over the display. + */ + USED(nop); + if (0 && nop) + fprint(2, "serial: %c, unsupported nop ctl\n", c); + } + if(drain) + serialdrain(p); + if(lines && !set){ + if(ser->sendlines != nil && ser->sendlines(p) < 0) + return -1; + } else if(set){ + if(ser->setparam != nil && ser->setparam(p) < 0) + return -1; + } + ser->recover = 0; + return 0; +} + +char *pformat = "noems"; + +char * +serdumpst(Serialport *p, char *buf, int bufsz) +{ + char *e, *s; + Serial *ser; + + ser = p->s; + + e = buf + bufsz; + s = seprint(buf, e, "b%d ", p->baud); + s = seprint(s, e, "c%d ", p->dcd); /* unimplemented */ + s = seprint(s, e, "d%d ", p->dtr); + s = seprint(s, e, "e%d ", p->dsr); /* unimplemented */ + s = seprint(s, e, "l%d ", p->bits); + s = seprint(s, e, "m%d ", p->mctl); + if(p->parity >= 0 || p->parity < strlen(pformat)) + s = seprint(s, e, "p%c ", pformat[p->parity]); + else + s = seprint(s, e, "p%c ", '?'); + s = seprint(s, e, "r%d ", p->rts); + s = seprint(s, e, "s%d ", p->stop); + s = seprint(s, e, "i%d ", p->fifo); + s = seprint(s, e, "\ndev(%d) ", 0); + s = seprint(s, e, "type(%d) ", ser->type); + s = seprint(s, e, "framing(%d) ", p->nframeerr); + s = seprint(s, e, "overruns(%d) ", p->novererr); + s = seprint(s, e, "berr(%d) ", p->nbreakerr); + s = seprint(s, e, " serr(%d)\n", p->nparityerr); + return s; +} + +static int +serinit(Serialport *p) +{ + int res; + res = 0; + Serial *ser; + + ser = p->s; + + if(ser->init != nil) + res = ser->init(p); + if(ser->getparam != nil) + ser->getparam(p); + p->nframeerr = p->nparityerr = p->nbreakerr = p->novererr = 0; + + return res; +} + +static void +dattach(Req *req) +{ + req->fid->qid = (Qid) {0, 0, QTDIR}; + req->ofcall.qid = req->fid->qid; + respond(req, nil); +} + +static int +dirgen(int n, Dir *d, void *) +{ + if(n >= nports * 2) + return -1; + d->qid.path = n + 1; + d->qid.vers = 0; + if(n >= 0) + d->qid.type = 0; + else + d->qid.type = QTDIR; + d->uid = strdup("usb"); + d->gid = strdup(d->uid); + d->muid = strdup(d->uid); + if(n >= 0){ + d->name = smprint((n & 1) ? "%sctl" : "%s", ports[n/2]->name); + d->mode = ((n & 1) ? 0664 : 0660); + }else{ + d->name = strdup(""); + d->mode = 0555 | QTDIR; + } + d->atime = d->mtime = time(0); + d->length = 0; + return 0; +} + +static char * +dwalk(Fid *fid, char *name, Qid *qidp) +{ + int i; + int len; + Qid qid; + char *p; + + qid = fid->qid; + if((qid.type & QTDIR) == 0){ + return "walk in non-directory"; + } + + if(strcmp(name, "..") == 0){ + fid->qid.path = 0; + fid->qid.vers = 0; + fid->qid.type = QTDIR; + *qidp = fid->qid; + return nil; + } + + for(i = 0; i < nports; i++) + if(strncmp(name, ports[i]->name, len = strlen(ports[i]->name)) == 0){ + p = name + len; + if(*p == 0) + fid->qid.path = 2 * i + 1; + else if(strcmp(p, "ctl") == 0) + fid->qid.path = 2 * i + 2; + else + continue; + fid->qid.vers = 0; + fid->qid.type = 0; + *qidp = fid->qid; + return nil; + } + return "does not exist"; +} + +static void +dstat(Req *req) +{ + if(dirgen(req->fid->qid.path - 1, &req->d, nil) < 0) + respond(req, "the front fell off"); + else + respond(req, nil); +} + +enum { + Serbufsize = 255, +}; + +static void +readproc(void *aux) +{ + int dfd; + Req *req; + long count, rcount; + void *data; + Serial *ser; + Serialport *p; + static int errrun, good; + char err[Serbufsize]; + + p = aux; + ser = p->s; + for(;;){ + qlock(&p->readq); + while(p->readfirst == nil) + rsleep(&p->readrend); + req = p->readfirst; + p->readfirst = req->aux; + if(p->readlast == req) + p->readlast = nil; + req->aux = nil; + qunlock(&p->readq); + + count = req->ifcall.count; + data = req->ofcall.data; + qlock(ser); + if(count > ser->maxread) + count = ser->maxread; + dsprint(2, "serial: reading from data\n"); + do { + err[0] = 0; + dfd = p->epin->dfd; + if(usbdebug >= 3) + dsprint(2, "serial: reading: %ld\n", count); + + assert(count > 0); + if(ser->wait4data != nil) + rcount = ser->wait4data(p, data, count); + else{ + qunlock(ser); + rcount = read(dfd, data, count); + qlock(ser); + } + /* + * if we encounter a long run of continuous read + * errors, do something drastic so that our caller + * doesn't just spin its wheels forever. + */ + if(rcount < 0) { + snprint(err, Serbufsize, "%r"); + ++errrun; + sleep(20); + if (good > 0 && errrun > 10000) { + /* the line has been dropped; give up */ + qunlock(ser); + fprint(2, "%s: line %s is gone: %r\n", + argv0, p->name); + threadexitsall("serial line gone"); + } + } else { + errrun = 0; + good++; + } + if(usbdebug >= 3) + dsprint(2, "serial: read: %s %ld\n", err, rcount); + } while(rcount < 0 && strstr(err, "timed out") != nil); + + dsprint(2, "serial: read from bulk %ld, %10.10s\n", rcount, err); + if(rcount < 0){ + dsprint(2, "serial: need to recover, data read %ld %r\n", + count); + serialrecover(ser, p, p->epin, err); + } + dsprint(2, "serial: read from bulk %ld\n", rcount); + if(rcount >= 0){ + req->ofcall.count = rcount; + respond(req, nil); + } else + responderror(req); + qunlock(ser); + } +} + +static void +dread(Req *req) +{ + char *e; /* change */ + Qid q; + Serial *ser; + vlong offset; + Serialport *p; + static char buf[Serbufsize]; + + q = req->fid->qid; + + if(q.path == 0){ + dirread9p(req, dirgen, nil); + respond(req, nil); + return; + } + + p = ports[(q.path - 1) / 2]; + ser = p->s; + offset = req->ifcall.offset; + + memset(buf, 0, sizeof buf); + qlock(ser); + switch((long)((q.path - 1) % 2)){ + case 0: + qlock(&p->readq); + if(p->readfirst == nil) + p->readfirst = req; + else + p->readlast->aux = req; + p->readlast = req; + rwakeup(&p->readrend); + qunlock(&p->readq); + break; + case 1: + if(offset == 0) { + if(!p->isjtag){ + e = serdumpst(p, buf, Serbufsize); + readbuf(req, buf, e - buf); + } + } + respond(req, nil); + break; + } + qunlock(ser); +} + +static long +altwrite(Serialport *p, uchar *buf, long count) +{ + int nw, dfd; + char err[128]; + Serial *ser; + + ser = p->s; + do{ + dsprint(2, "serial: write to bulk %ld\n", count); + + if(ser->wait4write != nil) + /* unlocked inside later */ + nw = ser->wait4write(p, buf, count); + else{ + dfd = p->epout->dfd; + qunlock(ser); + nw = write(dfd, buf, count); + qlock(ser); + } + rerrstr(err, sizeof err); + dsprint(2, "serial: written %s %d\n", err, nw); + } while(nw < 0 && strstr(err, "timed out") != nil); + + if(nw != count){ + dsprint(2, "serial: need to recover, status in write %d %r\n", + nw); + snprint(err, sizeof err, "%r"); + serialrecover(p->s, p, p->epout, err); + } + return nw; +} + +static void +dwrite(Req *req) +{ + ulong path; + char *cmd; + Serial *ser; + long count; + void *buf; + Serialport *p; + + path = req->fid->qid.path; + p = ports[(path-1)/2]; + ser = p->s; + count = req->ifcall.count; + buf = req->ifcall.data; + + qlock(ser); + switch((long)((path-1)%2)){ + case 0: + count = altwrite(p, (uchar *)buf, count); + break; + case 1: + if(p->isjtag) + break; + cmd = emallocz(count+1, 1); + memmove(cmd, buf, count); + cmd[count] = 0; + if(serialctl(p, cmd) < 0){ + qunlock(ser); + free(cmd); + respond(req, "bad control request"); + return; + } + free(cmd); + break; + } + if(count >= 0) + ser->recover = 0; + else + serialrecover(ser, p, p->epout, "writing"); + qunlock(ser); + if(count >= 0){ + req->ofcall.count = count; + respond(req, nil); + } else + responderror(req); +} + +static int +openeps(Serialport *p, int epin, int epout, int epintr) +{ + Serial *ser; + + ser = p->s; + p->epin = openep(ser->dev, epin); + if(p->epin == nil){ + fprint(2, "serial: openep %d: %r\n", epin); + return -1; + } + p->epout = openep(ser->dev, epout); + if(p->epout == nil){ + fprint(2, "serial: openep %d: %r\n", epout); + closedev(p->epin); + return -1; + } + + if(!p->isjtag){ + devctl(p->epin, "timeout 1000"); + devctl(p->epout, "timeout 1000"); + } + + if(ser->hasepintr){ + p->epintr = openep(ser->dev, epintr); + if(p->epintr == nil){ + fprint(2, "serial: openep %d: %r\n", epintr); + closedev(p->epin); + closedev(p->epout); + return -1; + } + opendevdata(p->epintr, OREAD); + devctl(p->epintr, "timeout 1000"); + } + + if(ser->seteps!= nil) + ser->seteps(p); + opendevdata(p->epin, OREAD); + opendevdata(p->epout, OWRITE); + if(p->epin->dfd < 0 ||p->epout->dfd < 0 || + (ser->hasepintr && p->epintr->dfd < 0)){ + fprint(2, "serial: open i/o ep data: %r\n"); + closedev(p->epin); + closedev(p->epout); + if(ser->hasepintr) + closedev(p->epintr); + return -1; + } + return 0; +} + +static int +findendpoints(Serial *ser, int ifc) +{ + int i, epin, epout, epintr; + Ep *ep, **eps; + + epintr = epin = epout = -1; + + /* + * interfc 0 means start from the start which is equiv to + * iterate through endpoints probably, could be done better + */ + eps = ser->dev->usb->conf[0]->iface[ifc]->ep; + + for(i = 0; i < Nep; i++){ + if((ep = eps[i]) == nil) + continue; + if(ser->hasepintr && ep->type == Eintr && + ep->dir == Ein && epintr == -1) + epintr = ep->id; + if(ep->type == Ebulk){ + if(ep->dir == Ein && epin == -1) + epin = ep->id; + if(ep->dir == Eout && epout == -1) + epout = ep->id; + } + } + dprint(2, "serial[%d]: ep ids: in %d out %d intr %d\n", ifc, epin, epout, epintr); + if(epin == -1 || epout == -1 || (ser->hasepintr && epintr == -1)) + return -1; + + if(openeps(&ser->p[ifc], epin, epout, epintr) < 0) + return -1; + + dprint(2, "serial: ep in %s out %s\n", ser->p[ifc].epin->dir, ser->p[ifc].epout->dir); + if(ser->hasepintr) + dprint(2, "serial: ep intr %s\n", ser->p[ifc].epintr->dir); + + if(usbdebug > 1 || serialdebug > 2){ + devctl(ser->p[ifc].epin, "debug 1"); + devctl(ser->p[ifc].epout, "debug 1"); + if(ser->hasepintr) + devctl(ser->p[ifc].epintr, "debug 1"); + devctl(ser->dev, "debug 1"); + } + return 0; +} + +/* keep in sync with main.c */ +static void +usage(void) +{ + fprint(2, "usage: usb/serial [-dD] [-m mtpt] [-s srv] devid\n"); + threadexitsall("usage"); +} + +static void +serdevfree(void *a) +{ + Serial *ser = a; + Serialport *p; + int i; + + if(ser == nil) + return; + + for(i = 0; i < ser->nifcs; i++){ + p = &ser->p[i]; + + if(ser->hasepintr) + closedev(p->epintr); + closedev(p->epin); + closedev(p->epout); + p->epintr = p->epin = p->epout = nil; + if(p->w4data != nil) + chanfree(p->w4data); + if(p->gotdata != nil) + chanfree(p->gotdata); + if(p->readc) + chanfree(p->readc); + + } + free(ser); +} + +static Srv serialfs = { + .attach = dattach, + .walk1 = dwalk, + .read = dread, + .write= dwrite, + .stat = dstat, +}; + +/* +static void +serialfsend(void) +{ + if(p->w4data != nil) + chanclose(p->w4data); + if(p->gotdata != nil) + chanclose(p->gotdata); + if(p->readc) + chanclose(p->readc); +} +*/ + +void +threadmain(int argc, char* argv[]) +{ + Serial *ser; + Dev *dev; + char buf[50]; + int i, devid; + Serialport *p; + + ARGBEGIN{ + case 'd': + serialdebug++; + break; + default: + usage(); + }ARGEND + if(argc != 1) + usage(); + devid = atoi(*argv); + dev = getdev(devid); + if(dev == nil) + sysfatal("getdev: %r"); + + ser = dev->aux = emallocz(sizeof(Serial), 1); + ser->maxrtrans = ser->maxwtrans = sizeof ser->p[0].data; + ser->maxread = ser->maxwrite = sizeof ser->p[0].data; + ser->dev = dev; + dev->free = serdevfree; + ser->jtag = -1; + ser->nifcs = 1; + + snprint(buf, sizeof buf, "vid %#06x did %#06x", + dev->usb->vid, dev->usb->did); + if(plmatch(buf) == 0){ + ser->hasepintr = 1; + ser->Serialops = plops; + } else if(uconsmatch(buf) == 0) + ser->Serialops = uconsops; + else if(ftmatch(ser, buf) == 0) + ser->Serialops = ftops; + else { + sysfatal("no serial devices found"); + } + for(i = 0; i < ser->nifcs; i++){ + p = &ser->p[i]; + p->interfc = i; + p->s = ser; + if(i == ser->jtag){ + p->isjtag++; + } + if(findendpoints(ser, i) < 0) + sysfatal("no endpoints found for ifc %d", i); + p->w4data = chancreate(sizeof(ulong), 0); + p->gotdata = chancreate(sizeof(ulong), 0); + } + + qlock(ser); + serialreset(ser); + for(i = 0; i < ser->nifcs; i++){ + p = &ser->p[i]; + dprint(2, "serial: valid interface, calling serinit\n"); + if(serinit(p) < 0){ + sysfatal("wserinit: %r"); + } + + dsprint(2, "serial: adding interface %d, %p\n", p->interfc, p); + if(p->isjtag){ + snprint(p->name, sizeof p->name, "jtag"); + dsprint(2, "serial: JTAG interface %d %p\n", i, p); + snprint(p->name, sizeof p->name, "jtag%d.%d", devid, i); + } else { + snprint(p->name, sizeof p->name, "eiaU"); + if(i == 0) + snprint(p->name, sizeof p->name, "eiaU%d", devid); + else + snprint(p->name, sizeof p->name, "eiaU%d.%d", devid, i); + } + fprint(2, "%s...", p->name); + incref(dev); + p->readrend.l = &p->readq; + p->readpid = proccreate(readproc, p, mainstacksize); + ports = realloc(ports, (nports + 1) * sizeof(Serialport*)); + ports[nports++] = p; + } + + qunlock(ser); + if(nports > 0){ + snprint(buf, sizeof buf, "serial-%d", devid); + threadpostsharesrv(&serialfs, nil, "usb", buf); + } +} |