/* 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 #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 int asyrxint __ARGS((struct asy *asyp)); static void asytxint __ARGS((int dev)); static void asymsint __ARGS((int dev)); 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,speed,cts,rlsd) int dev; struct iface *ifp; char *arg1,*arg2; /* Attach args for address and vector */ int16 bufsize; int trigchar; long speed; int cts; /* Use CTS flow control */ int rlsd; /* Use Received Line Signal Detect (aka CD) */ { register unsigned base; register struct fifo *fp; register struct asy *ap; char i_state; ap = &Asy[dev]; ap->iface = ifp; ap->addr = htoi(arg1); ap->vec = atoi(arg2); if(strchr(arg2,'c') != NULLCHAR) ap->chain = 1; else ap->chain = 0; /* 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->msr = 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 modem control flags */ ap->cts = cts; ap->rlsd = rlsd; /* 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); 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->iface == NULLIF) return -1; /* Not allocated */ ap->iface = NULLIF; base = ap->addr; stop_timer(&ap->idle); /* Stop the idle timer, if running */ (void)inportb(base+RBR); /* Purge the receive data buffer */ /* 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; 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); } 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; } /* Send a buffer on the serial transmitter and wait for completion */ void asy_write(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){ /* CTS flow control is enabled; let the modem control * interrupt enable transmit interrupts if CTS is off */ ier = IER_MS; if(asyp->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); /* Wait for completion */ i_state = dirps(); while(dp->flags == 1) pwait(asyp); 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) */ INTERRUPT (far *(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++; } return asyp->chain ? asyp->save.vec : NULL; } /* 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->cts && (ap->msr & MSR_DCTS)){ /* CTS has changed and we care */ if(ap->msr & MSR_CTS){ if(ap->dma.flags){ /* CTS now asserted, enable Transmit interrupts */ setbit(base+IER,IER_TxE); } } else { /* CTS now dropped, disable Transmit interrupts */ clrbit(base+IER,IER_TxE); } } if(ap->rlsd && (ap->msr & MSR_DRLSD)){ /* RLSD just changed and we care, signal it */ psignal( &(ap->rlsd), 1 ); /* Keep count */ if(ap->msr & MSR_RLSD) ap->answers++; else ap->remdrops++; } 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]; if(ap->rlsd == 0) return -1; for(;;){ if(new_rlsd && (ap->msr & MSR_RLSD)) return 1; if(!new_rlsd && !(ap->msr & MSR_RLSD)) return 0; /* Wait for state change to requested value */ pause(2L); pwait( &(ap->rlsd) ); } } /* 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){ tprintf(" [cts flow control]"); }; if(asyp->rlsd){ tprintf(" [rlsd line control]"); }; 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, %lu MS int, %lu THRE TO\n", asyp->txints, asyp->txchar, 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; { struct asy *asyp; if(dev < 0 || dev >= ASY_MAX) return -1; asyp = &Asy[dev]; dialer_kick(asyp); while(bp != NULLBUF){ /* Send the buffer */ asy_write(dev,bp->data,bp->cnt); /* Now do next buffer on chain */ bp = free_mbuf(bp); } start_timer(&asyp->idle); /* Restart idle timeout */ return 0; }