summaryrefslogtreecommitdiff
path: root/os/ks32/devuart.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/ks32/devuart.c')
-rw-r--r--os/ks32/devuart.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/os/ks32/devuart.c b/os/ks32/devuart.c
new file mode 100644
index 00000000..2251dd59
--- /dev/null
+++ b/os/ks32/devuart.c
@@ -0,0 +1,719 @@
+#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)
+ */
+
+/*
+ * problems fixed from previous vsn:
+ *
+ * - no kick on queue's, so redirections weren't getting
+ * started until the clock tick
+ *
+ * - lots of unnecessary overhead
+ *
+ * - initialization sequencing
+ *
+ * - uart[n] no longer indexed before calling uartinstall()
+ */
+#define DEBUG if(0)iprint
+
+static void uartintr(Ureg*, void*);
+
+enum
+{
+ Stagesize= 1024,
+ Dmabufsize=Stagesize/2,
+ Nuart=7, /* max per machine */
+};
+
+typedef struct Uart Uart;
+struct Uart
+{
+ QLock;
+
+ int opens;
+
+ int enabled;
+
+ int port; /* 0 or 1 */
+ int kickme; /* for kick */
+ int frame; /* framing errors */
+ int overrun; /* rcvr overruns */
+ int perror; /* parity error */
+ int bps; /* baud rate */
+ uchar bits;
+ char parity;
+ uchar stop;
+
+ 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 */
+
+ /* buffers */
+ int (*putc)(Queue*, int);
+ Queue *iq;
+ Queue *oq;
+
+ 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];
+};
+
+#define UCON_ENABLEMASK (UCON_RXMDMASK | UCON_TXMDMASK | UCON_SINTMASK)
+#define UCON_ENABLESET (UCON_RXMDINT | UCON_TXMDINT | UCON_SINTON)
+#define UCON_DISABLESET (UCON_RXMDOFF | UCON_TXMDOFF | UCON_SINTOFF)
+
+static Uart *uart[Nuart];
+static int nuart;
+
+static uchar
+readstatus(Uart *p)
+{
+ UartReg *reg = p->reg;
+ uchar stat = reg->stat;
+ if (stat & USTAT_OV)
+ p->overrun++;
+ if (stat & USTAT_PE)
+ p->perror++;
+ if (stat & USTAT_FE)
+ p->frame++;
+ return stat;
+}
+
+static void
+uartset(Uart *p)
+{
+ UartReg *reg = p->reg;
+ ulong denom;
+ ulong brdiv;
+ int n;
+ uchar lcon;
+
+ lcon= ULCON_CLOCKMCLK | ULCON_IROFF;
+ lcon |= ULCON_WL5 + (p->bits - 5);
+ lcon |= p->stop == 1 ? ULCON_STOP1 : ULCON_STOP2;
+ switch (p->parity) {
+ default:
+ case 'n':
+ lcon |= ULCON_PMDNONE;
+ break;
+ case 'o':
+ lcon |= ULCON_PMDODD;
+ break;
+ case 'e':
+ lcon |= ULCON_PMDEVEN;
+ break;
+ }
+ reg->lcon = lcon;
+
+ /* clear the break and loopback bits; leave everything else alone */
+ reg->con = (reg->con & ~(UCON_BRKMASK | UCON_LOOPMASK)) | UCON_BRKOFF | UCON_LOOPOFF;
+
+ denom = 2 * 16 * p->bps;
+ brdiv = (TIMER_HZ + denom / 2) / denom - 1;
+ reg->brdiv = brdiv << 4;
+
+ /* 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->con |= UCON_BRKON;
+ tsleep(&up->sleep, return0, 0, ms);
+ reg->con &= ~UCON_BRKON;
+}
+
+/*
+ * turn on a port
+ */
+static void
+uartenable(Uart *p)
+{
+ UartReg *reg = p->reg;
+
+ if(p->enabled)
+ return;
+
+ uartset(p);
+ // enable receive, transmit, and receive interrupt:
+ reg->con = (reg->con & UCON_ENABLEMASK) | UCON_ENABLESET;
+ p->enabled = 1;
+}
+
+/*
+ * turn off a port
+ */
+static void
+uartdisable(Uart *p)
+{
+ p->reg->con = (p->reg->con & UCON_ENABLEMASK) | UCON_DISABLESET;
+ p->enabled = 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)
+ 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 gag = 1;
+ while(p->op < p->oe || stageoutput(p)) {
+ if(readstatus(p) & USTAT_TBE) {
+ DEBUG("T");
+ reg->txbuf = *(p->op++);
+ p->wcount++;
+ } else {
+ DEBUG("F");
+ gag = 0;
+ break;
+ }
+ }
+ if (gag) {
+ DEBUG("G");
+ p->kickme = 1;
+ intrmask(UARTTXbit(p->port), 0);
+ }
+}
+
+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)
+ print("qproduce flow control");
+ p->ip = p->istage;
+}
+
+static void
+uartrecv(Uart *p)
+{
+ UartReg *reg = p->reg;
+ uchar stat = readstatus(p);
+
+DEBUG("R");
+ if (stat & USTAT_RDR) {
+ int c;
+ c = reg->rxbuf;
+ if (c == '?') {
+ DEBUG("mod 0x%.8lx\n", INTREG->mod);
+ DEBUG("msk 0x%.8lx\n", INTREG->msk);
+ DEBUG("pnd 0x%.8lx\n", INTREG->pnd);
+ }
+ *p->ip++ = c;
+/* if(p->ip >= p->ie) */
+ uartrecvq(p);
+ p->rcount++;
+ }
+}
+
+static void
+uartkick(void *a)
+{
+ Uart *p = a;
+ int x = splhi();
+ DEBUG("k");
+ if (p->kickme) {
+ p->kickme = 0;
+ DEBUG("K");
+ intrunmask(UARTTXbit(p->port), 0);
+ }
+ splx(x);
+}
+
+/*
+ * UART Interrupt Handler
+ */
+static void
+uarttxintr(Ureg*, void* arg)
+{
+ Uart *p = arg;
+ intrclear(UARTTXbit(p->port), 0);
+ p->inters++;
+ p->winters++;
+ uartxmit(p);
+}
+
+static void
+uartrxintr(Ureg*, void* arg)
+{
+ Uart *p = arg;
+ intrclear(UARTRXbit(p->port), 0);
+ p->inters++;
+ p->rinters++;
+ uartrecv(p);
+}
+
+
+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->reg = &UARTREG[port];
+ p->bps = 9600;
+ p->bits = 8;
+ p->parity = 'n';
+ p->stop = 1;
+ p->kickme = 0;
+ p->port = port;
+
+ 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;
+
+ intrenable(UARTTXbit(port), uarttxintr, p, 0);
+ intrenable(UARTRXbit(port), uartrxintr, p, 0);
+}
+
+static void
+uartinstall(void)
+{
+ static int already;
+
+ if(already)
+ return;
+ already = 1;
+
+ uartsetup(0, "eia0");
+// uartsetup(1, "eia1");
+}
+
+/*
+ * 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++;
+}
+
+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;
+ sprint(str, "opens %d ferr %d oerr %d perr %d baud %d parity %c"
+ " intr %d rintr %d wintr %d"
+ " rcount %d wcount %d",
+ p->opens, p->frame, p->overrun, 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) || (readstatus(p) & USTAT_TC) == 0); 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 's':
+ case 'S':
+ if(n < 1 || n > 2)
+ error(Ebadarg);
+ p->stop = n;
+ uartset(p);
+ 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;
+ }
+}
+
+static int
+uartwstat(Chan *c, uchar *dp, int n)
+{
+ error(Eperm);
+ return 0;
+#ifdef xxx
+ 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[3 * NETID(c->qid.path)];
+ convM2D(dp, &d);
+ d.mode &= 0666;
+ dt[0].perm = dt[1].perm = d.mode;
+#endif
+}
+
+Dev uartdevtab = {
+ 't',
+ "uart",
+
+ uartreset,
+ devinit,
+ devshutdown,
+ uartattach,
+ uartwalk,
+ uartstat,
+ uartopen,
+ devcreate,
+ uartclose,
+ uartread,
+ devbread,
+ uartwrite,
+ devbwrite,
+ devremove,
+ uartwstat,
+};
+
+void
+uartputc(int c)
+{
+ UartReg *u;
+
+ if (!c)
+ return;
+ u = &UARTREG[1];
+ while ((u->stat & USTAT_TBE) == 0)
+ ;
+ u->txbuf = c;
+ if (c == '\n')
+ while((u->stat & USTAT_TC) == 0) /* flush xmit fifo */
+ ;
+}
+
+void
+uartputs(char *data, int len)
+{
+ int x;
+
+ clockpoll();
+ x = splfhi();
+ while (len--){
+ if(*data == '\n')
+ uartputc('\r');
+ uartputc(*data++);
+ }
+ splx(x);
+}
+
+int
+uartgetc(void)
+{
+ UartReg *u;
+
+ clockcheck();
+ u = &UARTREG[1];
+ while((u->stat & USTAT_RDR) == 0)
+ clockcheck();
+ return u->rxbuf;
+}