home *** CD-ROM | disk | FTP | other *** search
- /* OS- and machine-dependent stuff for the 8250 asynch chip on a IBM-PC
- * Copyright 1991 Phil Karn, KA9Q
- *
- * 16550A support plus some statistics added mah@hpuviea.at 15/7/89
- *
- * CTS hardware flow control from dkstevens@ucdavis,
- * additional stats from delaroca@oac.ucla.edu added by karn 4/17/90
- * Feb '91 RLSD line control reorganized by Bill_Simpson@um.cc.umich.edu
- * Sep '91 All control signals reorganized by Bill Simpson
- */
- #include <stdio.h>
- #include <dos.h>
- #include "global.h"
- #include "mbuf.h"
- #include "proc.h"
- #include "iface.h"
- #include "8250.h"
- #include "asy.h"
- #include "devparam.h"
- #include "pc.h"
-
- static void asy_monitor __ARGS((int dev,void *p1,void *p2));
-
- static void asy_output __ARGS((int dev,char *buf,unsigned short cnt));
- static int asyrxint __ARGS((struct asy *asyp));
- static void asytxint __ARGS((int dev));
- static void asymsint __ARGS((int dev));
-
- static void asy_tx __ARGS((int dev,void *p1,void *p2));
-
- struct asy Asy[ASY_MAX];
-
- /* ASY interrupt handlers */
- static INTERRUPT (*Handle[])() = {asy0vec,asy1vec,asy2vec,asy3vec,asy4vec};
-
-
- /* Initialize asynch port "dev" */
- int
- asy_init(dev,ifp,arg1,arg2,bufsize,trigchar,monitor,speed)
- int dev;
- struct iface *ifp;
- char *arg1,*arg2; /* Attach args for address and vector */
- int16 bufsize;
- int trigchar;
- char monitor;
- long speed;
- {
- register unsigned base;
- register struct fifo *fp;
- register struct asy *ap;
- char *ifn;
- char i_state;
-
- ap = &Asy[dev];
- ap->iface = ifp;
- ap->addr = htoi(arg1);
- ap->vec = htoi(arg2);
-
- /* Set up receiver FIFO */
- fp = &ap->fifo;
- fp->buf = mallocw(bufsize);
- fp->bufsize = bufsize;
- fp->wp = fp->rp = fp->buf;
- fp->cnt = 0;
- fp->hiwat = 0;
- fp->overrun = 0;
- base = ap->addr;
- ap->trigchar = trigchar;
-
- /* Purge the receive data buffer */
- (void)inportb(base+RBR);
-
- i_state = dirps();
-
- /* Save original interrupt vector, mask state, control bits */
- ap->save.vec = getirq(ap->vec);
- ap->save.mask = getmask(ap->vec);
- ap->save.lcr = inportb(base+LCR);
- ap->save.ier = inportb(base+IER);
- ap->save.mcr = inportb(base+MCR);
- ap->save.msr = inportb(base+MSR);
-
- /* save speed bytes */
- setbit(base+LCR,LCR_DLAB);
- ap->save.divl = inportb(base+DLL);
- ap->save.divh = inportb(base+DLM);
- clrbit(base+LCR,LCR_DLAB);
-
- /* save some information on the starting state */
- ap->cts_flow_control = (ap->save.msr & MSR_CTS) ? FOUND_UP : FOUND_DOWN;
- ap->rlsd_line_control = (ap->save.msr & MSR_RLSD) ? FOUND_UP : FOUND_DOWN;
- ap->dtr_usage = (ap->save.mcr & MCR_DTR) ? FOUND_UP : FOUND_DOWN;
- ap->rts_usage = (ap->save.mcr & MCR_RTS) ? FOUND_UP : FOUND_DOWN;
-
- /* Set interrupt vector to SIO handler */
- setirq(ap->vec,Handle[dev]);
-
- /* Set line control register: 8 bits, no parity */
- outportb(base+LCR,(char)LCR_8BITS);
-
- /* determine if 16550A, turn on FIFO mode and clear RX and TX FIFOs */
- outportb(base+FCR,(char) FIFO_ENABLE);
-
- /* According to National ap note AN-493, the FIFO in the 16550 chip
- * is broken and must not be used. To determine if this is a 16550A
- * (which has a good FIFO implementation) check that both bits 7
- * and 6 of the IIR are 1 after setting the fifo enable bit. If
- * not, don't try to use the FIFO.
- */
- if ((inportb(base+IIR) & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED) {
- ap->is_16550a = TRUE;
- outportb(base+FCR,(char) FIFO_SETUP);
- } else {
- /* Chip is not a 16550A. In case it's a 16550 (which has a
- * broken FIFO), turn off the FIFO bit.
- */
- outportb(base+FCR,(char)0);
- ap->is_16550a = FALSE;
- }
-
- /* Turn on modem status and receive interrupts,
- * leave transmit interrupts off for now.
- */
- outportb(base+IER, (char)(IER_MS + IER_DAV));
-
- /* Turn on 8250 master interrupt enable (connected to OUT2) */
- setbit(base+MCR,(MCR_OUT2));
-
- /* Enable interrupt */
- maskon(ap->vec);
- restore(i_state);
-
- asy_speed(dev,speed);
-
- if ( monitor ) {
- /* set up to monitor line signals */
- ap->monitor = newproc( ifn = if_name( ifp, " monitor" ),
- 256, asy_monitor, ifp->dev, ifp, NULL, 0);
- free(ifn);
- pwait(NULL); /* let hooks get set up */
- }
-
- ifp->txproc = newproc( ifn = if_name(ifp," tx"),
- 256, asy_tx, dev, ifp, NULL, 0);
- free(ifn);
-
- if ( (ap->dtr_usage & FOUND_UP)
- && (ap->rlsd_line_control & FOUND_UP)
- || ap->monitor == NULL ) {
- if ( ifp->iostatus != NULL ) {
- (*ifp->iostatus)(ifp, PARAM_UP, 0L);
- }
- }
- return 0;
- }
-
-
- int
- asy_stop(ifp)
- struct iface *ifp;
- {
- register unsigned base;
- register struct asy *ap;
- char i_state;
-
- ap = &Asy[ifp->dev];
-
- if ( ap->monitor != NULL ) {
- ap->rlsd_line_control = IGNORED;
- killproc( ap->monitor );
- ap->monitor = NULL;
- }
- ap->iface = NULLIF;
-
- base = ap->addr;
-
- /* Purge the receive data buffer */
- (void)inportb(base+RBR);
-
- /* and hardware fifos if available */
- if (ap->is_16550a)
- outportb(base+FCR,(char) FIFO_SETUP);
-
- /* Restore original interrupt vector and 8259 mask state */
- i_state = dirps();
- setirq(ap->vec,ap->save.vec);
- if(ap->save.mask)
- maskon(ap->vec);
- else
- maskoff(ap->vec);
-
- /* Restore speed regs */
- setbit(base+LCR,LCR_DLAB);
- outportb(base+DLL,ap->save.divl); /* Low byte */
- outportb(base+DLM,ap->save.divh); /* Hi byte */
- clrbit(base+LCR,LCR_DLAB);
-
- /* Restore control regs */
- /* except, leave DTR in current state {bsimpson} */
- outportb(base+LCR,ap->save.lcr);
- outportb(base+IER,ap->save.ier);
- outportb(base+MCR,ap->save.mcr);
- #ifdef notdef
- outportb(base+MCR,(ap->save.mcr &~ MCR_DTR)
- | (inportb(base+MCR) & MCR_DTR) );
- #endif
-
- restore(i_state);
- free(ap->fifo.buf);
- return 0;
- }
-
-
- /* Set asynch line speed */
- int
- asy_speed(dev,bps)
- int dev;
- long bps;
- {
- register unsigned base;
- register long divisor;
- struct asy *asyp;
- char i_state;
-
- if(bps <= 0 || dev >= ASY_MAX)
- return -1;
- asyp = &Asy[dev];
- if(asyp->iface == NULLIF)
- return -1;
-
- switch(bps) {
- case 300:
- case 1200:
- case 2400:
- case 4800:
- case 9600:
- case 19200:
- case 38400L:
- /* supported speed */
- asyp->speed = bps;
- break;
- default:
- /* unsupported speed */
- return -1;
- };
-
- base = asyp->addr;
- divisor = BAUDCLK / bps;
-
- i_state = dirps();
-
- /* Purge the receive data buffer */
- (void)inportb(base+RBR);
- if (asyp->is_16550a) /* clear tx+rx fifos */
- outportb(base+FCR,(char) FIFO_SETUP);
-
- /* Turn on divisor latch access bit */
- setbit(base+LCR,LCR_DLAB);
-
- /* Load the two bytes of the register */
- outportb(base+DLL,(char)(divisor & 0xff)); /* Low byte */
- outportb(base+DLM,(char)((divisor >> 8) & 0xff)); /* Hi byte */
-
- /* Turn off divisor latch access bit */
- clrbit(base+LCR,LCR_DLAB);
-
- restore(i_state);
- return 0;
- }
-
-
- /* Asynchronous line I/O control */
- int32
- asy_ioctl(ifp,cmd,set,val)
- struct iface *ifp;
- int cmd;
- int set;
- int32 val;
- {
- struct asy *ap = &Asy[ifp->dev];
- int16 base = ap->addr;
-
- switch(cmd){
- case PARAM_SPEED:
- if(set)
- asy_speed(ifp->dev,val);
- return ap->speed;
- case PARAM_DTR:
- if(set) {
- writebit(base+MCR,MCR_DTR,(int)val);
- ap->dtr_usage = (val) ? MOVED_UP : MOVED_DOWN;
- }
- return (inportb(base+MCR) & MCR_DTR) ? TRUE : FALSE;
- case PARAM_RTS:
- if(set) {
- writebit(base+MCR,MCR_RTS,(int)val);
- ap->rts_usage = (val) ? MOVED_UP : MOVED_DOWN;
- }
- return (inportb(base+MCR) & MCR_RTS) ? TRUE : FALSE;
- case PARAM_DOWN:
- clrbit(base+IER,(char)IER_DAV);
- clrbit(base+MCR,MCR_RTS);
- clrbit(base+MCR,MCR_DTR);
- ap->rts_usage = MOVED_DOWN;
- ap->dtr_usage = MOVED_DOWN;
- return FALSE;
- case PARAM_UP:
- setbit(base+IER,(char)IER_DAV);
- setbit(base+MCR,MCR_RTS);
- setbit(base+MCR,MCR_DTR);
- ap->rts_usage = MOVED_UP;
- ap->dtr_usage = MOVED_UP;
- return TRUE;
- case PARAM_BLIND:
- setbit(base+IER,(char)IER_DAV);
- ap->rlsd_line_control = IGNORED;
-
- if ( ap->monitor != NULL ) {
- killproc( ap->monitor );
- ap->monitor = NULL;
- }
-
- /* can't see what we are doing, so pretend we're up */
- if ( ifp->iostatus != NULL ) {
- (*ifp->iostatus)(ifp, PARAM_UP, 0L);
- }
- return TRUE;
- };
- return -1;
- }
-
-
- /* Start transmission of a buffer on the serial transmitter */
- static void
- asy_output(dev,buf,cnt)
- int dev;
- char *buf;
- unsigned short cnt;
- {
- register struct dma *dp;
- unsigned base;
- char i_state;
- char ier;
- struct asy *asyp;
-
- if(dev < 0 || dev >= ASY_MAX)
- return;
- asyp = &Asy[dev];
- if(asyp->iface == NULLIF)
- return;
-
- base = asyp->addr;
- dp = &asyp->dma;
- i_state = dirps();
-
- if(dp->flags){
- restore(i_state);
- return; /* Already busy */
- }
- dp->data = buf;
- dp->cnt = cnt;
- dp->flags = 1;
-
- if(asyp->cts_flow_control & MOVED_DOWN){
- /* CTS flow control is enabled; let the modem control
- * interrupt enable transmit interrupts if CTS is off
- */
- ier = IER_MS;
- if(inportb(base+MSR) & MSR_CTS)
- ier |= IER_TxE;
- } else {
- /* Enable transmit interrupts; this will cause an immediate
- * interrupt that will start things going
- */
- ier = IER_TxE;
- }
- setbit(base+IER,ier);
-
- /* "Kick start" the transmitter interrupt routine, in case just
- * setting the interrupt enable bit doesn't cause an interrupt
- */
- if(ier & IER_TxE)
- asytxint(dev);
- restore(i_state);
- }
-
-
- /* Blocking read from asynch line
- * Returns character or -1 if aborting
- */
- int
- get_asy(dev)
- int dev;
- {
- char i_state;
- register struct fifo *fp;
- char c;
-
- fp = &Asy[dev].fifo;
-
- i_state = dirps();
- while(fp->cnt == 0){
- if(pwait(fp) != 0){
- restore(i_state);
- return -1;
- }
- }
- fp->cnt--;
- restore(i_state);
-
- c = *fp->rp++;
- if(fp->rp >= &fp->buf[fp->bufsize])
- fp->rp = fp->buf;
-
- return uchar(c);
- }
-
-
- /* Interrupt handler for 8250 asynch chip (called from asyvec.asm) */
- void
- asyint(dev)
- int dev;
- {
- struct asy *asyp;
- unsigned base;
- char iir;
-
- asyp = &Asy[dev];
- base = asyp->addr;
- while(((iir = inportb(base+IIR)) & IIR_IP) == 0){
- switch(iir & IIR_ID_MASK){
- case IIR_RDA: /* Receiver interrupt */
- asyrxint(asyp);
- break;
- case IIR_THRE: /* Transmit interrupt */
- asytxint(dev);
- break;
- case IIR_MSTAT: /* Modem status change */
- asymsint(dev);
- asyp->msint_count++;
- break;
- }
- /* should happen at end of a single packet */
- if(iir & IIR_FIFO_TIMEOUT)
- asyp->fifotimeouts++;
- }
- }
-
-
- /* Process 8250 receiver interrupts */
- static int
- asyrxint(asyp)
- struct asy *asyp;
- {
- register struct fifo *fp;
- unsigned base;
- char c,lsr;
- int cnt = 0;
- int trigseen = FALSE;
-
- asyp->rxints++;
- base = asyp->addr;
- fp = &asyp->fifo;
- for(;;){
- lsr = inportb(base+LSR);
- if(lsr & LSR_OE)
- asyp->overrun++;
-
- if(lsr & LSR_DR){
- asyp->rxchar++;
- c = inportb(base+RBR);
- if(asyp->trigchar == uchar(c))
- trigseen = TRUE;
- /* If buffer is full, we have no choice but
- * to drop the character
- */
- if(fp->cnt != fp->bufsize){
- *fp->wp++ = c;
- if(fp->wp >= &fp->buf[fp->bufsize])
- /* Wrap around */
- fp->wp = fp->buf;
- fp->cnt++;
- if(fp->cnt > fp->hiwat)
- fp->hiwat = fp->cnt;
- cnt++;
- } else
- fp->overrun++;
- } else
- break;
- }
- if(cnt > asyp->rxhiwat)
- asyp->rxhiwat = cnt;
- if(trigseen)
- psignal(fp,1);
- return cnt;
- }
-
-
- /* Handle 8250 transmitter interrupts */
- static void
- asytxint(dev)
- int dev;
- {
- register struct dma *dp;
- register unsigned base;
- register int count;
- struct asy *asyp;
-
- asyp = &Asy[dev];
- base = asyp->addr;
- dp = &asyp->dma;
- asyp->txints++;
- if(!dp->flags){
- /* "Shouldn't happen", but disable transmit
- * interrupts anyway
- */
- clrbit(base+IER,IER_TxE);
- return; /* Nothing to send */
- }
- if(!(inportb(base+LSR) & LSR_THRE))
- return; /* Not really ready */
-
- /* If it's a 16550A, load up to 16 chars into the tx hw fifo
- * at once. With an 8250, it can be one char at most.
- */
- if(asyp->is_16550a){
- count = min(dp->cnt,OUTPUT_FIFO_SIZE);
-
- /* 16550A: LSR_THRE will drop after the first char loaded
- * so we can't look at this bit to determine if the hw fifo is
- * full. There seems to be no way to determine if the tx fifo
- * is full (any clues?). So we should never get here while the
- * fifo isn't empty yet.
- */
- asyp->txchar += count;
- dp->cnt -= count;
- #ifdef notdef /* This is apparently too fast for some chips */
- dp->data = outbuf(base+THR,dp->data,count);
- #else
- while(count-- != 0)
- outportb(base+THR,*dp->data++);
- #endif
- if(dp->cnt == 0){
- dp->flags = 0;
- /* Disable transmit interrupts */
- clrbit(base+IER,IER_TxE);
- psignal(asyp,1);
- }
- } else { /* 8250 */
- do {
- asyp->txchar++;
- outportb(base+THR,*dp->data++);
-
- if(--dp->cnt == 0){
- dp->flags = 0;
- /* Disable transmit interrupts */
- clrbit(base+IER,IER_TxE);
- psignal(asyp,1);
- break;
- }
- } while(inportb(base+LSR) & LSR_THRE);
- }
- }
-
-
- /* Handle 8250 modem status change
- * If the signals are unchanging, we ignore them.
- * If they change, we use them to condition the line.
- */
- static void
- asymsint(dev)
- int dev;
- {
- struct asy *ap = &Asy[dev];
- unsigned base = ap->addr;
-
- ap->msr = inportb(base+MSR);
-
- if ( ap->msr & MSR_CTS ) {
- switch ( ap->cts_flow_control ) {
- case FOUND_DOWN:
- case MOVED_DOWN:
- ap->cts_flow_control = MOVED_UP;
-
- /* CTS now asserted, enable Transmit interrupts */
- if(ap->dma.flags)
- setbit(base+IER,IER_TxE);
- break;
- };
- } else {
- switch ( ap->cts_flow_control ) {
- case FOUND_UP:
- case MOVED_UP:
- ap->cts_flow_control = MOVED_DOWN;
-
- /* CTS now dropped, disable Transmit interrupts */
- clrbit(base+IER,IER_TxE);
- break;
- };
- }
-
- if ( ap->msr & MSR_RLSD ) {
- switch ( ap->rlsd_line_control ) {
- case FOUND_DOWN:
- case MOVED_DOWN:
- ap->rlsd_line_control = MOVED_UP;
-
- /* RLSD just went up */
- psignal( &(ap->rlsd_line_control), 1 );
- break;
- };
- } else {
- switch ( ap->rlsd_line_control ) {
- case FOUND_UP:
- case MOVED_UP:
- ap->rlsd_line_control = MOVED_DOWN;
-
- /* RLSD just went down */
- psignal( &(ap->rlsd_line_control), 1 );
- break;
- };
- }
-
- if ( ap->msr & (MSR_TERI | MSR_RI) ) {
- asy_ioctl( ap->iface, PARAM_UP, TRUE, 0L );
- }
- }
-
-
- /* Wait for a signal that the RLSD modem status has changed */
- int
- get_rlsd_asy(dev, new_rlsd)
- int dev;
- int new_rlsd;
- {
- struct asy *ap = &Asy[dev];
- struct iface *ifp = ap->iface;
- int result;
-
- if ( ap->rlsd_line_control & IGNORED ) {
- return IGNORED;
- }
-
- switch ( new_rlsd ) {
- case IGNORED:
- /* Just return the current value */
- return(ap->rlsd_line_control);
-
- case MOVED_DOWN:
- if ( !(ap->rlsd_line_control & FOUND_UP) ) {
- /* Already at requested value */
- return(new_rlsd);
- }
- break;
- case MOVED_UP:
- if ( ap->rlsd_line_control & FOUND_UP ) {
- /* Already at requested value */
- return(new_rlsd);
- }
- break;
- };
-
- /* Wait for state change to requested value */
- while (ap->rlsd_line_control != new_rlsd) {
- pause(2L);
- pwait( &(ap->rlsd_line_control) );
- }
-
- if ( ap->rlsd_line_control & FOUND_UP )
- result = PARAM_UP;
- else /* DOWN or IGNORED */
- result = PARAM_DOWN;
-
- /* set our control signals to follow RLSD */
- if ( ifp->ioctl != NULL )
- (*ifp->ioctl)( ifp, result, TRUE, 0L );
- if ( ifp->iostatus != NULL )
- (*ifp->iostatus)( ifp, result, 0L );
- return(ap->rlsd_line_control);
- }
-
-
- /* Monitor RLSD signal, and report status changes */
- static void
- asy_monitor( dev, p1, p2 )
- int dev;
- void *p1;
- void *p2;
- {
- int save_rlsd = Asy[dev].rlsd_line_control;
-
- while ( save_rlsd != IGNORED ) {
- save_rlsd = get_rlsd_asy( dev,
- ( save_rlsd ^ FOUND_UP ) | MOVED_DOWN );
- }
- Asy[dev].monitor = NULL;
- }
-
-
- /* Poll the asynch input queues; called on every clock tick.
- * This helps limit the interrupt ring buffer occupancy when long
- * packets are being received.
- */
- void
- asytimer()
- {
- register struct asy *asyp;
- register struct fifo *fp;
- register int i;
-
- for(i=0;i<ASY_MAX;i++){
- asyp = &Asy[i];
- fp = &asyp->fifo;
- if(fp->cnt != 0)
- psignal(fp,1);
- if(asyp->dma.flags != 0 && (inportb(asyp->addr+LSR) & LSR_THRE) ) {
- asyp->txto++;
- asytxint(asyp->iface->dev);
- }
- }
- }
- int
- doasystat(argc,argv,p)
- int argc;
- char *argv[];
- void *p;
- {
- register struct asy *asyp;
-
- for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
- if(asyp->iface == NULLIF)
- continue;
-
- tprintf("%s:",asyp->iface->name);
- if(asyp->is_16550a)
- tprintf(" [NS16550A]");
- if(asyp->trigchar != -1)
- tprintf(" [trigger 0x%02x]",asyp->trigchar);
- switch (asyp->cts_flow_control) {
- case MOVED_DOWN:
- case MOVED_UP:
- tprintf(" [cts flow control]");
- break;
- };
- switch (asyp->rlsd_line_control) {
- case MOVED_DOWN:
- case MOVED_UP:
- tprintf(" [rlsd line control]");
- break;
- case IGNORED:
- tprintf(" [blind]");
- break;
- };
- tprintf(" %lu bps\n",asyp->speed);
-
- tprintf(" RX: %lu int, %lu chr, %lu hw over, %lu hw hi,",
- asyp->rxints,
- asyp->rxchar,
- asyp->overrun,
- asyp->rxhiwat);
- asyp->rxhiwat = 0;
- if(asyp->is_16550a)
- tprintf(" %lu fifo TO,",asyp->fifotimeouts);
- tprintf(" %lu sw over, %u sw hi\n",
- asyp->fifo.overrun,
- asyp->fifo.hiwat);
- asyp->fifo.hiwat = 0;
-
- if(tprintf(" TX: %lu int, %lu chr, %u q, %lu MS int, %lu THRE TO\n",
- asyp->txints,
- asyp->txchar,
- len_q(asyp->sndq),
- asyp->msint_count,
- asyp->txto) == EOF)
- break;
- }
- return 0;
- }
- /* Send a message on the specified serial line */
- int
- asy_send(dev,bp)
- int dev;
- struct mbuf *bp;
- {
- if(dev < 0 || dev >= ASY_MAX)
- return -1;
- enqueue(&Asy[dev].sndq,bp);
- return 0;
- }
-
- /* Serial transmit process, common to all protocols */
- static void
- asy_tx(dev,p1,p2)
- int dev;
- void *p1;
- void *p2;
- {
- register struct mbuf *bp;
- struct asy *asyp;
- struct dma *dp;
- int i_state;
-
- asyp = &Asy[dev];
- dp = &asyp->dma;
-
- for(;;){
- /* Fetch a buffer for transmission */
- while(asyp->sndq == NULLBUF)
- pwait(&asyp->sndq);
-
- bp = dequeue(&asyp->sndq);
-
- while(bp != NULLBUF){
- /* Start the transmitter */
- asy_output(dev,bp->data,bp->cnt);
-
- /* Wait for completion */
- i_state = dirps();
- while(dp->flags == 1)
- pwait(asyp);
- restore(i_state);
-
- /* Now do next buffer on chain */
- bp = free_mbuf(bp);
- }
- }
- }
-
-