jehanne/sys/src/kern/port/devuart.c

813 lines
13 KiB
C

/* Copyright (C) Charles Forsyth
* See /doc/license/NOTICE.Plan9-9k.txt for details about the licensing.
*/
/* Portions of this file are Copyright (C) 2015-2018 Giacomo Tesio <giacomo@tesio.it>
* See /doc/license/gpl-2.0.txt for details about the licensing.
*/
/* Portions of this file are Copyright (C) 9front's team.
* See /doc/license/9front-mit for details about the licensing.
* See http://git.9front.org/plan9front/plan9front/HEAD/info.html for a list of authors.
*/
#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
{
/* soft flow control chars */
CTLS= 023,
CTLQ= 021,
};
extern Dev uartdevtab;
extern PhysUart* physuart[];
static Uart* uartlist;
static Uart** uart;
static int uartnuart;
static Dirtab *uartdir;
static int uartndir;
static Timer *uarttimer;
struct Uartalloc {
Lock;
Uart *elist; /* list of enabled interfaces */
} uartalloc;
static void uartclock(void);
static void uartflow(void*);
/*
* enable/disable uart and add/remove to list of enabled uarts
*/
Uart*
uartenable(Uart *p)
{
Uart **l;
if(p->enabled)
return p;
if(p->iq == nil){
if((p->iq = qopen(8*1024, Qcoalesce, uartflow, p)) == nil)
return nil;
}
else
qreopen(p->iq);
if(p->oq == nil){
if((p->oq = qopen(8*1024, 0, uartkick, p)) == nil){
qfree(p->iq);
p->iq = nil;
return nil;
}
}
else
qreopen(p->oq);
p->ir = p->istage;
p->iw = p->istage;
p->ie = &p->istage[Stagesize];
p->op = p->ostage;
p->oe = p->ostage;
p->hup_dsr = p->hup_dcd = 0;
p->dsr = p->dcd = 0;
/* assume we can send */
p->cts = 1;
p->ctsbackoff = 0;
if(p->bits == 0)
uartctl(p, "l8");
if(p->stop == 0)
uartctl(p, "s1");
if(p->parity == 0)
uartctl(p, "pn");
if(p->baud == 0)
uartctl(p, "b9600");
(*p->phys->enable)(p, 1);
/*
* use ilock because uartclock can otherwise interrupt here
* and would hang on an attempt to lock uartalloc.
*/
ilock(&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;
iunlock(&uartalloc);
return p;
}
static void
uartdisable(Uart *p)
{
Uart **l;
if(!p->enabled)
return;
(*p->phys->disable)(p);
ilock(&uartalloc);
for(l = &uartalloc.elist; *l; l = &(*l)->elist){
if(*l == p){
*l = p->elist;
break;
}
}
p->enabled = 0;
iunlock(&uartalloc);
}
static Uart*
uartport(char *which)
{
int port;
char *p;
port = strtol(which, &p, 0);
if(p == which)
error(Ebadarg);
if(port < 0 || port >= uartnuart || uart[port] == nil)
error(Enodev);
return uart[port];
}
void
uartmouse(char *which, int (*putc)(Queue*, int), int setb1200)
{
Uart *p;
p = uartport(which);
qlock(&p->ql);;
if(p->opens++ == 0 && uartenable(p) == nil){
qunlock(&p->ql);;
error(Enodev);
}
if(setb1200)
uartctl(p, "b1200");
p->putc = putc;
p->special = 1;
qunlock(&p->ql);;
}
void
uartsetmouseputc(char *which, int (*putc)(Queue*, int))
{
Uart *p;
p = uartport(which);
qlock(&p->ql);;
if(p->opens == 0 || p->special == 0){
qunlock(&p->ql);;
error(Enodev);
}
p->putc = putc;
qunlock(&p->ql);;
}
static void
setlength(int i)
{
Uart *p;
if(i > 0){
p = uart[i];
if(p && p->opens && p->iq)
uartdir[1+3*i].length = qlen(p->iq);
} else for(i = 0; i < uartnuart; i++){
p = uart[i];
if(p && p->opens && p->iq)
uartdir[1+3*i].length = qlen(p->iq);
}
}
/*
* set up the '#t' directory
*/
static void
uartreset(void)
{
int i;
Dirtab *dp;
Uart *p, *tail;
tail = nil;
for(i = 0; physuart[i] != nil; i++){
if(physuart[i]->pnp == nil)
continue;
if((p = physuart[i]->pnp()) == nil)
continue;
if(uartlist != nil)
tail->next = p;
else
uartlist = p;
for(tail = p; tail->next != nil; tail = tail->next)
uartnuart++;
uartnuart++;
}
if(uartnuart)
uart = xalloc(uartnuart*sizeof(Uart*));
uartndir = 1 + 3*uartnuart;
uartdir = xalloc(uartndir * sizeof(Dirtab));
if(uartnuart && uart == nil || uartdir == nil)
panic("uartreset: no memory");
dp = uartdir;
strcpy(dp->name, ".");
mkqid(&dp->qid, 0, 0, QTDIR);
dp->length = 0;
dp->perm = DMDIR|0555;
dp++;
p = uartlist;
for(i = 0; i < uartnuart; i++){
/* 3 directory entries per port */
snprint(dp->name, sizeof dp->name, "eia%d", i);
dp->qid.path = NETQID(i, Ndataqid);
dp->perm = 0660;
dp++;
snprint(dp->name, sizeof dp->name, "eia%dctl", i);
dp->qid.path = NETQID(i, Nctlqid);
dp->perm = 0660;
dp++;
snprint(dp->name, sizeof dp->name, "eia%dstatus", i);
dp->qid.path = NETQID(i, Nstatqid);
dp->perm = 0444;
dp++;
uart[i] = p;
p->dev = i;
if(p->console || p->special){
if(uartenable(p) != nil){
if(p->console){
serialoq = p->oq;
}
p->opens++;
}
}
p = p->next;
}
if(uartnuart){
/*
* at 115200 baud, the 1024 char buffer takes 56 ms to process,
* processing it every 22 ms should be fine.
*/
uarttimer = addclock0link(uartclock, 22);
}
}
static Chan*
uartattach(Chan *c, Chan *ac, char *spec, int flags)
{
return devattach('t', spec);
}
static Walkqid*
uartwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, uartdir, uartndir, devgen);
}
static long
uartstat(Chan *c, uint8_t *dp, long n)
{
if(NETTYPE(c->qid.path) == Ndataqid)
setlength(NETID(c->qid.path));
return devstat(c, dp, n, uartdir, uartndir, devgen);
}
static Chan*
uartopen(Chan *c, unsigned long 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)];
qlock(&p->ql);;
if(p->opens++ == 0 && uartenable(p) == nil){
qunlock(&p->ql);;
c->flag &= ~COPEN;
error(Enodev);
}
qunlock(&p->ql);;
break;
}
c->iounit = qiomaxatomic;
return c;
}
static int
uartdrained(void* arg)
{
Uart *p;
p = arg;
return qlen(p->oq) == 0 && p->op == p->oe;
}
static void
uartdrainoutput(Uart *p)
{
if(!p->enabled)
return;
p->drain = 1;
if(waserror()){
p->drain = 0;
nexterror();
}
sleep(&p->r, uartdrained, p);
poperror();
}
static void
uartclose(Chan *c)
{
Uart *p;
if(c->qid.type & QTDIR)
return;
if((c->flag & COPEN) == 0)
return;
switch(NETTYPE(c->qid.path)){
case Ndataqid:
case Nctlqid:
p = uart[NETID(c->qid.path)];
qlock(&p->ql);;
if(--(p->opens) == 0){
qclose(p->iq);
ilock(&p->rlock);
p->ir = p->iw = p->istage;
iunlock(&p->rlock);
/*
*/
qhangup(p->oq, nil);
if(!waserror()){
uartdrainoutput(p);
poperror();
}
qclose(p->oq);
uartdisable(p);
p->dcd = p->dsr = p->dohup = 0;
}
qunlock(&p->ql);;
break;
}
}
static long
uartread(Chan *c, void *buf, long n, int64_t off)
{
Uart *p;
uint32_t offset = off;
if(c->qid.type & QTDIR){
setlength(-1);
return devdirread(c, buf, n, uartdir, uartndir, devgen);
}
p = uart[NETID(c->qid.path)];
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;
}
int
uartctl(Uart *p, char *cmd)
{
char *f[16];
int i, n, nf;
nf = tokenize(cmd, f, nelem(f));
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':
uartdrainoutput(p);
if((*p->phys->baud)(p, n) < 0)
return -1;
break;
case 'C':
case 'c':
p->hup_dcd = n;
break;
case 'D':
case 'd':
uartdrainoutput(p);
(*p->phys->dtr)(p, n);
break;
case 'E':
case 'e':
p->hup_dsr = n;
break;
case 'f':
case 'F':
if(p->oq != nil)
qflush(p->oq);
break;
case 'H':
case 'h':
if(p->iq != nil)
qhangup(p->iq, 0);
if(p->oq != nil)
qhangup(p->oq, 0);
break;
case 'i':
case 'I':
uartdrainoutput(p);
(*p->phys->fifo)(p, n);
break;
case 'K':
case 'k':
uartdrainoutput(p);
(*p->phys->dobreak)(p, n);
break;
case 'L':
case 'l':
uartdrainoutput(p);
if((*p->phys->bits)(p, n) < 0)
return -1;
break;
case 'm':
case 'M':
uartdrainoutput(p);
(*p->phys->modemctl)(p, n);
break;
case 'n':
case 'N':
if(p->oq != nil)
qnoblock(p->oq, n);
break;
case 'P':
case 'p':
uartdrainoutput(p);
if((*p->phys->parity)(p, *(f[i]+1)) < 0)
return -1;
break;
case 'Q':
case 'q':
if(p->iq != nil)
qsetlimit(p->iq, n);
if(p->oq != nil)
qsetlimit(p->oq, n);
break;
case 'R':
case 'r':
uartdrainoutput(p);
(*p->phys->rts)(p, n);
break;
case 'S':
case 's':
uartdrainoutput(p);
if((*p->phys->stop)(p, n) < 0)
return -1;
break;
case 'W':
case 'w':
if(uarttimer == nil || n < 1)
return -1;
uarttimer->tns = (int64_t)n * 100000LL;
break;
case 'X':
case 'x':
ilock(&p->tlock);
p->xonoff = n;
p->blocked = 0;
iunlock(&p->tlock);
break;
}
}
return 0;
}
static long
uartwrite(Chan *c, void *buf, long n, int64_t _)
{
Uart *p;
char *cmd;
if(c->qid.type & QTDIR)
error(Eperm);
p = uart[NETID(c->qid.path)];
switch(NETTYPE(c->qid.path)){
case Ndataqid:
qlock(&p->ql);;
if(waserror()){
qunlock(&p->ql);;
nexterror();
}
n = qwrite(p->oq, buf, n);
qunlock(&p->ql);;
poperror();
break;
case Nctlqid:
cmd = smalloc(n+1);
memmove(cmd, buf, n);
cmd[n] = 0;
qlock(&p->ql);;
if(waserror()){
qunlock(&p->ql);;
free(cmd);
nexterror();
}
/* let output drain */
if(uartctl(p, cmd) < 0)
error(Ebadarg);
qunlock(&p->ql);;
poperror();
free(cmd);
break;
}
return n;
}
static long
uartwstat(Chan *c, uint8_t *dp, long n)
{
Dir d;
Dirtab *dt;
if(!iseve())
error(Eperm);
if(QTDIR & c->qid.type)
error(Eperm);
if(NETTYPE(c->qid.path) == Nstatqid)
error(Eperm);
dt = &uartdir[1 + 3 * NETID(c->qid.path)];
n = convM2D(dp, n, &d, nil);
if(n == 0)
error(Eshortstat);
if(d.mode != ~0)
dt[0].perm = dt[1].perm = d.mode;
return n;
}
void
uartpower(int on)
{
Uart *p;
for(p = uartlist; p != nil; p = p->next) {
if(p->phys->power)
(*p->phys->power)(p, on);
}
}
Dev uartdevtab = {
't',
"uart",
uartreset,
devinit,
devshutdown,
uartattach,
uartwalk,
uartstat,
uartopen,
devcreate,
uartclose,
uartread,
devbread,
uartwrite,
devbwrite,
devremove,
uartwstat,
uartpower,
};
/*
* 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;
if(p->blocked)
return;
ilock(&p->tlock);
(*p->phys->kick)(p);
iunlock(&p->tlock);
if(p->drain && uartdrained(p)){
p->drain = 0;
wakeup(&p->r);
}
}
/*
* Move data from the interrupt staging area to
* the input Queue.
*/
static void
uartstageinput(Uart *p)
{
int n;
uint8_t *ir, *iw;
while(p->ir != p->iw){
ir = p->ir;
if(p->ir > p->iw){
iw = p->ie;
p->ir = p->istage;
}
else{
iw = p->iw;
p->ir = p->iw;
}
if((n = qproduce(p->iq, ir, iw - ir)) < 0){
p->serr++;
(*p->phys->rts)(p, 0);
}
else if(n == 0)
p->berr++;
}
}
/*
* receive a character at interrupt time
*/
void
uartrecv(Uart *p, char ch)
{
uint8_t *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 if (p->iw) { /* maybe the line isn't enabled yet */
ilock(&p->rlock);
next = p->iw + 1;
if(next == p->ie)
next = p->istage;
if(next == p->ir)
uartstageinput(p);
if(next != p->ir){
*p->iw = ch;
p->iw = next;
}
iunlock(&p->rlock);
}
}
/*
* we save up input characters till clock time to reduce
* per character interrupt overhead.
*/
static void
uartclock(void)
{
Uart *p;
ilock(&uartalloc);
for(p = uartalloc.elist; p; p = p->elist){
/* this hopefully amortizes cost of qproduce to many chars */
if(p->iw != p->ir){
ilock(&p->rlock);
uartstageinput(p);
iunlock(&p->rlock);
}
/* 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);
}
}
iunlock(&uartalloc);
}
/*
* polling console input, output
*/
Uart* consuart;
int
uartgetc(void)
{
if(consuart == nil || consuart->phys->getc == nil)
return -1;
return consuart->phys->getc(consuart);
}
void
uartputc(int c)
{
if(consuart == nil || consuart->phys->putc == nil)
return;
consuart->phys->putc(consuart, c);
}
void
uartputs(char *s, int n)
{
char *e;
if(consuart == nil || consuart->phys->putc == nil)
return;
e = s+n;
for(; s<e; s++){
if(*s == '\n')
consuart->phys->putc(consuart, '\r');
consuart->phys->putc(consuart, *s);
}
}