From 923766f2b968ed8a3ad74fce4e83fc7989bb93e4 Mon Sep 17 00:00:00 2001 From: David du Colombier <0intro@gmail.com> Date: Sat, 1 Jun 1991 00:00:00 +0000 Subject: [PATCH] Plan 9 from Bell Labs 1991-06-01 --- port/devscc.c | 707 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 707 insertions(+) create mode 100644 port/devscc.c diff --git a/port/devscc.c b/port/devscc.c new file mode 100644 index 0000000000000000000000000000000000000000..2750857a2017431f6c2840705e8f042061e580ae --- /dev/null +++ b/port/devscc.c @@ -0,0 +1,707 @@ +#include "u.h" +#include "lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "errno.h" + +#include "devtab.h" + +/* + * Driver for the Z8530. + */ +enum +{ + /* wr 0 */ + ResExtPend= 2<<3, + ResTxPend= 5<<3, + ResErr= 6<<3, + + /* wr 1 */ + ExtIntEna= 1<<0, + TxIntEna= 1<<1, + RxIntDis= 0<<3, + RxIntFirstEna= 1<<3, + RxIntAllEna= 2<<3, + + /* wr 3 */ + RxEna= 1, + Rx5bits= 0<<6, + Rx7bits= 1<<6, + Rx6bits= 2<<6, + Rx8bits= 3<<6, + + /* wr 4 */ + SyncMode= 0<<2, + Rx1stop= 1<<2, + Rx1hstop= 2<<2, + Rx2stop= 3<<2, + X16= 1<<6, + + /* wr 5 */ + TxRTS= 1<<1, + TxEna= 1<<3, + TxBreak= 1<<4, + TxDTR= 1<<7, + Tx5bits= 0<<5, + Tx7bits= 1<<5, + Tx6bits= 2<<5, + Tx8bits= 3<<5, + + /* wr 9 */ + IntEna= 1<<3, + ResetB= 1<<6, + ResetA= 2<<6, + HardReset= 3<<6, + + /* wr 11 */ + TRxCOutBR= 2, + TxClockBR= 2<<3, + RxClockBR= 2<<5, + TRxCOI= 1<<2, + + /* wr 14 */ + BREna= 1, + BRSource= 2, + + /* rr 0 */ + RxReady= 1, + TxReady= 1<<2, + RxDCD= 1<<3, + RxCTS= 1<<5, + RxBreak= 1<<7, + + /* rr 3 */ + ExtPendB= 1, + TxPendB= 1<<1, + RxPendB= 1<<2, + ExtPendA= 1<<3, + TxPendA= 1<<4, + RxPendA= 1<<5, +}; + +typedef struct SCC SCC; +struct SCC +{ + QLock; + ushort sticky[16]; /* sticky write register values */ + uchar *ptr; /* command/pointer register in Z8530 */ + uchar *data; /* data register in Z8530 */ + int printing; /* true if printing */ + + /* console interface */ + int nostream; /* can't use the stream interface */ + IOQ *iq; /* input character queue */ + IOQ *oq; /* output character queue */ + + /* stream interface */ + Queue *wq; /* write queue */ + Rendez r; /* kproc waiting for input */ + Alarm *a; /* alarm for waking the kernel process */ + int delay; /* between character input and waking kproc */ + int kstarted; /* kproc started */ + uchar delim[256]; /* characters that act as delimiters */ +}; +SCC scc[2]; + +void +onepointseven(void) +{ + int i; + for(i = 0; i < 20; i++) + ; +} + +/* + * Access registers using the pointer in register 0. + * This is a bit stupid when accessing register 0. + */ +void +sccwrreg(SCC *sp, int addr, int value) +{ + onepointseven(); + *sp->ptr = addr; + wbflush(); + onepointseven(); + *sp->ptr = sp->sticky[addr] | value; + wbflush(); +} +ushort +sccrdreg(SCC *sp, int addr) +{ + onepointseven(); + *sp->ptr = addr; + wbflush(); + onepointseven(); + return *sp->ptr; +} + +/* + * set the baud rate by calculating and setting the baudrate + * generator constant. This will work with fairly non-standard + * baud rates. + */ +void +sccsetbaud(SCC *sp, int rate) +{ + int brconst; + + brconst = (SCCFREQ+16*rate-1)/(2*16*rate) - 2; + + sccwrreg(sp, 12, brconst & 0xff); + sccwrreg(sp, 13, (brconst>>8) & 0xff); +} + +/* + * toggle DTR + */ +void +sccdtr(SCC *sp, int n) +{ + if(n) + sp->sticky[5] |= TxDTR; + else + sp->sticky[5] &=~TxDTR; + sccwrreg(sp, 5, 0); +} + +/* + * toggle RTS + */ +void +sccrts(SCC *sp, int n) +{ + if(n) + sp->sticky[5] |= TxRTS; + else + sp->sticky[5] &=~TxRTS; + sccwrreg(sp, 5, 0); +} + +/* + * send break + */ +void +sccbreak(SCC *sp, int ms) +{ + sp->sticky[1] &=~TxIntEna; + sccwrreg(sp, 1, 0); + sccwrreg(sp, 5, TxBreak|TxEna); + tsleep(&u->p->sleep, return0, 0, ms); + sccwrreg(sp, 5, 0); + if(sp->oq){ + sp->sticky[1] |= TxIntEna; + sccwrreg(sp, 1, 0); + } +} + +/* + * 9600 baud, 1 stop bit, 8 bit chars, no interrupts, + * transmit and receive enabled, interrupts disabled. + */ +void +sccsetup(void) +{ + SCC *sp; + static int already; + + if(already) + return; + already = 1; + + /* + * get port addresses + */ + scc[0].ptr = &SCCADDR->ptra; + scc[0].data = &SCCADDR->dataa; + scc[1].ptr = &SCCADDR->ptrb; + scc[1].data = &SCCADDR->datab; + + for(sp = scc; sp < &scc[2]; sp++){ + memset(sp->sticky, 0, sizeof(sp->sticky)); + + /* + * turn on baud rate generator and set rate to 9600 baud. + * use 1 stop bit. + */ + sp->sticky[14] = BRSource; + sccwrreg(sp, 14, 0); + sccsetbaud(sp, 9600); + sp->sticky[4] = Rx1stop | X16; + sccwrreg(sp, 4, 0); + sp->sticky[11] = TxClockBR | RxClockBR | TRxCOutBR | TRxCOI; + sccwrreg(sp, 11, 0); + sp->sticky[14] = BREna | BRSource; + sccwrreg(sp, 14, 0); + + /* + * enable I/O, 8 bits/character + */ + sp->sticky[3] = RxEna | Rx8bits; + sccwrreg(sp, 3, 0); + sp->sticky[5] = TxEna | Tx8bits; + sccwrreg(sp, 5, 0); + } +} + +/* + * Queue n characters for output; if queue is full, we lose characters. + * Get the output going if it isn't already. + */ +void +sccputs(IOQ *cq, char *s, int n) +{ + SCC *sp = cq->ptr; + int ch, x; + + x = splhi(); + lock(cq); + puts(cq, s, n); + if(sp->printing == 0){ + ch = getc(cq); + /*kprint("", ch);*/ + if(ch >= 0){ + sp->printing = 1; + while((*sp->ptr&TxReady)==0) + ; + *sp->data = ch; + } + } + unlock(cq); + splx(x); +} + +/* + * an scc interrupt (a damn lot of work for one character) + */ +void +sccintr0(SCC *sp, uchar x) +{ + int ch; + IOQ *cq; + + if(x & ExtPendB){ + /*kprint("scc %d: %2.2ux\n", sp-scc, *sp->ptr);*/ + sccwrreg(sp, 0, ResExtPend); + } + if(x & RxPendB){ + cq = sp->iq; + while(*sp->ptr&RxReady){ + onepointseven(); + ch = *sp->data; + if(cq->putc) + (*cq->putc)(cq, ch); + else { + putc(cq, ch); + if(sp->delim[ch]) + wakeup(&cq->r); + } + } + } + if(x & TxPendB){ + cq = sp->oq; + lock(cq); + ch = getc(cq); + /*kprint("<%2.2ux>", ch);*/ + onepointseven(); + if(ch < 0){ + /*kprint("", ch);*/ + sccwrreg(sp, 0, ResTxPend); + sp->printing = 0; + wakeup(&cq->r); + }else + *sp->data = ch; + unlock(cq); + } +} +void +sccintr(void) +{ + uchar x; + + x = sccrdreg(&scc[0], 3); + sccintr0(&scc[1], x); + sccintr0(&scc[0], x>>3); +} + +/* + * turn on a port's interrupts. set DTR and RTS + */ +void +sccdevice(SCC *sp) +{ + /* + * turn on both ports + */ + sccsetup(); + + /* + * set up i/o routines + */ + if(sp->oq){ + sp->oq->puts = sccputs; + sp->oq->ptr = sp; + sp->sticky[1] |= TxIntEna | ExtIntEna; + } + if(sp->iq){ + sp->iq->ptr = sp; + sp->sticky[1] |= RxIntAllEna | ExtIntEna; + } + + /* + * turn on interrupts + */ + sccwrreg(sp, 1, 0); + sp->sticky[9] |= IntEna; + sccwrreg(sp, 9, 0); + + /* + * turn on DTR and RTS + */ + sccdtr(sp, 1); + sccrts(sp, 1); +} + +/* + * set up an scc port as something other than a stream + */ +void +sccspecial(int port, IOQ *oq, IOQ *iq, int baud) +{ + SCC *sp = &scc[port]; + + sp->nostream = 1; + sp->oq = oq; + sp->iq = iq; + sccdevice(sp); + sccsetbaud(sp, baud); +} + +static void scctimer(Alarm*); +static int sccputc(IOQ *, int); +static void sccstopen(Queue*, Stream*); +static void sccstclose(Queue*); +static void sccoput(Queue*, Block*); +static void scckproc(void *); +Qinfo sccinfo = +{ + nullput, + sccoput, + sccstopen, + sccstclose, + "scc" +}; + +/* + * create a helper process per port + */ +static void +scctimer(Alarm *a) +{ + SCC *sp = a->arg; + + cancel(a); + sp->a = 0; + wakeup(&sp->iq->r); +} + +static int +sccputc(IOQ *cq, int ch) +{ + SCC *sp = cq->ptr; int r; + + r = putc(cq, ch); + + /* + * pass upstream within sp->delay milliseconds + */ + if(sp->a==0){ + if(sp->delay == 0) + wakeup(&cq->r); + else + sp->a = alarm(sp->delay, scctimer, sp); + } + return r; +} + +static void +sccstopen(Queue *q, Stream *s) +{ + SCC *sp; + char name[NAMELEN]; + + kprint("sccstopen: q=0x%ux, inuse=%d, type=%d, dev=%d, id=%d\n", + q, s->inuse, s->type, s->dev, s->id); + sp = &scc[s->id]; + qlock(sp); + sp->wq = WR(q); + WR(q)->ptr = sp; + RD(q)->ptr = sp; + sp->delay = 64; + sp->iq->putc = sccputc; + qunlock(sp); + + /* start with all characters as delimiters */ + memset(sp->delim, 1, sizeof(sp->delim)); + + if(sp->kstarted == 0){ + sp->kstarted = 1; + sprint(name, "scc%d", s->id); + kproc(name, scckproc, sp); + } +} + +static void +sccstclose(Queue *q) +{ + SCC *sp = q->ptr; + + qlock(sp); + kprint("sccstclose: q=0x%ux, id=%d\n", q, sp-scc); + sp->wq = 0; + sp->iq->putc = 0; + WR(q)->ptr = 0; + RD(q)->ptr = 0; + qunlock(sp); +} + +static void +sccoput(Queue *q, Block *bp) +{ + SCC *sp = q->ptr; + IOQ *cq; + int n, m; + + if(sp == 0){ + freeb(bp); + return; + } + cq = sp->oq; + if(waserror()){ + freeb(bp); + nexterror(); + } + if(bp->type == M_CTL){ + while (cangetc(cq)) /* let output drain */ + sleep(&cq->r, cangetc, cq); + n = strtoul((char *)(bp->rptr+1), 0, 0); + switch(*bp->rptr){ + case 'B': + case 'b': + sccsetbaud(sp, n); + break; + case 'D': + case 'd': + sccdtr(sp, n); + break; + case 'K': + case 'k': + sccbreak(sp, n); + break; + case 'R': + case 'r': + sccrts(sp, n); + break; + case 'W': + case 'w': + if(n>=0 && n<1000) + sp->delay = n; + break; + } + }else while((m = BLEN(bp)) > 0){ + while ((n = canputc(cq)) == 0){ + kprint(" sccoput: sleeping\n"); + sleep(&cq->r, canputc, cq); + } + if(n > m) + n = m; + (*cq->puts)(cq, bp->rptr, n); + bp->rptr += n; + } + freeb(bp); + poperror(); +} + +/* + * process to send bytes upstream for a port + */ +static void +scckproc(void *a) +{ + SCC *sp = a; + IOQ *cq = sp->iq; + Block *bp; + int n; + +loop: + while ((n = cangetc(cq)) == 0) + sleep(&cq->r, cangetc, cq); + /*kprint(" scckproc: %d\n", n);*/ + qlock(sp); + if(sp->wq == 0){ + cq->out = cq->in; + }else{ + bp = allocb(n); + bp->flags |= S_DELIM; + bp->wptr += gets(cq, bp->wptr, n); + PUTNEXT(RD(sp->wq), bp); + } + qunlock(sp); + goto loop; +} + +enum{ + Qdir= 0, + Qtty0= STREAMQID(0, Sdataqid), + Qtty0ctl= STREAMQID(0, Sctlqid), + Qtty1= STREAMQID(1, Sdataqid), + Qtty1ctl= STREAMQID(1, Sctlqid), +}; + +Dirtab sccdir[]={ + "tty0", {Qtty0}, 0, 0666, + "tty0ctl", {Qtty0ctl}, 0, 0666, + "tty1", {Qtty1}, 0, 0666, + "tty1ctl", {Qtty1ctl}, 0, 0666, +}; + +#define NSCC (sizeof sccdir/sizeof(Dirtab)) + +/* + * allocate the queues if no one else has + */ +void +sccreset(void) +{ + SCC *sp; + + for(sp = scc; sp < &scc[2]; sp++){ + if(sp->nostream) + continue; + sp->iq = ialloc(sizeof(IOQ), 0); + initq(sp->iq); + sp->oq = ialloc(sizeof(IOQ), 0); + initq(sp->oq); + sccdevice(sp); + } +} + +void +sccinit(void) +{ +} + +Chan* +sccattach(char *spec) +{ + return devattach('t', spec); +} + +Chan* +sccclone(Chan *c, Chan *nc) +{ + return devclone(c, nc); +} + +int +sccwalk(Chan *c, char *name) +{ + return devwalk(c, name, sccdir, NSCC, devgen); +} + +void +sccstat(Chan *c, char *dp) +{ + switch(c->qid.path){ + case Qtty0: + streamstat(c, dp, "tty0"); + break; + case Qtty1: + streamstat(c, dp, "tty1"); + break; + default: + devstat(c, dp, sccdir, NSCC, devgen); + break; + } +} + +Chan* +sccopen(Chan *c, int omode) +{ + SCC *sp; + + switch(c->qid.path){ + case Qtty0: + case Qtty0ctl: + sp = &scc[0]; + break; + case Qtty1: + case Qtty1ctl: + sp = &scc[1]; + break; + default: + sp = 0; + break; + } + + if(sp && sp->nostream) + errors("in use"); + + if((c->qid.path & CHDIR) == 0) + streamopen(c, &sccinfo); + return devopen(c, omode, sccdir, NSCC, devgen); +} + +void +scccreate(Chan *c, char *name, int omode, ulong perm) +{ + error(Eperm); +} + +void +sccclose(Chan *c) +{ + if(c->stream) + streamclose(c); +} + +long +sccread(Chan *c, void *buf, long n, ulong offset) +{ + SCC *sp = &scc[0]; int s; + + switch(c->qid.path&~CHDIR){ + case Qdir: + return devdirread(c, buf, n, sccdir, NSCC, devgen); + case Qtty1ctl: + ++sp; + /* fall through */ + case Qtty0ctl: + if(offset) + return 0; + s = splhi(); + *(uchar *)buf = *sp->ptr; + splx(s); + return 1; + } + return streamread(c, buf, n); +} + +long +sccwrite(Chan *c, void *va, long n, ulong offset) +{ + return streamwrite(c, va, n, 0); +} + +void +sccremove(Chan *c) +{ + error(Eperm); +} + +void +sccwstat(Chan *c, char *dp) +{ + error(Eperm); +}