home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Media Share 9
/
MEDIASHARE_09.ISO
/
hamradio
/
s920603.zip
/
N8250.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-06-03
|
16KB
|
694 lines
/* 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
* Apr '92 Control signals redone again by Phil Karn
*/
#include <stdio.h>
#include <dos.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "n8250.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));
static void pasy __ARGS((struct asy *asyp));
struct asy Asy[ASY_MAX];
/* ASY interrupt handlers */
static INTERRUPT (*Handle[ASY_MAX])() = {
asy0vec,asy1vec,asy2vec,asy3vec,asy4vec,asy5vec
};
/* 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;
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);
DISABLE();
/* 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 receive interrupts and optionally modem interrupts;
* leave transmit interrupts off until we actually send data.
*/
if(ap->rlsd || ap->cts)
outportb(base+IER,IER_MS|IER_DAV);
else
outportb(base+IER,IER_DAV);
/* Turn on 8250 master interrupt enable (connected to OUT2) */
setbit(base+MCR,MCR_OUT2);
/* Enable interrupt */
maskon(ap->vec);
RESTORE();
asy_speed(dev,speed);
return 0;
}
int
asy_stop(ifp)
struct iface *ifp;
{
register unsigned base;
register struct asy *ap;
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 */
if(ap->is_16550a){
/* Purge hardware FIFOs and disable. Apparently some
* other comm programs can't handle 16550s already in
* FIFO mode; they expect 16450 compatibility mode.
*/
outportb(base+FCR,(char) FIFO_SETUP);
outportb(base+FCR,0);
}
/* Restore original interrupt vector and 8259 mask state */
DISABLE();
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();
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;
if(bps <= 0 || dev >= ASY_MAX)
return -1;
asyp = &Asy[dev];
if(asyp->iface == NULLIF)
return -1;
if(bps == 0)
return -1;
asyp->speed = bps;
base = asyp->addr;
divisor = BAUDCLK / bps;
DISABLE();
/* 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();
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+MCR,MCR_RTS);
clrbit(base+MCR,MCR_DTR);
return FALSE;
case PARAM_UP:
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 */
int
asy_write(dev,buf,cnt)
int dev;
char *buf;
unsigned short cnt;
{
register struct dma *dp;
unsigned base;
struct asy *asyp;
int tmp;
if(dev < 0 || dev >= ASY_MAX)
return -1;
asyp = &Asy[dev];
if(asyp->iface == NULLIF)
return -1;
base = asyp->addr;
dp = &asyp->dma;
if(dp->busy)
return -1; /* Already busy */
dp->data = buf;
dp->cnt = cnt;
dp->busy = 1;
/* If CTS flow control is disabled or CTS is true,
* enable transmit interrupts here so we'll take an immediate
* interrupt to get things going. Otherwise let the
* modem control interrupt enable transmit interrupts
* when CTS comes up. If we do turn on TxE,
* "kick start" the transmitter interrupt routine, in case just
* setting the interrupt enable bit doesn't cause an interrupt
*/
if(!asyp->cts || (asyp->msr & MSR_CTS)){
setbit(base+IER,IER_TxE);
asytxint(dev);
}
/* Wait for completion */
for(;;){
DISABLE();
tmp = dp->busy;
RESTORE();
if(tmp == 0)
break;
pwait(&asyp->dma);
}
return cnt;
}
/* Blocking read from asynch line
* Returns character or -1 if aborting
*/
int
get_asy(dev)
int dev;
{
register struct fifo *fp;
char c;
int tmp;
fp = &Asy[dev].fifo;
for(;;){
/* Atomic read and decrement of fp->cnt */
DISABLE();
tmp = fp->cnt;
if(tmp != 0){
fp->cnt--;
RESTORE();
break;
}
RESTORE();
if(pwait(fp) != 0)
return -1;
}
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->busy || (asyp->cts && !(asyp->msr & MSR_CTS))){
/* These events "shouldn't happen". Either the
* transmitter is idle, in which case the transmit
* interrupts should have been disabled, or flow control
* is enabled but CTS is low, and interrupts should also
* have been disabled.
*/
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
} else { /* 8250 */
do {
asyp->txchar++;
outportb(base+THR,*dp->data++);
} while(--dp->cnt != 0 && (inportb(base+LSR) & LSR_THRE));
}
if(dp->cnt == 0){
dp->busy = 0;
/* Disable further transmit interrupts */
clrbit(base+IER,IER_TxE);
psignal(&asyp->dma,1);
}
}
/* 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){
/* CTS went up */
if(ap->dma.busy){
/* enable transmit interrupts and kick */
setbit(base+IER,IER_TxE);
asytxint(dev);
}
} 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;i<ASY_MAX;i++){
asyp = &Asy[i];
fp = &asyp->fifo;
if(fp->cnt != 0)
psignal(fp,1);
if(asyp->dma.busy
&& (inportb(asyp->addr+LSR) & LSR_THRE)
&& (!asyp->cts || (asyp->msr & MSR_CTS))){
asyp->txto++;
DISABLE();
asytxint(asyp->iface->dev);
RESTORE();
}
}
}
int
doasystat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
register struct asy *asyp;
struct iface *ifp;
int i;
if(argc < 2){
for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
if(asyp->iface != NULLIF)
pasy(asyp);
}
return 0;
}
for(i=1;i<argc;i++){
if((ifp = if_lookup(argv[i])) == NULLIF){
printf("Interface %s unknown\n",argv[i]);
continue;
}
for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
if(asyp->iface == ifp){
pasy(asyp);
break;
}
}
if(asyp == &Asy[ASY_MAX])
printf("Interface %s not asy\n",argv[i]);
}
return 0;
}
static void
pasy(asyp)
struct asy *asyp;
{
int mcr;
printf("%s:",asyp->iface->name);
if(asyp->is_16550a)
printf(" [NS16550A]");
if(asyp->trigchar != -1)
printf(" [trigger 0x%02x]",asyp->trigchar);
if(asyp->cts)
printf(" [cts flow control]");
if(asyp->rlsd)
printf(" [rlsd line control]");
printf(" %lu bps\n",asyp->speed);
mcr = inportb(asyp->addr+MCR);
printf(" MC: int %lu DTR %s RTS %s CTS %s DSR %s RI %s CD %s\n",
asyp->msint_count,
(mcr & MCR_DTR) ? "On" : "Off",
(mcr & MCR_RTS) ? "On" : "Off",
(asyp->msr & MSR_CTS) ? "On" : "Off",
(asyp->msr & MSR_DSR) ? "On" : "Off",
(asyp->msr & MSR_RI) ? "On" : "Off",
(asyp->msr & MSR_RLSD) ? "On" : "Off");
printf(" RX: int %lu chars %lu hw over %lu hw hi %lu",
asyp->rxints,asyp->rxchar,asyp->overrun,asyp->rxhiwat);
asyp->rxhiwat = 0;
if(asyp->is_16550a)
printf(" fifo TO %lu",asyp->fifotimeouts);
printf(" sw over %lu sw hi %u\n",
asyp->fifo.overrun,asyp->fifo.hiwat);
asyp->fifo.hiwat = 0;
printf(" TX: int %lu chars %lu THRE TO %lu%s\n",
asyp->txints,asyp->txchar,asyp->txto,
asyp->dma.busy ? " BUSY" : "");
}
/* 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){
free_p(bp);
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;
}