#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "../port/netif.h" enum { Nuart = 4, /* soft flow control chars */ CTLS= 023, CTLQ= 021, }; static Uart* uart[Nuart]; static int nuart; static Dirtab *uartdir; static int uartndir; /* * means the kernel is using this as a console */ static char Ekinuse[] = "device in use by kernel"; struct Uartalloc { Lock; Uart *elist; /* list of enabled interfaces */ } uartalloc; static void uartdcdhup(Uart*, int); static void uartdcdts(Uart*, int); static void uartdsrhup(Uart*, int); static void uartenable(Uart*); static void uartdisable(Uart*); static void uartclock(void); static void uartflow(void*); /* * define a Uart */ Uart* uartsetup(PhysUart *phys, void *regs, ulong freq, char *name) { Uart *p; if(nuart >= Nuart) return nil; p = xalloc(sizeof(Uart)); memset(p, 0, sizeof(*p)); uart[nuart] = p; strcpy(p->name, name); p->dev = nuart++; p->freq = freq; p->regs = regs; p->phys = phys; /* * set rate to 115200 baud. * 8 bits/character. * 1 stop bit. * enabled with interrupts disabled. */ (*p->phys->bits)(p, 8); (*p->phys->stop)(p, 1); (*p->phys->baud)(p, 115200); (*p->phys->enable)(p, 0); p->iq = qopen(4*1024, 0, uartflow, p); p->oq = qopen(4*1024, 0, uartkick, p); if(p->iq == nil || p->oq == nil) panic("uartsetup"); p->ir = p->istage; p->iw = p->istage; p->ie = &p->istage[Stagesize]; p->op = p->ostage; p->oe = p->ostage; return p; } /* * enable/diable uart and add/remove to list of enabled uarts */ static void uartenable(Uart *p) { Uart **l; p->hup_dsr = p->hup_dcd = 0; p->dsr = p->dcd = 0; /* assume we can send */ p->cts = 1; (*p->phys->enable)(p, 1); lock(&uartalloc); for(l = &uartalloc.elist; *l; l = &(*l)->elist){ if(*l == p) break; } if(*l == 0){ p->elist = uartalloc.elist; uartalloc.elist = p; } p->enabled = 1; unlock(&uartalloc); } static void uartdisable(Uart *p) { Uart **l; (*p->phys->disable)(p); lock(&uartalloc); for(l = &uartalloc.elist; *l; l = &(*l)->elist){ if(*l == p){ *l = p->elist; break; } } p->enabled = 0; unlock(&uartalloc); } static void setlength(int i) { Uart *p; if(i > 0){ p = uart[i]; if(p && p->opens && p->iq) uartdir[3*i].length = qlen(p->iq); } else for(i = 0; i < nuart; i++){ p = uart[i]; if(p && p->opens && p->iq) uartdir[3*i].length = qlen(p->iq); } } /* * setup the '#t' directory */ static void uartreset(void) { int i; Dirtab *dp; uartndir = 3*nuart; uartdir = xalloc(uartndir * sizeof(Dirtab)); dp = uartdir; for(i = 0; i < nuart; i++){ /* 3 directory entries per port */ sprint(dp->name, "eia%d", i); dp->qid.path = NETQID(i, Ndataqid); dp->perm = 0660; dp++; sprint(dp->name, "eia%dctl", i); dp->qid.path = NETQID(i, Nctlqid); dp->perm = 0660; dp++; sprint(dp->name, "eia%dstat", i); dp->qid.path = NETQID(i, Nstatqid); dp->perm = 0444; dp++; } addclock0link(uartclock); } static Chan* uartattach(char *spec) { return devattach('t', spec); } static int uartwalk(Chan *c, char *name) { return devwalk(c, name, uartdir, uartndir, devgen); } static void uartstat(Chan *c, char *dp) { if(NETTYPE(c->qid.path) == Ndataqid) setlength(NETID(c->qid.path)); devstat(c, dp, uartdir, uartndir, devgen); } static Chan* uartopen(Chan *c, int omode) { Uart *p; c = devopen(c, omode, uartdir, uartndir, devgen); switch(NETTYPE(c->qid.path)){ case Nctlqid: case Ndataqid: p = uart[NETID(c->qid.path)]; if(p->kinuse) error(Ekinuse); qlock(p); if(p->opens++ == 0){ uartenable(p); qreopen(p->iq); qreopen(p->oq); } qunlock(p); break; } return c; } static void uartclose(Chan *c) { Uart *p; if(c->qid.path & CHDIR) return; if((c->flag & COPEN) == 0) return; switch(NETTYPE(c->qid.path)){ case Ndataqid: case Nctlqid: p = uart[NETID(c->qid.path)]; if(p->kinuse) error(Ekinuse); qlock(p); if(--(p->opens) == 0){ uartdisable(p); qclose(p->iq); qclose(p->oq); p->ir = p->iw = p->istage; p->dcd = p->dsr = p->dohup = 0; } qunlock(p); break; } } static long uartread(Chan *c, void *buf, long n, vlong off) { Uart *p; ulong offset = off; if(c->qid.path & CHDIR){ setlength(-1); return devdirread(c, buf, n, uartdir, uartndir, devgen); } p = uart[NETID(c->qid.path)]; if(p->kinuse) error(Ekinuse); switch(NETTYPE(c->qid.path)){ case Ndataqid: return qread(p->iq, buf, n); case Nctlqid: return readnum(offset, buf, n, NETID(c->qid.path), NUMSIZE); case Nstatqid: return (*p->phys->status)(p, buf, n, offset); } return 0; } static void uartctl(Uart *p, char *cmd) { int i, n; char *f[32]; int nf; /* let output drain for a while */ for(i = 0; i < 16 && qlen(p->oq); i++) tsleep(&p->r, (int(*)(void*))qlen, p->oq, 125); nf = getfields(cmd, f, nelem(f), 1, " \t\n"); for(i = 0; i < nf; i++){ if(strncmp(f[i], "break", 5) == 0){ (*p->phys->dobreak)(p, 0); continue; } n = atoi(f[i]+1); switch(*f[i]){ case 'B': case 'b': (*p->phys->baud)(p, n); break; case 'C': case 'c': uartdcdhup(p, n); break; case 'D': case 'd': (*p->phys->dtr)(p, n); break; case 'E': case 'e': uartdsrhup(p, n); break; case 'f': case 'F': qflush(p->oq); break; case 'H': case 'h': qhangup(p->iq, 0); qhangup(p->oq, 0); break; case 'i': case 'I': break; case 'L': case 'l': (*p->phys->bits)(p, n); break; case 'm': case 'M': (*p->phys->modemctl)(p, n); break; case 'n': case 'N': qnoblock(p->oq, n); break; case 'P': case 'p': (*p->phys->parity)(p, *(cmd+1)); break; case 'K': case 'k': (*p->phys->dobreak)(p, n); break; case 'R': case 'r': (*p->phys->rts)(p, n); break; case 'Q': case 'q': qsetlimit(p->iq, n); qsetlimit(p->oq, n); break; case 'T': case 't': uartdcdts(p, n); break; case 'W': case 'w': /* obsolete */ break; case 'X': case 'x': ilock(&p->tlock); p->xonoff = n; iunlock(&p->tlock); break; } } } static long uartwrite(Chan *c, void *buf, long n, vlong) { Uart *p; char cmd[32]; if(c->qid.path & CHDIR) error(Eperm); p = uart[NETID(c->qid.path)]; if(p->kinuse) error(Ekinuse); switch(NETTYPE(c->qid.path)){ case Ndataqid: return qwrite(p->oq, buf, n); case Nctlqid: if(n >= sizeof(cmd)) n = sizeof(cmd)-1; memmove(cmd, buf, n); cmd[n] = 0; uartctl(p, cmd); return n; } } static void uartwstat(Chan *c, char *dp) { Dir d; Dirtab *dt; if(!iseve()) error(Eperm); if(CHDIR & c->qid.path) error(Eperm); if(NETTYPE(c->qid.path) == Nstatqid) error(Eperm); dt = &uartdir[3 * NETID(c->qid.path)]; convM2D(dp, &d); d.mode &= 0666; dt[0].perm = dt[1].perm = d.mode; } Dev uartdevtab = { 't', "uart", uartreset, devinit, uartattach, devclone, uartwalk, uartstat, uartopen, devcreate, uartclose, uartread, devbread, uartwrite, devbwrite, devremove, uartwstat, }; /* * decide if we should hangup when dsr or dcd drops. */ static void uartdsrhup(Uart *p, int n) { p->hup_dsr = n; } static void uartdcdhup(Uart *p, int n) { p->hup_dcd = n; } /* * save dcd timestamps for gps clock */ static void uartdcdts(Uart *p, int n) { p->dcdts = n; } /* * restart input if it's off */ static void uartflow(void *v) { Uart *p; p = v; if(p->modem) (*p->phys->rts)(p, 1); } /* * put some bytes into the local queue to avoid calling * qconsume for every character */ int uartstageoutput(Uart *p) { int n; n = qconsume(p->oq, p->ostage, Stagesize); if(n <= 0) return 0; p->op = p->ostage; p->oe = p->ostage + n; return n; } /* * restart output */ void uartkick(void *v) { Uart *p = v; ilock(&p->tlock); (*p->phys->kick)(p); iunlock(&p->tlock); } /* * streceiveage a character at interrupt time */ void uartrecv(Uart *p, char ch) { uchar *next; /* software flow control */ if(p->xonoff){ if(ch == CTLS){ p->blocked = 1; }else if (ch == CTLQ){ p->blocked = 0; p->ctsbackoff = 2; /* clock gets output going again */ } } /* receive the character */ if(p->putc) p->putc(p->iq, ch); else { next = p->iw + 1; if(next == p->ie) next = p->istage; if(next != p->ir){ *p->iw = ch; p->iw = next; } } } /* * we save up input characters till clock time to reduce * per character interrupt overhead. */ static void uartclock(void) { Uart *p; uchar *iw; for(p = uartalloc.elist; p; p = p->elist){ /* this amortizes cost of qproduce to many chars */ if(p->iw != p->ir){ iw = p->iw; if(iw < p->ir){ if(qproduce(p->iq, p->ir, p->ie-p->ir) < 0) (*p->phys->rts)(p, 0); p->ir = p->istage; } if(qproduce(p->iq, p->ir, iw-p->ir) < 0) (*p->phys->rts)(p, 0); p->ir = iw; } /* hang up if requested */ if(p->dohup){ qhangup(p->iq, 0); qhangup(p->oq, 0); p->dohup = 0; } /* this adds hysteresis to hardware/software flow control */ if(p->ctsbackoff){ ilock(&p->tlock); if(p->ctsbackoff){ if(--(p->ctsbackoff) == 0) (*p->phys->kick)(p); } iunlock(&p->tlock); } } } /* * configure a uart port as a console or a mouse */ void uartspecial(Uart *p, int baud, Queue **in, Queue **out, int (*putc)(Queue*, int)) { uartenable(p); if(baud) (*p->phys->baud)(p, baud); p->putc = putc; if(in) *in = p->iq; if(out) *out = p->oq; p->opens++; }