/* 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 * RLSD flow control reorganized by Bill_Simpson@um.cc.umich.edu; Feb 91 */ #include #include #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_rlsd __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,iface,arg1,arg2,bufsize,trigchar,cts,rlsd,speed) int dev; struct iface *iface; char *arg1,*arg2; /* Attach args for address and vector */ int16 bufsize; int trigchar; char cts; char rlsd; int16 speed; { register unsigned base; register struct fifo *fp; register struct asy *ap; char *ifn; char i_state; ap = &Asy[dev]; ap->iface = iface; ap->addr = htoi(arg1); ap->vec = htoi(arg2); ap->cts_flow_control = cts; /* set up to monitor rlsd (carrier detect) */ if ( rlsd ) { ap->rlsd_line_control = RLSD_DOWN; ifn = if_name(iface," rlsd"); newproc(ifn, 256, asy_rlsd, dev, NULL,NULL,0); free(ifn); pwait(NULL); } else { ap->rlsd_line_control = RLSD_NONE; } /* 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); /* 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); /* 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; } if ( rlsd ) { /* Turn on modem status interrupt, leave receive * and transmit interrupts off until RLSD is asserted */ outportb(base+IER, (char)IER_MS); /* DTR and RTS should match the RLSD signal */ asymsint(dev); /* Turn on 8250 master interrupt enable (connected to OUT2) */ outportb(base+MCR, (char)(MCR_OUT2)); } else { /* Turn on receive interrupt enable in 8250, leave transmit * and modem status interrupts turned off for now */ outportb(base+IER,(char)IER_DAV); /* Set modem control register: assert DTR, RTS, turn on 8250 * master interrupt enable (connected to OUT2) */ outportb(base+MCR,(char)(MCR_DTR|MCR_RTS|MCR_OUT2)); } /* Enable interrupt */ maskon(ap->vec); restore(i_state); asy_speed(dev,speed); ifn = if_name(iface," tx"); iface->txproc = newproc(ifn,256,asy_tx,dev,NULL,NULL,0); free(ifn); if ( !rlsd ) { /* If we're not checking, must assume we're up */ if ( iface->iostatus != NULL ) { (*iface->iostatus)(iface, PARAM_UP, 0L); } } return 0; } int asy_stop(iface) struct iface *iface; { register unsigned base; register struct asy *ap; char i_state; ap = &Asy[iface->dev]; if ( ap->rlsd_line_control ) { ap->rlsd_line_control = RLSD_NONE; psignal( &(ap->rlsd_line_control), 0 ); pwait(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 */ outportb(base+LCR,ap->save.lcr); outportb(base+IER,ap->save.ier); outportb(base+MCR,ap->save.mcr); restore(i_state); free(ap->fifo.buf); return 0; } /* Set asynch line speed */ int asy_speed(dev,bps) int dev; int16 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: /* 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(iface,cmd,set,val) struct iface *iface; int cmd; int set; int32 val; { int16 base = Asy[iface->dev].addr; switch(cmd){ case PARAM_SPEED: if(set) asy_speed(iface->dev,(int16)val); return Asy[iface->dev].speed; case PARAM_DTR: if(set) writebit(base+MCR,MCR_DTR,(int)val); return (inportb(base+MCR) & MCR_DTR) ? TRUE : FALSE; case PARAM_RTS: if(set) writebit(base+MCR,MCR_RTS,(int)val); 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); return FALSE; case PARAM_UP: setbit(base+IER,(char)IER_DAV); setbit(base+MCR,MCR_RTS); setbit(base+MCR,MCR_DTR); 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){ /* 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 case 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){ 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->cts_flow_ints++; break; } /* should happen at end of a single slip 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 */ static void asymsint(dev) int dev; { char msr; unsigned base; struct asy *asyp; struct dma *dp; asyp = &Asy[dev]; base = asyp->addr; msr = inportb(base+MSR); dp = &asyp->dma; /* Check status of modem control signals from remote host */ if (asyp->rlsd_line_control) { if (msr & MSR_RLSD) { /* RLSD is asserted */ if (asyp->rlsd_line_control == RLSD_DOWN) { /* RLSD just went up */ asyp->rlsd_line_control = RLSD_UP; psignal( &(asyp->rlsd_line_control), 1 ); } } else { /* RLSD not asserted */ if (asyp->rlsd_line_control == RLSD_UP) { /* RLSD just went down */ asyp->rlsd_line_control = RLSD_DOWN; psignal( &(asyp->rlsd_line_control), 1 ); } } } if (asyp->cts_flow_control) { if(msr & MSR_CTS){ /* CTS now asserted, enable Transmit interrupts */ if(dp->flags) setbit(base+IER,IER_TxE); } else { /* CTS now dropped, disable Transmit interrupts */ clrbit(base+IER,IER_TxE); } } } /* 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 (new_rlsd == RLSD_NONE) /* Just return the current value */ return(ap->rlsd_line_control); if (ap->rlsd_line_control == new_rlsd) /* Already at requested value */ return(new_rlsd); /* Wait for state change to requested value */ while (ap->rlsd_line_control != new_rlsd) { pwait( &(ap->rlsd_line_control) ); pause(2L); } if ( ap->rlsd_line_control == RLSD_UP ) result = PARAM_UP; else result = PARAM_DOWN; if ( ifp->ioctl != NULL ) (*ifp->ioctl)( ifp, result, TRUE, 0L ); if ( ifp->iostatus != NULLFP ) (*ifp->iostatus)( ifp, result, 0L ); if ( ifp->supv != NULLPROC ) alert( ifp->supv, 0 ); return(ap->rlsd_line_control); } /* Monitor RLSD signal, and report status changes */ static void asy_rlsd( dev, p1, p2 ) int dev; void *p1; void *p2; { int save_rlsd = Asy[dev].rlsd_line_control; while ( save_rlsd != RLSD_NONE ) { if ( save_rlsd != RLSD_UP ) { save_rlsd = get_rlsd_asy( dev, RLSD_UP ); } else { save_rlsd = get_rlsd_asy( dev, RLSD_DOWN ); } } } /* 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;ififo; 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); if(asyp->cts_flow_control) tprintf(" [cts flow control]"); if(asyp->rlsd_line_control) tprintf(" [rlsd line control]"); tprintf(" %u 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 CTS/RLSD, %lu THRE TO\n", asyp->txints, asyp->txchar, len_q(asyp->sndq), asyp->cts_flow_ints, 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); } } }