ref: 254b07675e1a5348f2a8723f55e6b5b1b18a4721
dir: /os/sa1110/devuart.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"
/*
* currently no DMA or flow control (hardware or software)
*/
enum
{
Stagesize= 1024,
Dmabufsize=Stagesize/2,
Nuart=7, /* max per machine */
CTLS= 023,
CTLQ= 021,
};
typedef struct Uart Uart;
struct Uart
{
QLock;
int opens;
int enabled;
int frame; /* framing errors */
int overrun; /* rcvr overruns */
int soverrun; /* software overruns */
int perror; /* parity error */
int bps; /* baud rate */
uchar bits;
char parity;
int inters; /* total interrupt count */
int rinters; /* interrupts due to read */
int winters; /* interrupts due to write */
int rcount; /* total read count */
int wcount; /* total output count */
int xonoff; /* software flow control on */
int blocked; /* output blocked */
/* buffers */
int (*putc)(Queue*, int);
Queue *iq;
Queue *oq;
int port;
UartReg *reg;
/* staging areas to avoid some of the per character costs */
uchar *ip;
uchar *ie;
uchar *op;
uchar *oe;
/* put large buffers last to aid register-offset optimizations: */
char name[KNAMELEN];
uchar istage[Stagesize];
uchar ostage[Stagesize];
};
enum {
UTCR0_PE= 0x01,
UTCR0_OES= 0x02,
UTCR0_SBS= 0x04,
UTCR0_DSS= 0x08,
UTCR0_SCE= 0x10,
UTCR0_RCE= 0x20,
UTCR0_TCE= 0x40,
UTCR3_RXE= 0x01,
UTCR3_TXE= 0x02,
UTCR3_BRK= 0x04,
UTCR3_RIM= 0x08,
UTCR3_TIM= 0x10,
UTCR3_LBM= 0x20,
UTSR0_TFS= 0x01,
UTSR0_RFS= 0x02,
UTSR0_RID= 0x04,
UTSR0_RBB= 0x08,
UTSR0_REB= 0x10,
UTSR0_EIF= 0x20,
UTSR1_TBY= 0x01,
UTSR1_RNE= 0x02,
UTSR1_TNF= 0x04,
UTSR1_PRE= 0x08,
UTSR1_FRE= 0x10,
UTSR1_ROR= 0x20,
};
static Uart *uart[Nuart];
static int nuart;
static int uartspcl;
int redirectconsole;
static void
uartset(Uart *p)
{
UartReg *reg = p->reg;
ulong ocr3;
ulong brdiv;
int n;
brdiv = CLOCKFREQ/16/p->bps - 1;
ocr3 = reg->utcr3;
reg->utcr3 = ocr3&~(UTCR3_RXE|UTCR3_TXE);
reg->utcr1 = brdiv >> 8;
reg->utcr2 = brdiv & 0xff;
/* set PE and OES appropriately for o/e/n: */
reg->utcr0 = ((p->parity&3)^UTCR0_OES)|(p->bits&UTCR0_DSS);
reg->utcr3 = ocr3;
/* set buffer length according to speed, to allow
* at most a 200ms delay before dumping the staging buffer
* into the input queue
*/
n = p->bps/(10*1000/200);
p->ie = &p->istage[n < Stagesize ? n : Stagesize];
}
/*
* send break
*/
static void
uartbreak(Uart *p, int ms)
{
UartReg *reg = p->reg;
if(ms == 0)
ms = 200;
reg->utcr3 |= UTCR3_BRK;
tsleep(&up->sleep, return0, 0, ms);
reg->utcr3 &= ~UTCR3_BRK;
}
/*
* turn on a port
*/
static void
uartenable(Uart *p)
{
UartReg *reg = p->reg;
if(p->enabled)
return;
archuartpower(p->port, 1);
uartset(p);
reg->utsr0 = 0xff; // clear all sticky status bits
// enable receive, transmit, and receive interrupt:
reg->utcr3 = UTCR3_RXE|UTCR3_TXE|UTCR3_RIM;
p->blocked = 0;
p->xonoff = 0;
p->enabled = 1;
}
/*
* turn off a port
*/
static void
uartdisable(Uart *p)
{
p->reg->utcr3 = 0; // disable TX, RX, and ints
p->blocked = 0;
p->xonoff = 0;
p->enabled = 0;
archuartpower(p->port, 0);
}
/*
* put some bytes into the local queue to avoid calling
* qconsume for every character
*/
static int
stageoutput(Uart *p)
{
int n;
Queue *q = p->oq;
if(q == nil)
return 0;
n = qconsume(q, p->ostage, Stagesize);
if(n <= 0)
return 0;
p->op = p->ostage;
p->oe = p->ostage + n;
return n;
}
static void
uartxmit(Uart *p)
{
UartReg *reg = p->reg;
ulong e = 0;
if(!p->blocked) {
while(p->op < p->oe || stageoutput(p)) {
if(reg->utsr1 & UTSR1_TNF) {
reg->utdr = *(p->op++);
p->wcount++;
} else {
e = UTCR3_TIM;
break;
}
}
}
reg->utcr3 = (reg->utcr3&~UTCR3_TIM)|e;
}
static void
uartrecvq(Uart *p)
{
uchar *cp = p->istage;
int n = p->ip - cp;
if(n == 0)
return;
if(p->putc)
while(n-- > 0)
p->putc(p->iq, *cp++);
else if(p->iq)
if(qproduce(p->iq, p->istage, n) < n){
/* if xonoff, should send XOFF when qwindow(p->iq) < threshold */
p->soverrun++;
//print("qproduce flow control");
}
p->ip = p->istage;
}
static void
uartrecv(Uart *p)
{
UartReg *reg = p->reg;
ulong n;
while(reg->utsr1 & UTSR1_RNE) {
int c;
n = reg->utsr1;
c = reg->utdr;
if(n & (UTSR1_PRE|UTSR1_FRE|UTSR1_ROR)) {
if(n & UTSR1_PRE)
p->perror++;
if(n & UTSR1_FRE)
p->frame++;
if(n & UTSR1_ROR)
p->overrun++;
continue;
}
if(p->xonoff){
if(c == CTLS){
p->blocked = 1;
}else if (c == CTLQ){
p->blocked = 0;
}
}
*p->ip++ = c;
if(p->ip >= p->ie)
uartrecvq(p);
p->rcount++;
}
if(reg->utsr0 & UTSR0_RID) {
reg->utsr0 = UTSR0_RID;
uartrecvq(p);
}
}
static void
uartclock(void)
{
Uart *p;
int i;
for(i=0; i<nuart; i++){
p = uart[i];
if(p != nil)
uartrecvq(p);
}
}
static void
uartkick(void *a)
{
Uart *p = a;
int x;
x = splhi();
uartxmit(p);
splx(x);
}
/*
* UART Interrupt Handler
*/
static void
uartintr(Ureg*, void* arg)
{
Uart *p = arg;
UartReg *reg = p->reg;
ulong m = reg->utsr0;
int dokick;
dokick = p->blocked;
p->inters++;
if(m & (UTSR0_RFS|UTSR0_RID|UTSR0_EIF)) {
p->rinters++;
uartrecv(p);
}
if(p->blocked)
dokick = 0;
if((m & UTSR0_TFS) && (reg->utcr3&UTCR3_TIM || dokick)) {
p->winters++;
uartxmit(p);
}
if(m & (UTSR0_RBB|UTSR0_REB)) {
//print("<BREAK>");
/* reg->utsr0 = UTSR0_RBB|UTSR0_REB; */
reg->utsr0 = m & (UTSR0_RBB|UTSR0_REB);
/* what to do? if anything */
}
}
static void
uartsetup(ulong port, char *name)
{
Uart *p;
if(nuart >= Nuart)
return;
p = xalloc(sizeof(Uart));
uart[nuart++] = p;
strcpy(p->name, name);
p->port = port;
p->reg = UARTREG(port);
p->bps = 9600;
p->bits = 8;
p->parity = 'n';
p->iq = qopen(4*1024, 0, 0 , p);
p->oq = qopen(4*1024, 0, uartkick, p);
p->ip = p->istage;
p->ie = &p->istage[Stagesize];
p->op = p->ostage;
p->oe = p->ostage;
if(port == 1)
GPCLKREG->gpclkr0 |= 1; /* SUS=1 for uart on serial 1 */
intrenable(UARTbit(port), uartintr, p, BusCPU, name);
}
static void
uartinstall(void)
{
static int already;
if(already)
return;
already = 1;
uartsetup(3, "eia0");
uartsetup(1, "eia1");
addclock0link(uartclock, 22);
}
/*
* called by main() to configure a duart port as a console or a mouse
*/
void
uartspecial(int port, int bps, char parity, Queue **in, Queue **out, int (*putc)(Queue*, int))
{
Uart *p;
uartinstall();
if(port >= nuart)
return;
p = uart[port];
if(bps)
p->bps = bps;
if(parity)
p->parity = parity;
uartenable(p);
p->putc = putc;
if(in)
*in = p->iq;
if(out)
*out = p->oq;
p->opens++;
uartspcl = 1;
}
Dirtab *uartdir;
int ndir;
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 < nuart; i++){
p = uart[i];
if(p && p->opens && p->iq)
uartdir[1+3*i].length = qlen(p->iq);
}
}
/*
* all uarts must be uartsetup() by this point or inside of uartinstall()
*/
static void
uartreset(void)
{
int i;
Dirtab *dp;
uartinstall();
ndir = 1+3*nuart;
uartdir = xalloc(ndir * sizeof(Dirtab));
dp = uartdir;
strcpy(dp->name, ".");
mkqid(&dp->qid, 0, 0, QTDIR);
dp->length = 0;
dp->perm = DMDIR|0555;
dp++;
for(i = 0; i < nuart; i++){
/* 3 directory entries per port */
strcpy(dp->name, uart[i]->name);
dp->qid.path = NETQID(i, Ndataqid);
dp->perm = 0660;
dp++;
sprint(dp->name, "%sctl", uart[i]->name);
dp->qid.path = NETQID(i, Nctlqid);
dp->perm = 0660;
dp++;
sprint(dp->name, "%sstatus", uart[i]->name);
dp->qid.path = NETQID(i, Nstatqid);
dp->perm = 0444;
dp++;
}
}
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, ndir, 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, ndir, devgen);
}
static Chan*
uartopen(Chan *c, int omode)
{
Uart *p;
c = devopen(c, omode, uartdir, ndir, devgen);
switch(NETTYPE(c->qid.path)){
case Nctlqid:
case Ndataqid:
p = uart[NETID(c->qid.path)];
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.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){
uartdisable(p);
qclose(p->iq);
qclose(p->oq);
p->ip = p->istage;
}
qunlock(p);
break;
}
}
static long
uartstatus(Chan *c, Uart *p, void *buf, long n, long offset)
{
char str[256];
USED(c);
str[0] = 0;
snprint(str, sizeof(str),
"b%d l%d p%c s%d x%d\n"
"opens %d ferr %d oerr %d perr %d baud %d parity %c"
" intr %d rintr %d wintr %d"
" rcount %d wcount %d",
p->bps, p->bits, p->parity, (p->reg->utcr0&UTCR0_SBS)?2:1, p->xonoff,
p->opens, p->frame, p->overrun+p->soverrun, p->perror, p->bps, p->parity,
p->inters, p->rinters, p->winters,
p->rcount, p->wcount);
strcat(str, "\n");
return readstr(offset, buf, n, str);
}
static long
uartread(Chan *c, void *buf, long n, vlong offset)
{
Uart *p;
if(c->qid.type & QTDIR){
setlength(-1);
return devdirread(c, buf, n, uartdir, ndir, 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 uartstatus(c, p, buf, n, offset);
}
return 0;
}
static void
uartctl(Uart *p, char *cmd)
{
int i, n;
/* let output drain for a while (up to 4 secs) */
for(i = 0; i < 200 && (qlen(p->oq) || p->reg->utsr1 & UTSR1_TBY); i++)
tsleep(&up->sleep, return0, 0, 20);
if(strncmp(cmd, "break", 5) == 0){
uartbreak(p, 0);
return;
}
n = atoi(cmd+1);
switch(*cmd){
case 'B':
case 'b':
if(n <= 0)
error(Ebadarg);
p->bps = n;
uartset(p);
break;
case 'f':
case 'F':
qflush(p->oq);
break;
case 'H':
case 'h':
qhangup(p->iq, 0);
qhangup(p->oq, 0);
break;
case 'L':
case 'l':
if(n < 7 || n > 8)
error(Ebadarg);
p->bits = n;
uartset(p);
break;
case 'n':
case 'N':
qnoblock(p->oq, n);
break;
case 'P':
case 'p':
p->parity = *(cmd+1);
uartset(p);
break;
case 'K':
case 'k':
uartbreak(p, n);
break;
case 'Q':
case 'q':
qsetlimit(p->iq, n);
qsetlimit(p->oq, n);
break;
case 'X':
case 'x':
p->xonoff = n;
break;
}
}
static long
uartwrite(Chan *c, void *buf, long n, vlong offset)
{
Uart *p;
char cmd[32];
USED(offset);
if(c->qid.type & QTDIR)
error(Eperm);
p = uart[NETID(c->qid.path)];
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;
default:
error(Egreg);
return 0;
}
}
static int
uartwstat(Chan *c, uchar *dp, int n)
{
Dir d;
Dirtab *dt;
if(!iseve())
error(Eperm);
if(c->qid.type & QTDIR)
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(d.mode != ~0UL){
d.mode &= 0666;
dt[0].perm = dt[1].perm = d.mode;
}
return n;
}
void
uartpower(int on)
{
Uart *p;
int i;
for(i=0; i<nuart; i++){
p = uart[i];
if(p != nil && p->opens){
if(on && !p->enabled){
p->enabled = 0;
uartenable(p);
uartkick(p);
}else{
if(p->port != 3) /* leave the console */
uartdisable(p);
p->enabled = 0;
}
}
}
}
Dev uartdevtab = {
't',
"uart",
uartreset,
devinit,
devshutdown,
uartattach,
uartwalk,
uartstat,
uartopen,
devcreate,
uartclose,
uartread,
devbread,
uartwrite,
devbwrite,
devremove,
uartwstat,
uartpower,
};
/*
* for use by iprint
*/
void
uartputc(int c)
{
UartReg *r;
if(!uartspcl && !redirectconsole)
return;
if(c == 0)
return;
r = UARTREG(3);
while((r->utsr1 & UTSR1_TNF) == 0)
{}
r->utdr = c;
if(c == '\n')
while(r->utsr1 & UTSR1_TBY) /* flush xmit fifo */
{}
}
void
uartputs(char *data, int len)
{
int s;
if(!uartspcl && !redirectconsole)
return;
clockpoll();
s = splfhi();
while(--len >= 0){
if(*data == '\n')
uartputc('\r');
uartputc(*data++);
}
splx(s);
}
/*
* for use by debugger
*/
int
uartgetc(void)
{
UartReg *r;
if(!uartspcl)
return -1;
clockcheck();
r = UARTREG(3);
while(!(r->utsr1 & UTSR1_RNE))
clockcheck();
return r->utdr;
}