/* 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
 * Jan '92      Added force 16550 fifo command
 *
 * Mods by PA0GRI
 * NRS flow control by WZ2B
 */

/* #define ALL_INTS            /* enable processing all port interrupts */

#include <dos.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "i8250.h"
#include "asy.h"
#include "devparam.h"
#include "pc.h"
#include "kisspoll.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,asy5vec
};
static unsigned char fifo_setup;
  
  
/* Initialize asynch port "dev" */
int
asy_init(
int dev,
struct iface *ifp,
char *arg1,
char *arg2,
int16 bufsize,
int trigchar,
char monitor,
long speed,
int force,
int triglevel,
int polled) {
  
    unsigned base;
    struct fifo *fp;
    struct asy *ap;
    char *ifn;
    int i_state;
    long interval;
  
    ap = &Asy[dev];
    ap->iface = ifp;
    ap->addr = htoi(arg1);
    ap->vec = htoi(arg2);      /* Note HEX, not DECIMAL! */
    if(strstr(strupr(arg2), "_C") == NULL) ap->chain = 0;
    else ap->chain = 1;   /* irq chained (4port card?). -- n5knx */
  
#ifdef POLLEDKISS
    ap->poller = NULL;
#endif
  
    /* 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);
    ap->save.iir = inportb(base+IIR);
  
    /* 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 */
#ifdef NRS
    if(ap->nrs_cts) {  /* WZ2B */
        ap->cts_flow_control = IGNORED;
        ap->rlsd_line_control = IGNORED;
        ap->dtr_usage = IGNORED;
        ap->rts_usage = IGNORED;
        monitor = 0;
    } else {
#endif
    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;
#ifdef NRS
    }
#endif

    /* 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.
     */
    /* Set the fifo trigger level according to the user's wishes :-) - WG7J */
    if(triglevel == 1)
        fifo_setup = FIFO_SETUP1;
    else if(triglevel == 4)
        fifo_setup = FIFO_SETUP4;
    else if(triglevel == 8)
        fifo_setup = FIFO_SETUP8;
    else if(triglevel == 14)
        fifo_setup = FIFO_SETUP14;
    else
        fifo_setup = FIFO_SETUP4;    /* default to 4 */
  
    if (((inportb(base+IIR) & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED) || force) {
        ap->is_16550a = TRUE;
        outportb(base+FCR,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.
     */
#ifdef NRS
    if(ap->nrs_cts)   /* WZ2B */
        outportb(base+IER, (char) IER_DAV);
    else
#endif
    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" ),
                               352, 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);
  
#ifdef NRS
    if (! ap->nrs_cts) {
#endif
    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);
        }
    }
#ifdef NRS
    }
#endif
  
#ifdef POLLEDKISS
    if(polled) {
        /* Calculate the poll interval: some processing time +
         * the packet duration for a mtu size packet.
         */
        interval = (((long)ifp->mtu * 10000L) / speed);
        ap->poller = newproc(ifn=if_name(ifp," poller"),
        384,kiss_poller,ifp->xdev,(void *)interval,NULL,0);
        free(ifn);
    }
#endif
  
    return 0;
}
  
  
int
asy_stop(ifp)
struct iface *ifp;
{
    register unsigned base;
    register struct asy *ap;
    int i_state;
  
    ap = &Asy[ifp->dev];
  
#ifdef POLLEDKISS
    if(ap->poller)
        killproc(ap->poller);
#endif
  
    if ( ap->monitor != NULL ) {
        killproc( ap->monitor );
        ap->monitor = NULL;
        ap->rlsd_line_control = IGNORED;
    }
    ap->iface = NULLIF;
  
    base = ap->addr;
  
    /* Purge the receive data buffer */
    (void)inportb(base+RBR);
  
    /* and hardware fifos if available */
    if (ap->is_16550a) {
        if((ap->save.iir & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED)
            outportb(base+FCR,fifo_setup);
        else
            outportb(base+FCR,0);
    }
  
    /* 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;
    int i_state;
  
    if(bps <= 0 || dev >= ASY_MAX)
        return -1;
    asyp = &Asy[dev];
    if(asyp->iface == NULLIF)
        return -1;
  
    switch(bps) {
        case 300L:
        case 1200L:
        case 2400L:
        case 4800L:
        case 9600L:
        case 19200L:
        case 38400L:
        case 57600L:
        case 115200L:
        /* 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,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:
#ifdef NRS
            if(! ap->nrs_cts) {	/* WZ2B */
#endif
            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;
#ifdef NRS
            }
#endif
            return FALSE;
        case PARAM_UP:
#ifdef NRS
            if(! ap->nrs_cts) {	/* WZ2B */
#endif
            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;
#ifdef NRS
            }
#endif
            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;
    int 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;
{
    int 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)*/
INTERRUPT (far *(asyint)(dev))()
int dev;
{
    struct asy *asyp;
    unsigned base;
    char iir;
  
#ifdef ALL_INTS
    unsigned startdev = dev;

    do {
#endif
        asyp = &Asy[dev];
#ifdef ALL_INTS
        if(asyp->iface) {
#endif
            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++;
            }
#ifdef ALL_INTS
        }
        if(++dev >= ASY_MAX)
            dev = 0;
    } while(dev != startdev);
#endif
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++;
                trigseen = TRUE;        /* @@@ */
        } 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(asyp->cts_flow_control & MOVED_DOWN   /* CTS flow control is enabled */
         && !(inportb(base+MSR) & MSR_CTS))  /* but CTS is de-asserted */
        return; /* nor is the modem 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++;
#if defined(HP95)
/* bug work-around, by Ron WA7TAS */
            clrbit(base+IER,IER_TxE);
            outportb(base+THR,*dp->data++);
            setbit(base+IER,IER_TxE);
#else
            outportb(base+THR,*dp->data++);
#endif
  
            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);
                    asytxint(dev);              /* @@@ */
                }
                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 (result == IGNORED)
                result = PARAM_DOWN;    /* give correct status @@@ */

    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)) {
            /* data waiting and uart buffer empty ... did we miss an interrupt? */
            /* && (inportb(asyp->addr+IER) & IER_TxE) */ /* this doesn't help! N5KNX */

            if(asyp->cts_flow_control & MOVED_DOWN   /* CTS flow control is enabled */
               && !(inportb(asyp->addr+MSR) & MSR_CTS))    /* but CTS is de-asserted */
                continue;    /* this is done in asytxint() but here, we avoid a false txto++ */
            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)
            tputs(" [NS16550A]");
        if(asyp->trigchar != -1)
            tprintf(" [trigger 0x%02x]",asyp->trigchar);
        switch (asyp->cts_flow_control) {
            case MOVED_DOWN:
            case MOVED_UP:
                tputs(" [cts flow control]");
                break;
        };
        switch (asyp->rlsd_line_control) {
            case MOVED_DOWN:
            case MOVED_UP:
                tputs(" [rlsd line control]");
                break;
            case IGNORED:
                tputs(" [rlsd blind]");
                break;
        };
#ifdef NRS
        if(asyp->nrs_cts)   /* WZ2B */
            tputs(" [NRS flow control]");
#endif
        tprintf(" %lu bps [@ %x,%x%s]\n",asyp->speed, asyp->addr, asyp->vec, (asyp->chain? "_C" : ""));
  
        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);
  /*      while (len_p((struct mbuf*)&Asy[dev].sndq)){
        pwait(NULL);
    }
  */
    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){
#ifdef NRS
            if(asyp->nrs_cts) { /* WZ2B */
                while(!(inportb((asyp->addr)+MSR)&MSR_CTS))
                    pwait(NULL);
                clrbit((asyp->addr)+MCR,MCR_RTS);
            }
#endif
            /* Start the transmitter */
            asy_output(dev,bp->data,bp->cnt);
  
            /* Wait for completion */
            i_state = dirps();
            while(dp->flags == 1)
                pwait(asyp);
#ifdef NRS
            if(asyp->nrs_cts) { /* WZ2B */
                while(!(inportb((asyp->addr)+LSR)&LSR_THRE))
                    pwait(NULL);
                while(!(inportb((asyp->addr)+LSR)&LSR_TSRE))
                    pwait(NULL);
                setbit((asyp->addr)+MCR,MCR_RTS);
            }
#endif
            restore(i_state);
  
            /* Now do next buffer on chain */
            bp = free_mbuf(bp);
        }
    }
}
  
#ifdef TIPSERVER
int
carrier_detect(int dev)
{
    struct asy *ap = &Asy[dev];
    unsigned base = ap->addr;
  
    ap->msr = inportb(base+MSR);
    if ( ap->msr & MSR_RLSD ) {
        return 1;
    } else {
        return 0;
    }
}
  
#endif
  
#ifdef TERMSERVER
void
asy_sendbreak(int dev)
{
    struct asy *ap = &Asy[dev];
    unsigned base = ap->addr;
#ifndef BREAK_DURATION
#define BREAK_DURATION 100L
#endif
  
    setbit(base+LCR,LCR_SB);    /* start sending BREAK */
    pause(BREAK_DURATION);      /* for BREAK_DURATION ms */
    clrbit(base+LCR,LCR_SB);
}
#endif /* TERMSERVER */
  
