
access to non standard serial port COM3 at i/o port 0x200 causes kernel panic on some machines (Toshiba Sattelite 1415-S115). also, some machines have gameport at 0x200. i readded uartisa to the pcf and pccpuf kernel configurations so one can use plan9.ini to add non standard uarts like: uart2=type=isa port=0x200 irq=5
759 lines
12 KiB
C
759 lines
12 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
|
|
*/
|
|
//static
|
|
Uart*
|
|
uartenable(Uart *p)
|
|
{
|
|
Uart **l;
|
|
|
|
if (up == nil)
|
|
return p; /* too soon; try again later */
|
|
// return nil;
|
|
|
|
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 (up) {
|
|
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;
|
|
|
|
(*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 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 (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 && up)
|
|
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)
|
|
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':
|
|
if(p->enabled){
|
|
ilock(&p->tlock);
|
|
p->xonoff = n;
|
|
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 = malloc(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)
|
|
// n = 0; /* experiment */
|
|
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;
|
|
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);
|
|
}
|
|
uartkick(p); /* keep it moving */
|
|
}
|
|
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);
|
|
}
|
|
}
|