#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
void duartputs(IOQ*, char*, int);
void iprint(char*, ...);
#define PAD 15 /* registers are well-spaced */
/*
* Register set for half the duart. There are really two sets in adjacent
* memory locations.
*/
struct Duartreg
{
uchar mr1_2, /* Mode Register Channels 1 & 2 */
pad0[PAD];
uchar sr_csr, /* Status Register/Clock Select Register */
pad1[PAD];
uchar cmnd, /* Command Register */
pad2[PAD];
uchar data, /* RX Holding / TX Holding Register */
pad3[PAD];
uchar ipc_acr, /* Input Port Change/Aux. Control Register */
pad4[PAD];
uchar is_imr, /* Interrupt Status/Interrupt Mask Register */
pad5[PAD];
uchar ctur, /* Counter/Timer Upper Register */
pad6[PAD];
uchar ctlr, /* Counter/Timer Lower Register */
pad7[PAD];
};
#define ppcr is_imr /* in the second register set */
#define DBD75 0
#define DBD110 1
#define DBD38400 2
#define DBD150 3
#define DBD300 4
#define DBD600 5
#define DBD1200 6
#define DBD2000 7
#define DBD2400 8
#define DBD4800 9
#define DBD1800 10
#define DBD9600 11
#define DBD19200 12
enum{
CHAR_ERR =0x00, /* MR1x - Mode Register 1 */
EVEN_PAR =0x00,
ODD_PAR =0x04,
NO_PAR =0x10,
CBITS8 =0x03,
CBITS7 =0x02,
CBITS6 =0x01,
CBITS5 =0x00,
NORM_OP =0x00, /* MR2x - Mode Register 2 */
TWOSTOPB =0x0F,
ONESTOPB =0x07,
ENB_RX =0x01, /* CRx - Command Register */
DIS_RX =0x02,
ENB_TX =0x04,
DIS_TX =0x08,
RESET_MR =0x10,
RESET_RCV =0x20,
RESET_TRANS =0x30,
RESET_ERR =0x40,
RESET_BCH =0x50,
STRT_BRK =0x60,
STOP_BRK =0x70,
RCV_RDY =0x01, /* SRx - Channel Status Register */
FIFOFULL =0x02,
XMT_RDY =0x04,
XMT_EMT =0x08,
OVR_ERR =0x10,
PAR_ERR =0x20,
FRM_ERR =0x40,
RCVD_BRK =0x80,
IM_IPC =0x80, /* IMRx/ISRx - Interrupt Mask/Interrupt Status */
IM_DBB =0x40,
IM_RRDYB =0x20,
IM_XRDYB =0x10,
IM_CRDY =0x08,
IM_DBA =0x04,
IM_RRDYA =0x02,
IM_XRDYA =0x01,
BD38400 =0xCC|0x0000,
BD19200 =0xCC|0x0100,
BD9600 =0xBB|0x0000,
BD4800 =0x99|0x0000,
BD2400 =0x88|0x0000,
BD1200 =0x66|0x0000,
BD300 =0x44|0x0000,
Maxduart =8,
};
/*
* requests to perform on a duart
*/
enum {
Dnone= 0,
Dbaud,
Dbreak,
Ddtr,
Dprint,
Dena,
Dstate,
};
/*
* a duart
*/
typedef struct Duart Duart;
struct Duart
{
QLock;
Duartreg *reg; /* duart registers */
uchar imr; /* sticky interrupt mask reg bits */
uchar acr; /* sticky auxiliary reg bits */
int inited;
};
Duart duart[Maxduart];
/*
* values specific to a single duart port
*/
typedef struct Port Port;
struct Port
{
QLock;
int printing; /* true if printing */
Duart *d; /* device */
Duartreg *reg; /* duart registers (for this port) */
int c; /* character to restart output */
int op; /* operation requested */
int val; /* value of operation */
Rendez opr; /* waiot here for op to complete */
/* 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 */
int kstarted; /* kproc started */
};
Port duartport[2*Maxduart];
/*
* configure a duart port, default is 9600 baud, 8 bits/char, 1 stop bit,
* no parity
*/
void
duartsetup(Port *p, Duart *d, int devno)
{
Duartreg *reg;
p->d = d;
reg = &d->reg[devno];
p->reg = reg;
reg->cmnd = RESET_RCV|DIS_TX|DIS_RX;
reg->cmnd = RESET_TRANS;
reg->cmnd = RESET_ERR;
reg->cmnd = STOP_BRK;
reg->cmnd = RESET_MR;
reg->mr1_2 = NO_PAR|CBITS8;
reg->mr1_2 = ONESTOPB;
reg->sr_csr = (DBD9600<<4)|DBD9600;
reg->cmnd = ENB_TX|ENB_RX;
}
/*
* init the duart on the current processor
*/
void
duartinit(void)
{
Port *p;
Duart *d;
d = &duart[m->machno];
if(d->inited)
return;
d->reg = DUARTREG;
d->imr = IM_RRDYA|IM_XRDYA|IM_RRDYB|IM_XRDYB;
d->reg->is_imr = d->imr;
d->acr = 0x80; /* baud rate set 2 */
d->reg->ipc_acr = d->acr;
p = &duartport[2*m->machno];
duartsetup(p, d, 0);
p++;
duartsetup(p, d, 1);
d->inited = 1;
}
/*
* enable a duart port
*/
void
duartenable(Port *p)
{
p->reg->cmnd = ENB_TX|ENB_RX;
}
void
duartenable0(void)
{
DUARTREG->cmnd = ENB_TX|ENB_RX;
}
void
duartbaud(Port *p, int b)
{
int x;
switch(b){
case 38400:
x = BD38400;
break;
case 19200:
x = BD19200;
break;
case 9600:
x = BD9600;
break;
case 4800:
x = BD4800;
break;
case 2400:
x = BD2400;
break;
case 1200:
x = BD1200;
break;
case 300:
x = BD300;
break;
default:
return;
}
if(x & 0x0100)
p->d->acr |= 0x80;
else
p->d->acr &= ~0x80;
p->d->reg->ipc_acr = p->d->acr;
p->reg->sr_csr = x;
}
void
duartdtr(Port *p, int val)
{
if (val)
p->reg->ctlr = 0x01;
else
p->reg->ctur = 0x01;
}
void
duartbreak(Port *p, int val)
{
Duartreg *reg;
reg = p->reg;
if (val){
p->d->imr &= ~IM_XRDYB;
p->d->reg->is_imr = p->d->imr;
reg->cmnd = STRT_BRK|ENB_TX;
} else {
reg->cmnd = STOP_BRK|ENB_TX;
p->d->imr |= IM_XRDYB;
p->d->reg->is_imr = p->d->imr;
}
}
/*
* do anything requested for this CPU's duarts
*/
void
duartslave0(Port *p)
{
switch(p->op){
case Ddtr:
duartdtr(p, p->val);
break;
case Dbaud:
duartbaud(p, p->val);
break;
case Dbreak:
duartbreak(p, p->val);
break;
case Dprint:
p->reg->cmnd = ENB_TX;
p->reg->data = p->val;
break;
case Dena:
duartenable(p);
break;
case Dstate:
p->val = p->reg->ppcr;
break;
}
p->op = Dnone;
wakeup(&p->opr);
}
void
duartslave(void)
{
IOQ *cq;
Port *p;
p = &duartport[2*m->machno];
cq = p->iq;
if(p->wq && cangetc(cq))
wakeup(&cq->r);
if(p->op != Dnone)
duartslave0(p);
p++;
cq = p->iq;
if(p->wq && cangetc(cq))
wakeup(&cq->r);
if(p->op != Dnone)
duartslave0(p);
}
void
duartrintr(Port *p)
{
Duartreg *reg;
IOQ *cq;
int status;
char ch;
reg = p->reg;
status = reg->sr_csr;
ch = reg->data;
if(status & (FRM_ERR|OVR_ERR|PAR_ERR))
reg->cmnd = RESET_ERR;
cq = p->iq;
if(cq->putc)
(*cq->putc)(cq, ch);
else
putc(cq, ch);
}
void
duartxintr(Port *p)
{
Duartreg *reg;
IOQ *cq;
int ch;
cq = p->oq;
lock(cq);
ch = getc(cq);
reg = p->reg;
if(ch < 0){
p->printing = 0;
wakeup(&cq->r);
reg->cmnd = DIS_TX;
} else
reg->data = ch;
unlock(cq);
}
void
duartintr(void)
{
int cause;
Duartreg *reg;
Port *p;
p = &duartport[2*m->machno];
reg = p->reg;
cause = reg->is_imr;
/*
* I can guess your interrupt.
*/
/*
* Is it 1?
*/
if(cause & IM_RRDYA)
duartrintr(p);
/*
* Is it 2?
*/
if(cause & IM_XRDYA)
duartxintr(p);
/*
* Is it 3?
*/
if(cause & IM_RRDYB)
duartrintr(p+1);
/*
* Is it 4?
*/
if(cause & IM_XRDYB)
duartxintr(p+1);
}
/*
* processor 0 only
*/
int
duartrawputc(int c)
{
Duartreg *reg;
int i;
reg = DUARTREG;
if(c == '\n')
duartrawputc('\r');
reg->cmnd = ENB_TX;
i = 0;
while((reg->sr_csr&XMT_RDY) == 0)
if(++i >= 1000000){
duartsetup(&duartport[0], &duart[0], 0);
for(i=0; i<100000; i++)
;
break;
}
reg->data = c;
if(c == '\n')
for(i=0; i<100000; i++)
;
return c;
}
void
duartrawputs(char *s)
{
int i;
while(*s){
duartrawputc(*s++);
}
for(i=0; i < 1000000; i++)
;
}
void
iprint(char *fmt, ...)
{
char buf[1024];
long *arg;
arg = (long*)(&fmt+1);
sprint(buf, fmt, *arg, *(arg+1), *(arg+2), *(arg+3));
duartrawputs(buf);
}
/*
* Queue n characters for output; if queue is full, we lose characters.
* Get the output going if it isn't already.
*/
void
duartputs(IOQ *cq, char *s, int n)
{
int ch, x;
Port *p;
x = splhi();
lock(cq);
puts(cq, s, n);
p = cq->ptr;
if(p->printing == 0){
ch = getc(cq);
if(ch >= 0){
p->printing = 1;
p->val = ch;
p->op = Dprint;
}
}
unlock(cq);
splx(x);
}
/*
* set up an duart port as something other than a stream
*/
void
duartspecial(int port, IOQ *oq, IOQ *iq, int baud)
{
Port *p = &duartport[port];
IOQ *zq;
p->nostream = 1;
if(oq){
p->oq = oq;
p->oq->puts = duartputs;
p->oq->ptr = p;
}
if(iq){
p->iq = iq;
p->iq->ptr = p;
/*
* Stupid HACK to undo a stupid hack
*/
zq = &kbdq;
if(iq == zq)
kbdq.putc = kbdcr2nl;
}
duartenable(p);
duartbaud(p, baud);
}
static int duartputc(IOQ *, int);
static void duartstopen(Queue*, Stream*);
static void duartstclose(Queue*);
static void duartoput(Queue*, Block*);
static void duartkproc(void *);
Qinfo duartinfo =
{
nullput,
duartoput,
duartstopen,
duartstclose,
"duart"
};
static int
opdone(void *x)
{
Port *p = x;
return p->op == Dnone;
}
static void
duartstopen(Queue *q, Stream *s)
{
Port *p;
char name[NAMELEN];
p = &duartport[s->id];
qlock(p);
p->wq = WR(q);
WR(q)->ptr = p;
RD(q)->ptr = p;
qunlock(p);
if(p->kstarted == 0){
p->kstarted = 1;
sprint(name, "duart%d", s->id+1);
kproc(name, duartkproc, p);
}
/* enable the port */
qlock(p);
p->op = Dena;
sleep(&p->opr, opdone, p);
qunlock(p);
}
static void
duartstclose(Queue *q)
{
Port *p = q->ptr;
qlock(p);
p->wq = 0;
p->iq->putc = 0;
WR(q)->ptr = 0;
RD(q)->ptr = 0;
qunlock(p);
}
static void
duartoput(Queue *q, Block *bp)
{
Port *p = q->ptr;
IOQ *cq;
int n, m;
if(p == 0){
freeb(bp);
return;
}
cq = p->oq;
if(waserror()){
freeb(bp);
nexterror();
}
if(bp->type == M_CTL){
if(waserror()){
qunlock(p);
qunlock(p->d);
nexterror();
}
qlock(p);
qlock(p->d);
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':
p->val = n;
p->op = Dbaud;
sleep(&p->opr, opdone, p);
break;
case 'D':
case 'd':
p->val = n;
p->op = Ddtr;
sleep(&p->opr, opdone, p);
break;
case 'K':
case 'k':
p->val = 1;
p->op = Dbreak;
if(!waserror()){
sleep(&p->opr, opdone, p);
tsleep(&p->opr, return0, 0, n);
poperror();
}
p->val = 0;
p->op = Dbreak;
sleep(&p->opr, opdone, p);
break;
case 'R':
case 'r':
/* can't control? */
break;
}
qunlock(p->d);
qunlock(p);
poperror();
}else while((m = BLEN(bp)) > 0){
while ((n = canputc(cq)) == 0){
kprint(" duartoput: 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
duartkproc(void *a)
{
Port *p = a;
IOQ *cq = p->iq;
Block *bp;
int n;
loop:
while ((n = cangetc(cq)) == 0)
sleep(&cq->r, cangetc, cq);
qlock(p);
if(p->wq == 0){
cq->out = cq->in;
}else{
bp = allocb(n);
bp->flags |= S_DELIM;
bp->wptr += gets(cq, bp->wptr, n);
PUTNEXT(RD(p->wq), bp);
}
qunlock(p);
goto loop;
}
Dirtab *duartdir;
int nduartport;
/*
* allocate the queues if no one else has
*/
void
duartreset(void)
{
Port *p;
int i;
/*
* allocate the directory and fill it in
*/
nduartport = 2*conf.nmach;
duartdir = xalloc(nduartport*2*sizeof(Dirtab));
for(i = 0; i < nduartport; i++){
sprint(duartdir[2*i].name, "eia%d", i+1);
sprint(duartdir[2*i+1].name, "eia%dctl", i+1);
duartdir[2*i].length = 0;
duartdir[2*i+1].length = 0;
duartdir[2*i].perm = 0666;
duartdir[2*i+1].perm = 0666;
duartdir[2*i].qid.path = STREAMQID(i, Sdataqid);
duartdir[2*i+1].qid.path = STREAMQID(i, Sctlqid);
}
/*
* allocate queues for any stream interfaces
*/
for(p = duartport; p < &duartport[nduartport]; p++){
if(p->nostream)
continue;
p->iq = xalloc(sizeof(IOQ));
initq(p->iq);
p->iq->ptr = p;
p->oq = xalloc(sizeof(IOQ));
initq(p->oq);
p->oq->ptr = p;
p->oq->puts = duartputs;
}
}
Chan*
duartattach(char *spec)
{
return devattach('t', spec);
}
Chan*
duartclone(Chan *c, Chan *nc)
{
return devclone(c, nc);
}
int
duartwalk(Chan *c, char *name)
{
return devwalk(c, name, duartdir, 2*nduartport, devgen);
}
void
duartstat(Chan *c, char *p)
{
switch(STREAMTYPE(c->qid.path)){
case Sdataqid:
streamstat(c, p, duartdir[2*STREAMID(c->qid.path)].name,
duartdir[2*STREAMID(c->qid.path)].perm);
break;
default:
devstat(c, p, duartdir, 2*nduartport, devgen);
break;
}
}
Chan*
duartopen(Chan *c, int omode)
{
Port *p;
switch(STREAMTYPE(c->qid.path)){
case Sdataqid:
case Sctlqid:
p = &duartport[STREAMID(c->qid.path)];
break;
default:
p = 0;
break;
}
if(p && p->nostream)
error(Einuse);
if((c->qid.path & CHDIR) == 0)
streamopen(c, &duartinfo);
return devopen(c, omode, duartdir, 2*nduartport, devgen);
}
void
duartcreate(Chan *c, char *name, int omode, ulong perm)
{
USED(c);
USED(name);
USED(omode);
USED(perm);
error(Eperm);
}
void
duartclose(Chan *c)
{
if(c->stream)
streamclose(c);
}
long
duartread(Chan *c, void *buf, long n, ulong offset)
{
Port *p;
if(c->qid.path&CHDIR)
return devdirread(c, buf, n, duartdir, 2*nduartport, devgen);
switch(STREAMTYPE(c->qid.path)){
case Sdataqid:
return streamread(c, buf, n);
case Sctlqid:
if(offset)
return 0;
p = &duartport[STREAMID(c->qid.path)];
qlock(p);
p->op = Dstate;
sleep(&p->opr, opdone, p);
*(uchar *)buf = p->val;
qunlock(p);
return 1;
}
error(Egreg);
return 0; /* not reached */
}
long
duartwrite(Chan *c, void *va, long n, ulong offset)
{
USED(offset);
return streamwrite(c, va, n, 0);
}
void
duartremove(Chan *c)
{
USED(c);
error(Eperm);
}
void
duartwstat(Chan *c, char *p)
{
USED(c);
USED(p);
error(Eperm);
}
int
duartactive(void)
{
int i;
for(i = 0; i < nduartport; i++)
if(duartport[i].printing)
return 1;
return 0;
}