diff options
Diffstat (limited to 'os/mpc/devuart.c')
| -rw-r--r-- | os/mpc/devuart.c | 1450 |
1 files changed, 1450 insertions, 0 deletions
diff --git a/os/mpc/devuart.c b/os/mpc/devuart.c new file mode 100644 index 00000000..13adf351 --- /dev/null +++ b/os/mpc/devuart.c @@ -0,0 +1,1450 @@ +#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 { + Nbuf= 2, /* double buffered */ + Rbufsize= 512, + Bufsize= (Rbufsize+CACHELINESZ-1)&~(CACHELINESZ-1), + Nuart= 2+4, /* max in any 8xx architecture (2xSMC, 4xSCC) */ + CTLS= 's'&037, + CTLQ= 'q'&037, +}; + +enum { + /* status bits in SCC receive buffer descriptors */ + RxBRK= 1<<7, /* break ended frame (async hdlc) */ + RxDE= 1<<7, /* DPLL error (hdlc) */ + RxBOF= 1<<6, /* BOF ended frame (async hdlc) */ + RxLG= 1<<5, /* frame too large (hdlc) */ + RxNO= 1<<4, /* bad bit alignment (hdlc) */ + RxBR= 1<<5, /* break received during frame (uart) */ + RxFR= 1<<4, /* framing error (uart) */ + RxPR= 1<<3, /* parity error (uart) */ + RxAB= 1<<3, /* frame aborted (hdlc, async hdlc) */ + RxCR= 1<<2, /* bad CRC (hdlc, async hdlc) */ + RxOV= 1<<1, /* receiver overrun (all) */ + RxCD= 1<<0, /* CD lost (all) */ + + /* hdlc-specific Rx/Tx BDs */ + TxTC= 1<<10, +}; + +/* + * SMC in UART mode + */ + +typedef struct Uartsmc Uartsmc; +struct Uartsmc { + IOCparam; + ushort maxidl; + ushort idlc; + ushort brkln; + ushort brkec; + ushort brkcr; + ushort rmask; +}; + +/* + * SCC2 UART parameters + */ +enum { + /* special mode bits */ + SccAHDLC = 1<<0, + SccHDLC = 1<<1, + SccIR = 1<<2, + SccPPP = 1<<3, +}; + +typedef struct Uartscc Uartscc; +struct Uartscc { + SCCparam; + uchar rsvd[8]; + ushort max_idl; + ushort idlc; + ushort brkcr; + ushort parec; + ushort frmec; + ushort nosec; + ushort brkec; + ushort brkln; + ushort uaddr1; + ushort uaddr2; + ushort rtemp; + ushort toseq; + ushort character[8]; + ushort rccm; + ushort rccrp; + ushort rlbc; +}; + +typedef struct UartAHDLC UartAHDLC; +struct UartAHDLC { + SCCparam; + ulong rsvd1; + ulong c_mask; + ulong c_pres; + ushort bof; + ushort eof; + ushort esc; + ushort rsvd2[2]; + ushort zero; + ushort rsvd3; + ushort rfthr; + ushort resvd4[2]; + ulong txctl_tbl; + ulong rxctl_tbl; + ushort nof; + ushort rsvd5; +}; + +typedef struct UartHDLC UartHDLC; +struct UartHDLC { + SCCparam; + ulong rsvd1; + ulong c_mask; + ulong c_pres; + ushort disfc; + ushort crcec; + ushort abtsc; + ushort nmarc; + ushort retrc; + ushort mflr; + ushort max_cnt; + ushort rfthr; + ushort rfcnt; + ushort hmask; + ushort haddr[4]; + ushort tmp; + ushort tmp_mb; +}; + +enum { + /* SCC events of possible interest here eventually */ + AB= 1<<9, /* autobaud detected */ + GRA= 1<<7, /* graceful stop completed */ + CCR= 1<<3, /* control character detected */ + + /* SCC, SMC common interrupt events */ + BSY= 1<<2, /* receive buffer was busy (overrun) */ + TXB= 1<<1, /* block sent */ + RXB= 1<<0, /* block received */ + + /* SCC events */ + TXE = 1<<4, /* transmission error */ + RXF = 1<<3, /* final block received */ + + /* gsmr_l */ + ENR = 1<<5, /* enable receiver */ + ENT = 1<<4, /* enable transmitter */ + + /* port A */ + RXD1= SIBIT(15), + TXD1= SIBIT(14), + + /* port B */ + RTS1B= IBIT(19), + + /* port C */ + RTS1C= SIBIT(15), + CTS1= SIBIT(11), + CD1= SIBIT(10), +}; + +typedef struct Uart Uart; +struct Uart +{ + QLock; + + Uart *elist; /* next enabled interface */ + char name[KNAMELEN]; + + int x; /* index: x in SMCx or SCCx */ + int cpmid; /* eg, SCC1ID, SMC1ID */ + CPMdev* cpm; + int opens; + uchar bpc; /* bits/char */ + uchar parity; + uchar stopb; + uchar setup; + uchar enabled; + int dev; + + ulong frame; /* framing errors */ + ulong perror; + ulong overrun; /* rcvr overruns */ + ulong crcerr; + ulong interrupts; + int baud; /* baud rate */ + + /* flow control */ + int xonoff; /* software flow control on */ + int blocked; + int modem; /* hardware flow control on */ + int cts; /* ... cts state */ + int rts; /* ... rts state */ + Rendez r; + + /* buffers */ + int (*putc)(Queue*, int); + Queue *iq; + Queue *oq; + + /* staging areas to avoid some of the per character costs */ + /* TO DO: should probably use usual Ring */ + Block* istage[Nbuf]; /* double buffered */ + int rdrx; /* last buffer read */ + + Lock plock; /* for output variables */ + Block* outb; /* currently transmitting Block */ + + BD* rxb; + BD* txb; + + SMC* smc; + SCC* scc; + IOCparam* param; + ushort* brkcr; /* brkcr location in appropriate block */ + int brgc; + int mode; + Block* partial; + int loopback; +}; + +static Uart *uart[Nuart]; +static int nuart; + +struct Uartalloc { + Lock; + Uart *elist; /* list of enabled interfaces */ +} uartalloc; + +static void uartintr(Uart*, int); +static void smcuintr(Ureg*, void*); +static void sccuintr(Ureg*, void*); + +static void +uartsetbuf(Uart *up) +{ + IOCparam *p; + BD *bd; + int i; + Block *bp; + + p = up->param; + p->rfcr = 0x18; + p->tfcr = 0x18; + p->mrblr = Rbufsize; + + if((bd = up->rxb) == nil){ + bd = bdalloc(Nbuf); + up->rxb = bd; + } + p->rbase = (ushort)bd; + for(i=0; i<Nbuf; i++){ + bd->status = BDEmpty|BDInt; + bd->length = 0; + if((bp = up->istage[i]) == nil) + up->istage[i] = bp = allocb(Bufsize); + bd->addr = PADDR(bp->wp); + dcflush(bp->wp, Bufsize); + bd++; + } + (bd-1)->status |= BDWrap; + up->rdrx = 0; + + if((bd = up->txb) == nil){ + bd = bdalloc(1); + up->txb = bd; + } + p->tbase = (ushort)bd; + bd->status = BDWrap|BDInt; + bd->length = 0; + bd->addr = 0; +} + +static void +smcsetup(Uart *up) +{ + IMM *io; + Uartsmc *p; + SMC *smc; + ulong txrx; + + archdisableuart(up->cpmid); + up->brgc = brgalloc(); + if(up->brgc < 0) + error(Eio); + m->iomem->brgc[up->brgc] = baudgen(up->baud, 16) | BaudEnable; + smcnmsi(up->x, up->brgc); + + archenableuart(up->cpmid, 0); + + if(up->x == 1) + txrx = IBIT(24)|IBIT(25); /* SMC1 RX/TX */ + else + txrx = IBIT(20)|IBIT(21); /* SMC2 */ + io = ioplock(); + io->pbpar |= txrx; + io->pbdir &= ~txrx; + iopunlock(); + + up->param = up->cpm->param; + uartsetbuf(up); + + cpmop(up->cpm, InitRxTx, 0); + + /* SMC protocol parameters */ + p = (Uartsmc*)up->param; + up->brkcr = &p->brkcr; + p->maxidl = 1; /* non-zero so buffer closes when idle before mrblr reached */ + p->brkln = 0; + p->brkec = 0; + p->brkcr = 1; + smc = up->cpm->regs; + smc->smce = 0xff; /* clear events */ + smc->smcm = BSY|RXB|TXB; /* enable all possible interrupts */ + up->smc = smc; + smc->smcmr = ((1+8+1-1)<<11)|(2<<4); /* 8-bit, 1 stop, no parity; UART mode */ + intrenable(VectorCPIC+up->cpm->irq, smcuintr, up, BUSUNKNOWN, up->name); + /* enable when device opened */ +} + +static void +smcuintr(Ureg*, void *a) +{ + Uart *up; + int events; + + up = a; + events = up->smc->smce; + eieio(); + up->smc->smce = events; + uartintr(up, events&(BSY|RXB|TXB)); +} + +/* + * set the IO ports to enable the control signals for SCCx + */ +static void +sccuartpins(int x, int mode) +{ + IMM *io; + int i, w; + + x--; + io = ioplock(); + i = 2*x; + w = (TXD1|RXD1)<<i; /* TXDn and RXDn in port A */ + io->papar |= w; /* enable TXDn and RXDn pins */ + io->padir &= ~w; + if((mode & SccIR) == 0) + io->paodr |= TXD1<<i; + else + io->paodr &= ~w; /* not open drain */ + + w = (CD1|CTS1)<<i; /* CDn and CTSn in port C */ + io->pcpar &= ~w; + io->pcdir &= ~w; + if(conf.nocts2 || mode) + io->pcso &= ~w; /* force CTS and CD on */ + else + io->pcso |= w; + + w = RTS1B<<x; + io->pbpar &= ~w; + io->pbdir &= ~w; + + w = RTS1C<<x; /* RTSn~ */ + if((mode & SccIR) == 0) + io->pcpar |= w; + else + io->pcpar &= ~w; /* don't use for IR */ + iopunlock(); +} + +static void +sccsetup(Uart *up) +{ + SCC *scc; + int i; + + scc = up->cpm->regs; + up->scc = scc; + up->param = up->cpm->param; + sccxstop(up->cpm); + archdisableuart(up->cpmid); + if(up->brgc < 0){ + up->brgc = brgalloc(); + if(up->brgc < 0) + error(Eio); + } + m->iomem->brgc[up->brgc] = baudgen(up->baud, 16) | BaudEnable; + sccnmsi(up->x, up->brgc, up->brgc); + sccuartpins(up->x, up->mode); + + uartsetbuf(up); + + cpmop(up->cpm, InitRxTx, 0); + + /* SCC protocol parameters */ + if((up->mode & (SccAHDLC|SccHDLC)) == 0){ + Uartscc *sp; + sp = (Uartscc*)up->param; + sp->max_idl = 1; + sp->brkcr = 1; + sp->parec = 0; + sp->frmec = 0; + sp->nosec = 0; + sp->brkec = 0; + sp->brkln = 0; + sp->brkec = 0; + sp->uaddr1 = 0; + sp->uaddr2 = 0; + sp->toseq = 0; + for(i=0; i<8; i++) + sp->character[i] = 0x8000; + sp->rccm = 0xC0FF; + up->brkcr = &sp->brkcr; + scc->irmode = 0; + scc->dsr = ~0; + scc->gsmrh = 1<<5; /* 8-bit oriented receive fifo */ + scc->gsmrl = 0x28004; /* UART mode */ + }else{ + UartAHDLC *hp; + hp = (UartAHDLC*)up->param; + hp->c_mask = 0x0000F0B8; + hp->c_pres = 0x0000FFFF; + if(up->mode & SccIR){ + hp->bof = 0xC0; + hp->eof = 0xC1; + //scc->dsr = 0xC0C0; + scc->dsr = 0x7E7E; + }else{ + hp->bof = 0x7E; + hp->eof = 0x7E; + scc->dsr = 0x7E7E; + } + hp->esc = 0x7D; + hp->zero = 0; + if(up->mode & SccHDLC) + hp->rfthr = 1; + else + hp->rfthr = 0; /* receive threshold of 1 doesn't work properly for Async HDLC */ + hp->txctl_tbl = 0; + hp->rxctl_tbl = 0; + if(up->mode & SccIR){ + /* low-speed infrared */ + hp->nof = 12-1; /* 12 flags */ + scc->irsip = 0; + scc->irmode = (2<<8) | 1; + archsetirxcvr(0); + if(up->loopback) + scc->irmode = (3<<4)|1; /* loopback */ + }else{ + scc->irmode = 0; + hp->txctl_tbl = ~0; + hp->rxctl_tbl = ~0; + hp->nof = 1-1; /* one opening flag */ + } + up->brkcr = nil; + scc->gsmrh = 1<<5; /* 8-bit oriented receive fifo */ + if(up->mode & SccHDLC) + scc->gsmrl = 0x28000; /* HDLC */ + else + scc->gsmrl = 0x28006; /* async HDLC/IrDA */ + } + archenableuart(up->cpmid, (up->mode&SccIR)!=0); + scc->scce = ~0; /* clear events */ + scc->sccm = TXE|BSY|RXF|TXB|RXB; /* enable all interesting interrupts */ + intrenable(VectorCPIC+up->cpm->irq, sccuintr, up, BUSUNKNOWN, up->name); + scc->psmr = 3<<12; /* 8-bit, 1 stop, no parity; UART mode */ + if(up->loopback && (up->mode & SccIR) == 0) + scc->gsmrl |= 1<<6; /* internal loop back */ + scc->gsmrl |= ENT|ENR; /* enable rx/tx */ + if(0){ + print("gsmrl=%8.8lux gsmrh=%8.8lux dsr=%4.4ux irmode=%4.4ux\n", scc->gsmrl, scc->gsmrh, scc->dsr, scc->irmode); + for(i=0; i<sizeof(Uartscc); i+=4) + print("%2.2ux %8.8lux\n", i, *(ulong*)((uchar*)up->param+i)); + } +} + +static void +sccuintr(Ureg*, void *a) +{ + Uart *up; + int events; + + up = a; + if(up->scc == nil) + return; + events = up->scc->scce; + eieio(); + up->scc->scce = events; + if(up->enabled){ + if(0) + print("#%ux|", events); + uartintr(up, events); + } +} + +static void +uartsetbaud(Uart *p, int rate) +{ + if(rate <= 0 || p->brgc < 0) + return; + p->baud = rate; + m->iomem->brgc[p->brgc] = baudgen(rate, 16) | BaudEnable; +} + +static void +uartsetmode(Uart *p) +{ + int r, clen; + + ilock(&p->plock); + clen = p->bpc; + if(p->parity == 'e' || p->parity == 'o') + clen++; + clen++; /* stop bit */ + if(p->stopb == 2) + clen++; + if(p->smc){ + r = p->smc->smcmr & 0x3F; /* keep mode, enable bits */ + r |= (clen<<11); + if(p->parity == 'e') + r |= 3<<8; + else if(p->parity == 'o') + r |= 2<<8; + if(p->stopb == 2) + r |= 1<<10; + eieio(); + p->smc->smcmr = r; + }else if(p->scc && p->mode == 0){ + r = p->scc->psmr & 0x8FE0; /* keep mode bits */ + r |= ((p->bpc-5)&3)<<12; + if(p->parity == 'e') + r |= (6<<2)|2; + else if(p->parity == 'o') + r |= (4<<2)|0; + if(p->stopb == 2) + r |= 1<<14; + eieio(); + p->scc->psmr = r; + } + iunlock(&p->plock); +} + +static void +uartparity(Uart *p, char type) +{ + ilock(&p->plock); + p->parity = type; + iunlock(&p->plock); + uartsetmode(p); +} + +/* + * set bits/character + */ +static void +uartbits(Uart *p, int bits) +{ + if(bits < 5 || bits > 14 || bits > 8 && p->scc) + error(Ebadarg); + + ilock(&p->plock); + p->bpc = bits; + iunlock(&p->plock); + uartsetmode(p); +} + + +/* + * toggle DTR + */ +static void +uartdtr(Uart *p, int n) +{ + if(p->scc == nil) + return; /* not possible */ + USED(n); /* not possible on FADS */ +} + +/* + * toggle RTS + */ +static void +uartrts(Uart *p, int n) +{ + p->rts = n; + if(p->scc == nil) + return; /* not possible */ + USED(n); /* not possible on FADS */ +} + +/* + * send break + */ +static void +uartbreak(Uart *p, int ms) +{ + if(p->brkcr == nil) + return; + + if(ms <= 0) + ms = 200; + + if(waserror()){ + ilock(&p->plock); + *p->brkcr = 1; + cpmop(p->cpm, RestartTx, 0); + iunlock(&p->plock); + nexterror(); + } + ilock(&p->plock); + *p->brkcr = ((p->baud/(p->bpc+2))*ms+500)/1000; + cpmop(p->cpm, StopTx, 0); + iunlock(&p->plock); + + tsleep(&up->sleep, return0, 0, ms); + + poperror(); + ilock(&p->plock); + *p->brkcr = 1; + cpmop(p->cpm, RestartTx, 0); + iunlock(&p->plock); +} + +/* + * modem flow control on/off (rts/cts) + */ +static void +uartmflow(Uart *p, int n) +{ + if(p->scc == nil) + return; /* not possible */ + if(n){ + p->modem = 1; + /* enable status interrupts ... */ + p->scc->psmr |= 1<<15; /* enable async flow control */ + p->cts = 1; + /* could change maxidl */ + }else{ + p->modem = 0; + /* stop status interrupts ... */ + p->scc->psmr &= ~(1<<15); + p->cts = 1; + } +} + +/* + * turn on a port's interrupts. set DTR and RTS + */ +void +uartenable(Uart *p) +{ + Uart **l; + + if(p->enabled) + return; + + if(p->setup == 0){ + if(p->cpmid == CPsmc1 || p->cpmid == CPsmc2) + smcsetup(p); + else + sccsetup(p); + p->setup = 1; + } + + /* + * turn on interrupts + */ + if(p->smc){ + cpmop(p->cpm, RestartTx, 0); + p->smc->smcmr |= 3; + p->smc->smcm = BSY|TXB|RXB; + eieio(); + }else if(p->scc){ + cpmop(p->cpm, RestartTx, 0); + p->scc->gsmrl |= ENT|ENR; + p->scc->sccm = BSY|TXB|RXB; + eieio(); + } + + /* + * turn on DTR and RTS + */ + uartdtr(p, 1); + uartrts(p, 1); + + /* + * assume we can send + */ + p->cts = 1; + p->blocked = 0; + + /* + * set baud rate to the last used + */ + uartsetbaud(p, p->baud); + + lock(&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; + unlock(&uartalloc); + p->cts = 1; + p->blocked = 0; + p->xonoff = 0; + p->enabled = 1; +} + +/* + * turn off a port's interrupts. reset DTR and RTS + */ +void +uartdisable(Uart *p) +{ + Uart **l; + + /* + * turn off interrpts + */ + if(p->smc) + smcxstop(p->cpm); + else if(p->scc) + sccxstop(p->cpm); + + /* + * revert to default settings + */ + p->bpc = 8; + p->parity = 0; + p->stopb = 0; + + /* + * turn off DTR, RTS, hardware flow control & fifo's + */ + uartdtr(p, 0); + uartrts(p, 0); + uartmflow(p, 0); + p->xonoff = p->blocked = 0; + + lock(&uartalloc); + for(l = &uartalloc.elist; *l; l = &(*l)->elist){ + if(*l == p){ + *l = p->elist; + break; + } + } + p->enabled = 0; + unlock(&uartalloc); +} + +/* + * set the next output buffer going + */ +static void +txstart(Uart *p) +{ + Block *b; + int n, flags; + + if(!p->cts || p->blocked || p->txb->status & BDReady) + return; + if((b = p->outb) == nil){ + if((b = qget(p->oq)) == nil) + return; + if(p->mode & SccPPP && + p->mode & SccAHDLC && + BLEN(b) >= 8){ /* strip framing data */ + UartAHDLC *hp; + hp = (UartAHDLC*)p->param; + if(hp != nil && (p->mode & SccIR) == 0){ + hp->txctl_tbl = nhgetl(b->rp); + hp->rxctl_tbl = nhgetl(b->rp+4); + } + b->rp += 8; + if(0) + print("tx #%lux rx #%lux\n", hp->txctl_tbl, hp->rxctl_tbl); + } + } + n = BLEN(b); + if(n <= 0) + print("txstart: 0\n"); + if(p->bpc > 8){ + /* half-word alignment and length if chars are long */ + if(PADDR(b->rp)&1){ /* must be even if chars are long */ + memmove(b->base, b->rp, n); + b->rp = b->base; + b->wp = b->rp+n; + } + if(n & 1) + n++; + } + dcflush(b->rp, n); + p->outb = b; + if(n > 0xFFFF) + n = 0xFFFE; + if(p->mode & SccHDLC) + flags = BDLast | TxTC; + else if(p->mode) + flags = BDLast; + else + flags = 0; + p->txb->addr = PADDR(b->rp); + p->txb->length = n; + eieio(); + p->txb->status = (p->txb->status & BDWrap) | flags | BDReady|BDInt; + eieio(); +} + +/* + * (re)start output + */ +static void +uartkick(void *v) +{ + Uart *p; + + p = v; + ilock(&p->plock); + if(p->outb == nil) + txstart(p); + iunlock(&p->plock); +} + +/* + * restart input if it's off + */ +static void +uartflow(void *v) +{ + Uart *p; + + p = v; + if(p->modem) + uartrts(p, 1); +} + +static void +uartsetup(int x, int lid, char *name) +{ + Uart *p; + + if(nuart >= Nuart) + return; + + p = xalloc(sizeof(Uart)); + uart[nuart] = p; + strcpy(p->name, name); + p->dev = nuart; + nuart++; + p->x = x; + p->cpmid = lid; + p->cpm = cpmdev(lid); + p->brgc = -1; + p->mode = 0; + + /* + * set rate to 9600 baud. + * 8 bits/character. + * 1 stop bit. + * interrupts enabled. + */ + p->bpc = 8; + p->parity = 0; + p->baud = 9600; + + p->iq = qopen(4*1024, Qcoalesce, uartflow, p); + p->oq = qopen(4*1024, 0, uartkick, p); +} + +/* + * called by main() to configure a duart port as a console or a mouse + */ +void +uartspecial(int port, int baud, Queue **in, Queue **out, int (*putc)(Queue*, int)) +{ + Uart *p; + + if(port < 0 || port >= nuart || (p = uart[port]) == nil) + return; /* specified port not implemented */ + uartenable(p); + if(baud) + uartsetbaud(p, baud); + p->putc = putc; + if(in) + *in = p->iq; + if(out) + *out = p->oq; + p->opens++; +} + +static int +uartinput(Uart *p, BD *bd) +{ + int ch, dokick, i, l; + uchar *bp; + + dokick = 0; + if(bd->status & RxFR) + p->frame++; + if(bd->status & RxOV) + p->overrun++; + l = bd->length; + if(bd->status & RxPR){ + p->perror++; + l--; /* it's the last character */ + } + bp = KADDR(bd->addr); + if(p->xonoff || p->putc && p->opens==1){ + for(i=0; i<l; i++){ + ch = bp[i]; + if(p->xonoff){ + if(ch == CTLS){ + p->blocked = 1; + cpmop(p->cpm, StopTx, 0); + }else if (ch == CTLQ){ + p->blocked = 0; + dokick = 1; + } + /* BUG? should discard on/off char? */ + } + if(p->putc) + (*p->putc)(p->iq, ch); + } + } + if(l > 0 && (p->putc == nil || p->opens>1)) + qproduce(p->iq, bp, l); + return dokick; +} + +static void +framedinput(Uart *p, BD *bd) +{ + Block *pkt; + int l; + + pkt = p->partial; + p->partial = nil; + if(bd->status & RxOV){ + p->overrun++; + goto Discard; + } + if(bd->status & (RxAB|RxCR|RxCD|RxLG|RxNO|RxDE|RxBOF|RxBRK)){ + if(bd->status & RxCR) + p->crcerr++; + else + p->frame++; + goto Discard; + } + if(pkt == nil){ + pkt = iallocb(1500); /* TO DO: allocate less if possible */ + if(pkt == nil) + return; + } + l = bd->length; + if(bd->status & BDLast) + l -= BLEN(pkt); /* last one gives size of entire frame */ + if(l > 0){ + if(pkt->wp+l > pkt->lim) + goto Discard; + memmove(pkt->wp, KADDR(bd->addr), l); + pkt->wp += l; + } + if(0) + print("#%ux|", bd->status); + if(bd->status & BDLast){ + if(p->mode & (SccHDLC|SccAHDLC)){ + if(BLEN(pkt) <= 2){ + p->frame++; + goto Discard; + } + pkt->wp -= 2; /* strip CRC */ + } + qpass(p->iq, pkt); + }else + p->partial = pkt; + return; + +Discard: + if(pkt != nil) + freeb(pkt); +} + +/* + * handle an interrupt to a single uart + */ +static void +uartintr(Uart *p, int events) +{ + int dokick; + BD *bd; + Block *b; + + if(events & BSY) + p->overrun++; + p->interrupts++; + dokick = 0; + while(p->rxb != nil && ((bd = &p->rxb[p->rdrx])->status & BDEmpty) == 0){ + dcinval(KADDR(bd->addr), bd->length); + if(p->mode) + framedinput(p, bd); + else if(uartinput(p, bd)) + dokick = 1; + bd->status = (bd->status & BDWrap) | BDEmpty|BDInt; + eieio(); + if(++p->rdrx >= Nbuf) + p->rdrx = 0; + } + if((bd = p->txb) != nil){ + if((bd->status & BDReady) == 0){ + ilock(&p->plock); + if((b = p->outb) != nil){ + b->rp += bd->length; + if(b->rp >= b->wp){ + p->outb = nil; + freeb(b); + } + } + txstart(p); + iunlock(&p->plock); + } + } + eieio(); + /* TO DO: modem status isn't available on 82xFADS */ + if(dokick && p->cts && !p->blocked){ + if(p->outb == nil){ + ilock(&p->plock); + txstart(p); + iunlock(&p->plock); + } + cpmop(p->cpm, RestartTx, 0); + } else if (events & TXE) + cpmop(p->cpm, RestartTx, 0); +} + +/* + * used to ensure uart console output when debugging + */ +void +uartwait(void) +{ + Uart *p = uart[0]; + int s; + + while(p && (p->outb||qlen(p->oq))){ + if(islo()) + continue; + s = splhi(); + if((p->txb->status & BDReady) == 0){ + p->blocked = 0; + p->cts = 1; + if(p->scc == nil) + smcuintr(nil, p); + else + sccuintr(nil, p); + } + splx(s); + } +} + +static Dirtab *uartdir; +static int ndir; + +static void +setlength(int i) +{ + Uart *p; + + if(i >= 0){ + p = uart[i]; + if(p && p->opens && p->iq) + uartdir[1+4*i].length = qlen(p->iq); + } else for(i = 0; i < nuart; i++){ + p = uart[i]; + if(p && p->opens && p->iq) + uartdir[1+4*i].length = qlen(p->iq); + } + +} + +void +uartinstall(void) +{ + static int already; + int i, n; + char name[2*KNAMELEN]; + if(already) + return; + already = 1; + n = 0; + for(i=0; i<2; i++) + if(conf.smcuarts & (1<<i)){ + snprint(name, sizeof(name), "eia%d", n++); + uartsetup(i+1, CPsmc1+i, name); + } + n = 2; + for(i=0; i<conf.nscc; i++) + if(conf.sccuarts & (1<<i)){ + snprint(name, sizeof(name), "eia%d", n++); + uartsetup(i+1, CPscc1+i, name); + } +} + +/* + * all uarts must be uartsetup() by this point or inside of uartinstall() + */ +static void +uartreset(void) +{ + int i; + Dirtab *dp; + + uartinstall(); /* architecture specific */ + + ndir = 1+4*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++){ + /* 4 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++; + sprint(dp->name, "%smode", uart[i]->name); + dp->qid.path = NETQID(i, Ntypeqid); + dp->perm = 0660; + 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); + } + qunlock(p); + break; + } +} + +static long +uartstatus(Chan*, Uart *p, void *buf, long n, long offset) +{ + IMM *io; + char str[256]; + +// TO DO: change to standard format for first line: +//"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n" + sprint(str, "opens %d ferr %lud oerr %lud crcerr %lud baud %ud perr %lud intr %lud", p->opens, + p->frame, p->overrun, p->crcerr, p->baud, p->perror, p->interrupts); + /* TO DO: cts, dsr, ring, dcd, dtr, rts aren't all available on 82xFADS */ + io = m->iomem; + if(p->scc){ + if((io->pcdat & SIBIT(9)) == 0) + strcat(str, " cts"); + if((io->pcdat & SIBIT(8)) == 0) + strcat(str, " dcd"); + if((io->pbdat & IBIT(22)) == 0) + strcat(str, " dtr"); + }else if(p->smc){ + if((io->pbdat & IBIT(23)) == 0) + strcat(str, " dtr"); + } + 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); + case Ntypeqid: + return readnum(offset, buf, n, p->mode, NUMSIZE); + } + + return 0; +} + +static Block* +uartbread(Chan *c, long n, ulong offset) +{ + if(c->qid.type & QTDIR || NETTYPE(c->qid.path) != Ndataqid) + return devbread(c, n, offset); + return qbread(uart[NETID(c->qid.path)]->iq, n); +} + +static void +uartctl(Uart *p, char *cmd) +{ + int i, n; + + /* let output drain for a while */ + for(i = 0; i < 16 && qlen(p->oq); i++) + tsleep(&p->r, (int(*)(void*))qlen, p->oq, 125); + + if(strncmp(cmd, "break", 5) == 0){ + uartbreak(p, 0); + return; + } + + n = atoi(cmd+1); + switch(*cmd){ + case 'B': + case 'b': + uartsetbaud(p, n); + break; + case 'D': + case 'd': + uartdtr(p, n); + 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': + uartbits(p, n); + break; + case 'm': + case 'M': + uartmflow(p, n); + break; + case 'n': + case 'N': + qnoblock(p->oq, n); + break; + case 'P': + case 'p': + uartparity(p, *(cmd+1)); + break; + case 'K': + case 'k': + uartbreak(p, n); + break; + case 'R': + case 'r': + uartrts(p, n); + break; + case 'Q': + case 'q': + qsetlimit(p->iq, n); + qsetlimit(p->oq, n); + break; + case 'W': + case 'w': + /* obsolete */ + break; + case 'X': + case 'x': + p->xonoff = n; + break; + case 'Z': + case 'z': + p->loopback = n; + break; + } +} + +static long +uartwrite(Chan *c, void *buf, long n, vlong offset) +{ + Uart *p; + char cmd[32]; + int m, inuse; + + 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; + case Ntypeqid: + if(p->smc || p->putc) + error(Ebadarg); + if(n >= sizeof(cmd)) + n = sizeof(cmd)-1; + memmove(cmd, buf, n); + cmd[n] = 0; + m = strtoul(cmd, nil, 0); + inuse = 0; + qlock(p); + if(p->opens == 0){ + p->mode = m & 0x7F; + p->loopback = (m&0x80)!=0; + p->setup = 0; + }else + inuse = 1; + qunlock(p); + if(inuse) + error(Einuse); + return n; + } +} + +static long +uartbwrite(Chan *c, Block *bp, ulong offset) +{ + if(c->qid.type & QTDIR || NETTYPE(c->qid.path) != Ndataqid) + return devbwrite(c, bp, offset); + return qbwrite(uart[NETID(c->qid.path)]->oq, bp); +} + +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+4 * 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; +} + +Dev uartdevtab = { + 't', + "uart", + + uartreset, + devinit, + devshutdown, + uartattach, + uartwalk, + uartstat, + uartopen, + devcreate, + uartclose, + uartread, + uartbread, + uartwrite, + uartbwrite, + devremove, + uartwstat, +}; |
