plan9fox/sys/src/9/port/devuart.c
cinap_lenrek 00542efd15 devuart: allow serial console on late detected uarts
pci uarts are detected late and usually do not contain
the console= parameter logic.

for these, we can just enable them when devuart is reset,
and replay the boot messages once enabled.

this is usefull as it allows us to use these uarts for
kernel debugging in interrupt context.
2022-03-19 15:53:40 +00:00

822 lines
13 KiB
C

#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);
if(p->opens++ == 0 && uartenable(p) == nil){
qunlock(p);
error(Enodev);
}
if(setb1200)
uartctl(p, "b1200");
p->putc = putc;
p->special = 1;
qunlock(p);
}
void
uartsetmouseputc(char *which, int (*putc)(Queue*, int))
{
Uart *p;
p = uartport(which);
qlock(p);
if(p->opens == 0 || p->special == 0){
qunlock(p);
error(Enodev);
}
p->putc = putc;
qunlock(p);
}
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;
/*
* enable serial console for uarts detected
* late during boot (like pci card).
*/
if(consuart == nil){
char *s, *options;
if((s = getconf("console")) != nil
&& strtoul(s, &options, 0) == i
&& options > s){
p->console = 1;
if(*options != '\0')
uartctl(p, options);
}
}
if(p->console || p->special){
if(uartenable(p) != nil){
if(p->console){
if(consuart == nil){
consuart = p;
uartputs(kmesg.buf, kmesg.n);
}
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(char *spec)
{
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 int
uartstat(Chan *c, uchar *dp, int 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, 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)];
qlock(p);
if(p->opens++ == 0 && uartenable(p) == nil){
qunlock(p);
c->flag &= ~COPEN;
error(Enodev);
}
qunlock(p);
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 || up == nil || !islo())
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);
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);
break;
}
}
static long
uartread(Chan *c, void *buf, long n, vlong off)
{
Uart *p;
ulong 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 = (vlong)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, vlong)
{
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);
if(waserror()){
qunlock(p);
nexterror();
}
n = qwrite(p->oq, buf, n);
qunlock(p);
poperror();
break;
case Nctlqid:
cmd = smalloc(n+1);
memmove(cmd, buf, n);
cmd[n] = 0;
qlock(p);
if(waserror()){
qunlock(p);
free(cmd);
nexterror();
}
/* let output drain */
if(uartctl(p, cmd) < 0)
error(Ebadarg);
qunlock(p);
poperror();
free(cmd);
break;
}
return n;
}
static int
uartwstat(Chan *c, uchar *dp, int 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 != ~0UL)
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->enabled || 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;
uchar *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)
{
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 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);
}
}