Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * 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 "usb.h"
#include "usbfs.h"
#include "serial.h"
#include "prolific.h"
#include "ucons.h"
#include "ftdi.h"
#include "silabs.h"

int serialdebug;

enum {
        /* Qids. Maintain order (relative to dirtabs structs) */
        Qroot   = 0,
        Qctl,
        Qdata,
        Qmax,
};

typedef struct Dirtab Dirtab;
struct Dirtab {
        char    *name;
        int     mode;
};

static Dirtab dirtab[] = {
        [Qroot] "/",            DMDIR|0555,
        [Qdata] "%s",           0660,
        [Qctl]  "%sctl",        0664,
};

static int sdebug;

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];
                usbfsdel(&p->fs);
                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 int
dwalk(Usbfs *fs, Fid *fid, char *name)
{
        int i;
        char *dname;
        Qid qid;
        Serialport *p;

        qid = fid->qid;
        if((qid.type & QTDIR) == 0){
                werrstr("walk in non-directory");
                return -1;
        }

        if(strcmp(name, "..") == 0){
                /* must be /eiaU%d; i.e. our root dir. */
                fid->qid.path = Qroot | fs->qid;
                fid->qid.vers = 0;
                fid->qid.type = QTDIR;
                return 0;
        }

        p = fs->aux;
        for(i = 1; i < nelem(dirtab); i++){
                dname = smprint(dirtab[i].name, p->name);
                if(strcmp(name, dname) == 0){
                        qid.path = i | fs->qid;
                        qid.vers = 0;
                        qid.type = dirtab[i].mode >> 24;
                        fid->qid = qid;
                        free(dname);
                        return 0;
                } else
                        free(dname);
        }
        werrstr(Enotfound);
        return -1;
}

static void
dostat(Usbfs *fs, int path, Dir *d)
{
        Dirtab *t;
        Serialport *p;

        t = &dirtab[path];
        d->qid.path = path;
        d->qid.type = t->mode >> 24;
        d->mode = t->mode;
        p = fs->aux;

        if(strcmp(t->name, "/") == 0)
                d->name = t->name;
        else
                snprint(d->name, Namesz, t->name, p->fs.name);
        d->length = 0;
}

static int
dstat(Usbfs *fs, Qid qid, Dir *d)
{
        int path;

        path = qid.path & ~fs->qid;
        dostat(fs, path, d);
        d->qid.path |= fs->qid;
        return 0;
}

static int
dopen(Usbfs *fs, Fid *fid, int)
{
        ulong path;
        Serialport *p;

        path = fid->qid.path & ~fs->qid;
        p = fs->aux;
        switch(path){           /* BUG: unneeded? */
        case Qdata:
                dsprint(2, "serial, opened data\n");
                break;
        case Qctl:
                dsprint(2, "serial, opened ctl\n");
                if(p->isjtag)
                        return 0;
                serialctl(p, "l8 i1");  /* default line parameters */
                break;
        }
        return 0;
}


static void
filldir(Usbfs *fs, Dir *d, Dirtab *tab, int i, void *v)
{
        Serialport *p;

        p = v;
        d->qid.path = i | fs->qid;
        d->mode = tab->mode;
        if((d->mode & DMDIR) != 0)
                d->qid.type = QTDIR;
        else
                d->qid.type = QTFILE;
        sprint(d->name, tab->name, p->name);    /* hope it fits */
}

static int
dirgen(Usbfs *fs, Qid, int i, Dir *d, void *p)
{
        i++;                            /* skip root */
        if(i >= nelem(dirtab))
                return -1;
        filldir(fs, d, &dirtab[i], i, p);
        return 0;
}

enum {
        Serbufsize      = 256,
};

static long
dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
{
        int dfd;
        long rcount;
        ulong path;
        char *e, *buf, *err;    /* change */
        Qid q;
        Serialport *p;
        Serial *ser;
        static int errrun, good;

        q = fid->qid;
        path = fid->qid.path & ~fs->qid;
        p = fs->aux;
        ser = p->s;

        buf = emallocz(Serbufsize, 1);
        err = emallocz(Serbufsize, 1);
        qlock(ser);
        switch(path){
        case Qroot:
                count = usbdirread(fs, q, data, count, offset, dirgen, p);
                break;
        case Qdata:
                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->fs.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);
                count = rcount;
                break;
        case Qctl:
                if(offset != 0 || p->isjtag)
                        count = 0;
                else {
                        e = serdumpst(p, buf, Serbufsize);
                        count = usbreadbuf(data, count, 0, buf, e - buf);
                }
                break;
        }
        if(count >= 0)
                ser->recover = 0;
        qunlock(ser);
        free(err);
        free(buf);
        return count;
}

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 long
dwrite(Usbfs *fs, Fid *fid, void *buf, long count, vlong)
{
        ulong path;
        char *cmd;
        Serialport *p;
        Serial *ser;

        p = fs->aux;
        ser = p->s;
        path = fid->qid.path & ~fs->qid;

        qlock(ser);
        switch(path){
        case Qdata:
                count = altwrite(p, (uchar *)buf, count);
                break;
        case Qctl:
                if(p->isjtag)
                        break;
                cmd = emallocz(count+1, 1);
                memmove(cmd, buf, count);
                cmd[count] = 0;
                if(serialctl(p, cmd) < 0){
                        qunlock(ser);
                        werrstr(Ebadctl);
                        free(cmd);
                        return -1;
                }
                free(cmd);
                break;
        default:
                qunlock(ser);
                werrstr(Eperm);
                return -1;
        }
        if(count >= 0)
                ser->recover = 0;
        else
                serialrecover(ser, p, p->epout, "writing");
        qunlock(ser);
        return count;
}

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;
        }
        if(epout == epin){
                incref(p->epin);
                p->epout = p->epin;
        }else
                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);
        if(p->epin == p->epout)
                opendevdata(p->epin, ORDWR);
        else{
                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 < Niface; 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 || ep->dir == Eboth) && epin == -1)
                                epin = ep->id;
                        if((ep->dir == Eout || ep->dir == Eboth) && 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 int
usage(void)
{
        werrstr("usage: usb/serial [-dD] [-m mtpt] [-s srv]");
        return -1;
}

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 Usbfs serialfs = {
        .walk = dwalk,
        .open = dopen,
        .read = dread,
        .write= dwrite,
        .stat = dstat,
};

static void
serialfsend(Usbfs *fs)
{
        Serialport *p;

        p = fs->aux;

        if(p->w4data != nil)
                chanclose(p->w4data);
        if(p->gotdata != nil)
                chanclose(p->gotdata);
        if(p->readc)
                chanclose(p->readc);
}

int
serialmain(Dev *dev, int argc, char* argv[])
{
        Serial *ser;
        Serialport *p;
        char buf[50];
        int i, devid;

        devid = dev->id;
        ARGBEGIN{
        case 'd':
                serialdebug++;
                break;
        case 'N':
                devid = atoi(EARGF(usage()));
                break;
        default:
                return usage();
        }ARGEND
        if(argc != 0)
                return usage();

        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 if(slmatch(buf) == 0)
                ser->Serialops = slops;
        else {
                werrstr("serial: no serial devices found");
                return -1;
        }
        for(i = 0; i < ser->nifcs; i++){
                p = &ser->p[i];
                p->interfc = i;
                p->s = ser;
                p->fs = serialfs;
                if(i == ser->jtag){
                        p->isjtag++;
                }
                if(findendpoints(ser, i) < 0){
                        werrstr("serial: no endpoints found for ifc %d", i);
                        return -1;
                }
                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){
                        dprint(2, "serial: serinit: %r\n");
                        return -1;
                }

                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->fs.name, sizeof p->fs.name, "jtag%d.%d", devid, i);
                } else {
                        snprint(p->name, sizeof p->name, "eiaU");
                        if(i == 0)
                                snprint(p->fs.name, sizeof p->fs.name, "eiaU%d", devid);
                        else
                                snprint(p->fs.name, sizeof p->fs.name, "eiaU%d.%d", devid, i);
                }
                fprint(2, "%s...", p->fs.name);
                p->fs.dev = dev;
                incref(dev);
                p->fs.aux = p;
                p->fs.end = serialfsend;
                usbfsadd(&p->fs);
        }

        qunlock(ser);
        return 0;
}