/*
 *  Copyright (c) 1996-2007 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 * =======================================================================
 *
 *
 * Edit Date/Ver   Edit Description
 * ==============  ===================================================
 * 01/13/99  JW    Added support for modem control signls (DCD and DTR)
 *                 in onccIoctl() and added new definitions in h file
 *
 * 10/06/99  CHG   Changed declaration of get_baud_div() to eliminate
 *                 warning message from compiler.
 *                 Removed definition of NET_OS since it is set by build
 *                 file.
 *
 * 10/12/99  SW    1. Merge ioctl for sending extra cr
 *                 2. Fix problem with O_RDONLY being defined as 0
 *                 3. Fix use of semaphore in close
 *                 4. Open should create semaphore for the requested chan
 * 12/13/99  JW    1. Increased read/write buffers from 1K to 2K
 *                 2. Added code to set RECV_CLOSED_IRQ bit in control
 *                    register A in onccIoctl()
 *                 3. Fixed a typecast bug inside of onccIoctl()
 *                 4. Added code to check and handle RECV_CLOSED bit in
 *                    status register in onccIntRd()
 *                 5. Added code to read in multiple characters in FIFO
 *                    in onccIntRd (Shing's fix)
 *
 * 03/14/00  JFU	   1. added * to arg on one of the statement in onccIoctl()
 *
 * 03/16/00  PMJ   Corrected netos_serlclose to flush and discard (cb_reset)
 *                 recieve data before completing close.
 *
 * 05/03/00 JFU     1. Added 115200 support
 *                  2. Corrected Odd parity setting
 *                  3. Improved hardware handshake problem in the onccIntRd and netos_serlread functions.
 *
 * 08/25/00 JFU     added DMA hardware flow control feature.
 *
 * 10/10/00 JFU     updated non parity setting and some time delay
 *
 * 10/25/00 JFU     updated some dma related bugs
 *
 * 11/20/00 JFU     updated baud rate calculation based on new frequency functions
 *
 * 11/21/00 PMJ     Removed references to XTAL_FREQ -- replaced with calls to NAgetXtalFreq()
 *
 * 11/28/00 PMJ     Clarified the processing in get_baud_div -- better reflection with the
 *                  HW Ref manual.
 *
 * 11/29/00 PMJ     Changed the error comparison in get_baud_div to parts per Baudrate instead of
 *                  percent -- eliminating division (thus round off error) and improving accuracy.
 *
 * 11/29/00 JFU     added code to handle empty DMA buffer interrupt
 *
 * 12/08/00 JFU     added code for send > 32768 bytes in DMA mode
 *
 * 12/21/00 JFU     added major changes to the driver due to two reasons
 *                  (1) 50-0 chip serial problem.  - this chip still does not support hardware handshake.
 *                  (2) modify driver to handle changes in the serial port behavior due to changes with char gap timer.
 *
 * 2/5/01 JFU       Added support for 1036800 BAUD in DMA mode with hardware handshake.
 *
 * 04/18/01 NGUR    Fix problem with the default baud rate greater then 57K
 *
 * 05/04/01 PMJ     Adde fixes per Softworks regarding PPP failures.
 * 5/17/01 ESUN     Modified OnccIntRd(), netos_serlwrite(), cb_add(), cb_remove(),
 *                  and cb_reset() to solve XON/XOFF problem.
 *
 * 6/25/01 JFU      Clear up the file otherwise it is same as 1.3.1.0 or netos 3.00A
 *
 * 09/28/01 JW      minor fix in onccIoctl() for the SIO_SET_STATUS_DTR request
 *
 * 11/16/01 JFU     modify the driver to handle nonblocking dma problem.
 * 03/04/02  SW     Remove the use of bit field                  
 * 04/11/02  PMJ    Added support to sense the DSR state.  See SIO_GET_STATUS_DSR.
 * 04/16/02  DXL    Cleaned up variable declared but never reference; warnings generated by new compiler
 * 06/12/02  JFU    Removed a bug related to software handshaking    
 * 08/02/02  PMJ    New and improved serial API with select functionality and better
 *                  modem signals control.  Introducing the RTS Transmission encapsulation
 *                  which can be used for RS-485 half-duplex style communucation.
 * 08/07/02  PMJ    Included POSIX functions ttyname() and isatty().  Included some error 
 *                  handling around SIO_TERM_SEND_CR and SIO_TERM_ECHO ioctls.
 * 09/24/02  LZ     Added 20UM support.
 *						1. if the gap timer count is too big, set to the MAX the 10 bits can hold
 *						2. Using F_crystal to calculate the gap timer count
 *						3. For higher baud rate, TMODE = 1 and TDCR/RDCR = 00/00 to lower the divisor error
 * 11/10/02  JW     Modified get_char_gap_count() to compute the gap count for baud rates
 *                  up to 115200.  Previous code computed gap count only for baud rates lower
 *                  than 57600.
 * 03/28/03  JZW    Cleaning up some magic numbers in functions ser_dma_write and
 *                  get_baud_div
 *
 */

#include <string.h>
#include <errno.h>
#include <netosIo.h>
#include <reg_def.h>
#include <serParam.h>
#include "xk_em_serl.h"
#include <NA_ISR.h>
#include <sysClock.h>
#include <ddi.h>
#include <Dma_def.h>
#include "serial_help.h"
#ifdef SERIAL_FIQ
#include "regs.h"
#endif
#ifdef NDS_SPECIFIC
#include "bcomm.h"
#endif
#include "armutils.h"

#include "assert.h"

#include "termios.h"
#include "termios_int.h"
#include "event_handler.h"

#ifdef NDS_SPECIFIC
#include "alarm_if.hh"
#ifdef PORTBUFFERING_ENABLED
#include "portbuffer.hh"
#endif
#endif

#ifdef DOTNETMF
extern void DIGI_EventSets(unsigned int Events);
#define DIGI_EVENT_FLAG_RXISR_COM1     0x00000020
#define DIGI_EVENT_FLAG_RXISR_COM2     0x00000040
#define DIGI_EVENT_FLAG_RXISR_COM3     0x00000200
#define DIGI_EVENT_FLAG_RXISR_COM4     0x00000400
#endif


#ifdef NET_OS_SPECIFIC
#include "bsp_serial.h"
#if (BSP_SERIAL_FAST_INTERRUPT == TRUE)
#define SERIAL_FIQ
#endif

#include "regs.h"

#define MONITOR_STACK_SIZE 0x2000
TX_THREAD serl_monitor_thread;
char serl_monitor_stack[MONITOR_STACK_SIZE];  

extern int serial_channel(ushort major, ushort minor);
#define MAJOR_NETOS_SERL  0         /*Not used in NETOS */
#define	O_ACCMODE	(O_RDONLY|O_WRONLY|O_RDWR)
#define O_NOFLUSH	0x10000000

#ifdef ALARMS_ENABLED
int* patternAlarmEnabled = NULL;    /*Not used in NETOS */
#define PM_FLOW_CONTROL_OFF 0       /*Not used in NETOS */
#define PATTERN_ALARM_ENABLED 1     /*Not used in NETOS */
int
patternAlarmNewData(int port, char *newData, int newDataLen)
{
    NA_UNUSED_PARAM(port);
    NA_UNUSED_PARAM(newData);
    NA_UNUSED_PARAM(newDataLen);
    return 0;
}
#endif

/* turn it on whenever you like to control CTS in software */
#define NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
#define NA_BYTES_IN_EACH_LINE 1         /* min is 1 and max is 4 */
#define NA_CTS_CHANGE         1

static TX_EVENT_FLAGS_GROUP cntrl_flags;
static ULONG actual_flags;
static void onccHandleCTSChange(ONCC_CHAN * pChan);
#define NA_MAX_SKID_RATE      16
#endif

#endif /* NET_OS_SPECIFIC */

#ifdef SERIAL_ACTIVITY_LED_ENABLED
#include "led_driver.h"
#endif

#ifdef CHANGE_TX_ISR_STATE_IN_DRIVER
#warning "It appears to be unsafe to change isr (Enable and Disable) in interrupt context.  Use this code at your own risk."
#endif


/* Function to notify event handling routines of event occurance */

extern void serl_cb_event(int, enum events);
static int serial_isopen_int(int chan);

/*Macro to handle logical enumeration on devices using xk/em, and sp drivers*/
#if 0 // Macro no longer needed
#if defined(MG_ENABLED) || defined(WISP2PORT_ENABLED) || defined(SP2PORT_ENABLED)
#define SERL_CB_EVENT(X, Y) serl_cb_event((X)+1, (Y))
#else
#define SERL_CB_EVENT(X, Y) serl_cb_event((X), (Y))
#endif
#endif // Macro no longer needed 

#if 0	/* Resource locking has been disabled. */
/*
 * Serial channel resource lock (used by serial driver & HDLC driver
 */
extern int serl_resource_lock[2];

/*
 * The following routines provide simple resource lock mechanism
 */
extern int GetResource(int *resource, void *owner);
extern int ReleaseResource(int *resource, void *owner);
#else
#define GetResource(a,b) 1
#define ReleaseResource(a,b) 1
#endif

static int onccTxStartup (SIO_CHAN * pSioChan);
static void onccIntRd (ONCC_CHAN * pChan);

/*#define DEBUG_SERIAL                       this can be defined to activate debugging */
#define EXIT_SUCCESS        0
#define OK                  EXIT_SUCCESS
#define ERROR               (-1)
#define SIZE_OF_BUF         4096            /* increase from 1K to 2K ... J.W. */
#define SW_BUF_THRESH_HOLD  400             /* arbitary number for sw handshake */
#define NUM_DMA_RX_BUF      8               /* set the dma rx buffer number to 2  - arbitary */
#define M_QHEAD_SIZE        8               /* size of the dma queue */

struct ourSemaphore
{
    int state;                              /* semaphore state  */
    int count;                              /* semaphore count  */
};

/* semaphore for reading */
static struct ourSemaphore          r_sem[NUM_NCC_SER_CHAN];

/* semaphore for writing */
static struct ourSemaphore          w_sem[NUM_NCC_SER_CHAN];

#define SSERIAL_INT

extern int SerialSetIRQ(int level);

#ifdef SERIAL_FIQ
#define _DISAB_INTS(a) a = SerialSetIRQ((IRQ_DISABLED | FIQ_DISABLED))
#else
#define _DISAB_INTS(a) a = SerialSetIRQ(IRQ_DISABLED)
#endif

#define _RESTOR_INT(a) SerialSetIRQ(a)


#define _OPEN_SEMA(x)               ((struct ourSemaphore *)x)->state = 1; ((struct ourSemaphore *)x)->count = 1

#define _CLOSE_SEMA(x)              ((struct ourSemaphore *)x)->state = 0; ((struct ourSemaphore *)x)->count = 0

#define _GET_SEMA(x)                {   int priorInterruptState;                            \
                                        _DISAB_INTS(priorInterruptState);                   \
                                        if (((struct ourSemaphore *)x)->count > 0) {        \
                                            ((struct ourSemaphore *)x)->count = 0;          \
                                            _RESTOR_INT (priorInterruptState);              \
                                        }                                                   \
                                        else { _RESTOR_INT (priorInterruptState);           \
                                            return EBUSY;  }                                \
                                    }

#define PUT_SEMA(x)                 {   int priorInterruptState;                            \
                                        _DISAB_INTS(priorInterruptState);                   \
                                        ((struct ourSemaphore *)x)->count++;                \
                                        _RESTOR_INT (priorInterruptState); }

#ifdef DEBUG_SERIAL
int rrdyc =0;                           /* these are counter for debugging purpose */
int rbcc = 0;
int xonc = 0;
int xoffc = 0;
int recvc = 0;
#endif

/* The ring buffer size must be a power of 2 to work correctly. */
#define RING_BUFFER_SIZE 4096
#define RING_BUFFER_MASK (RING_BUFFER_SIZE - 1)

/* ring buffer for storing input and output characters */
typedef struct
{
    unsigned int in;
    unsigned int out;
    char data[RING_BUFFER_SIZE];
} ring_buffer_t;

#define RING_BUFFER_EMPTY(rb) ((rb)->in == (rb)->out)

#define RING_BUFFER_FULL(rb) ((((rb)->in + 1) & RING_BUFFER_MASK) == (rb)->out)

#define RING_BUFFER_LENGTH(rb) (((rb)->in - (rb)->out) & RING_BUFFER_MASK)

#define RING_BUFFER_SPACE(rb) (((rb)->out - (rb)->in - 1) & RING_BUFFER_MASK)

#define RING_BUFFER_ADD(rb, c) \
{ \
    (rb)->data[(rb)->in] = (c); \
    (rb)->in = ((rb)->in + 1) & RING_BUFFER_MASK; \
}

#define RING_BUFFER_REMOVE(rb, c) \
{ \
     (c) = (rb)->data[(rb)->out]; \
    (rb)->out = ((rb)->out + 1) & RING_BUFFER_MASK; \
}

#define RING_BUFFER_RESET(rb) \
{ \
    int priorInterruptState; \
    _DISAB_INTS(priorInterruptState); \
    (rb)->out = (rb)->in = 0; \
    _RESTOR_INT (priorInterruptState); \
}


/* There are 2 buffers for each channel, one for recv and one for transmit, implemented using a ring buffer */
static ring_buffer_t tx_buffer[NUM_NCC_SER_CHAN];
static ring_buffer_t rx_buffer[NUM_NCC_SER_CHAN];


/* Local data structures */
static ONCC_SIO_CHAN_PARAS devParas[] =
{
    {INT_VEC_SERIAL1_RX, SER1RX_INT_LVL, INT_VEC_SERIAL1_TX, SER1TX_INT_LVL},
    {INT_VEC_SERIAL2_RX, SER2RX_INT_LVL, INT_VEC_SERIAL2_TX, SER2TX_INT_LVL},
};

static ONCC_SIO_CHAN_PARAS devDmaParas[] =
{
    {DMA_VEC_SERIAL1_RX, SER1RX_DMA_LVL, DMA_VEC_SERIAL1_TX, SER1TX_DMA_LVL}, 
    {DMA_VEC_SERIAL2_RX, SER2RX_DMA_LVL, DMA_VEC_SERIAL2_TX, SER2TX_DMA_LVL},
};

static ONCC_CHAN onccChan[NUM_NCC_SER_CHAN];
static unsigned int indexOfAvailableOnccStructures = 0;

static void echo (int arg, int channel, void *buffer, int len);

static struct termios current_termios[NUM_NCC_SER_CHAN];
static struct serial_cntr_t serial_counters[NUM_NCC_SER_CHAN];
static struct serial_status_t serial_status[NUM_NCC_SER_CHAN];
static struct sigchange_stats_t serial_sigchange[NUM_NCC_SER_CHAN];

#define _TX_FLOW_CONTROLLED(chan) ((chan->tx_sw_flow) \
                                   || (chan->tx_user_flow) \
                                   || (chan->tx_rtstoggle_flow))

#if eosdebug
static unsigned long iph_up_ctr[2];
static unsigned long iph_down_ctr[2];
static unsigned long rx_buf_maxcount1[2];
static unsigned long rx_buf_maxcount2[2];
static unsigned long lines[2][32];
static unsigned long datalines[2][32];
static unsigned long morelines[2];
static unsigned long nreads[2][5];
#endif

static unsigned long rx_note[NUM_NCC_SER_CHAN];
static unsigned long rx_noted[NUM_NCC_SER_CHAN];


#ifdef SERIAL_FIQ
static int fiq_list[NUM_NCC_SER_CHAN];
static int fiq_ports;
static int fiq_up = 0;
#endif



#define FLOW_REWRITE 1

#ifdef FLOW_REWRITE
static void
change_rx_flow(ONCC_CHAN * pChan)
{
    int flowstate = 0;
    int prevInterruptState;

    _DISAB_INTS (prevInterruptState);

    if (pChan->rx_hw_flow || 
	pChan->rx_sw_flow || 
	pChan->rx_user_flow || 
	pChan->rx_alarm_flow) 
    {
	flowstate = 1;
    }

    if (pChan->sw_flowc) {
	/* waiting for XMITR_RDY */
	while (!(XMITR_RDY_IRQ 
		 & (SER_REG (pChan->base_address, STAT_REG))));
            
		
	SER_REG_BYTE(pChan->base_address, DATA_REG) = 
	    (flowstate) ? pChan->xoff_ch : pChan->xon_ch;

    }

    if (pChan->hw_flowc) {
	UINT32 reg = SER_REG(pChan->base_address, CTRL_REGA);
	if (flowstate) {
	    reg &= ~(RTS_ON | RTS_RECV);
	} else {
	    reg |= RTS_ON;
	}
	SER_REG (pChan->base_address, CTRL_REGA) = reg;
    }
    _RESTOR_INT(prevInterruptState);
}
#endif

void check_rx_flow(ONCC_CHAN * pChannel)
{
    int prevInterruptState;

    _DISAB_INTS (prevInterruptState);

    if ((RING_BUFFER_LENGTH(&rx_buffer[pChannel->serChannel]) 
         < (SW_BUF_THRESH_HOLD)))
    {
        int flowdiff = 0;
        if (pChannel->rx_sw_flow) {
            pChannel->rx_sw_flow = FALSE;
            serial_status[pChannel->serChannel].estat &= ~EV_IPS;
            flowdiff = 1;
        }
        if (pChannel->rx_hw_flow) {
            pChannel->rx_hw_flow = FALSE;
            flowdiff = 1;
        }
        if (flowdiff) {
            change_rx_flow(pChannel);
        }
    }

    _RESTOR_INT(prevInterruptState);
}

static int
DipSwitchMode_int(int portnum)
{
    /* unused parameter */
    (void) portnum;

    return DIPSWITCH_232;
}

/***********************************************************************
 *			DMA implementation funcs begin
 **********************************************************************/
#define NUM_RX_DMA_BUF		32
#define SIZE_RX_DMA_BUF		(SIZE_OF_BUF / NUM_RX_DMA_BUF)

#define RX_BMD_HIWAT		(NUM_RX_DMA_BUF * 2 / 3)
#define RX_BMD_LOWAT		(NUM_RX_DMA_BUF / 3)

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
#define NUM_TX_DMA_BUF		64
#else
#define NUM_TX_DMA_BUF		32
#endif
#define SIZE_TX_DMA_BUF		(SIZE_OF_BUF / NUM_TX_DMA_BUF)


/* DMA transmission channel descriptor */
static volatile fb_buffer_desc_t  tx_bmds[NUM_NCC_SER_CHAN]
                         [NUM_TX_DMA_BUF];  

/* DMA receiving channel descriptor */
static volatile fb_buffer_desc_t  rx_bmds[NUM_NCC_SER_CHAN]
				         [NUM_RX_DMA_BUF];  

static int txlengths[NUM_NCC_SER_CHAN][NUM_TX_DMA_BUF];

/* indices for circular queue processing */
static volatile fb_buffer_desc_t *rx_bmd_start[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t *rx_bmd_end[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t * volatile rx_bmd_in[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t * volatile rx_bmd_out[NUM_NCC_SER_CHAN];
static int rx_bmd_partial[NUM_NCC_SER_CHAN];

static volatile fb_buffer_desc_t *tx_bmd_start[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t *tx_bmd_end[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t * volatile tx_bmd_in[NUM_NCC_SER_CHAN];
static volatile fb_buffer_desc_t * volatile tx_bmd_out[NUM_NCC_SER_CHAN];

static volatile UINT32 *rx_dma_ctrl[NUM_NCC_SER_CHAN];
static volatile UINT32 *rx_dma_status[NUM_NCC_SER_CHAN];
static volatile UINT32 *rx_dma_bmd[NUM_NCC_SER_CHAN];

static volatile UINT32 *tx_dma_ctrl[NUM_NCC_SER_CHAN];
static volatile UINT32 *tx_dma_status[NUM_NCC_SER_CHAN];
static volatile UINT32 *tx_dma_bmd[NUM_NCC_SER_CHAN];

static int dmamode[NUM_NCC_SER_CHAN];
static int prevmode[NUM_NCC_SER_CHAN];

/* serial_dma_wrint
 *
 * Serial DMA write interrupt
 */
static int serial_dma_wrint(void *context)
{
    ONCC_CHAN *pChan = (ONCC_CHAN *)context;
    int channel = pChan->serChannel;
    UINT32 status = *tx_dma_status[channel];
    volatile fb_buffer_desc_t *currbmd = tx_bmd_out[channel];
    ring_buffer_t *queue = &tx_buffer[channel];
    int nbytes=0;

    /* Acknowledge interrupt */
    *tx_dma_status[channel] |= status;

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    onccHandleCTSChange(pChan);
#endif

    /* Determine if there's an error */
    if (status & DMA_ERR_DONE_IP) 
    {
        /* Bad BMD or buffer pointer: Implies queuing code queued wrong */
        assert(0);
    }

#if 0
    if (status & DMA_BUF_NOT_RDY_IP) 
    {
        /* Queue is finished because F was not set on the next BMD */
        *tx_dma_ctrl[channel] &= ~DMA_CHANNEL_ENABLE;
    }
#endif

    if (status & DMA_CHAN_ABORT_IP) 
    {
        assert(0);
    }

#if 0
    if (status & DMA_NORM_DONE_IP) 
#endif
    {
        while (currbmd != tx_bmd_in[channel])
        {
            int bmdidx = currbmd - tx_bmd_start[channel];

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
            onccHandleCTSChange(pChan);
#endif
            if (currbmd->upper_word.bits.full == 1)
                break;

            // No errors to get here
            assert(currbmd->upper_word.bits.buflen == 0); 
            nbytes += txlengths[channel][bmdidx];

            currbmd++;
            if (currbmd == tx_bmd_end[channel]) 
            {
                currbmd = tx_bmd_start[channel];
            }
        }

        assert(nbytes <= RING_BUFFER_SIZE);

        tx_bmd_out[channel] = currbmd;
        serial_counters[channel].tbytes += nbytes;
        /* adjust tx_buffer.out */
        queue->out = (queue->out + (unsigned int) nbytes) & RING_BUFFER_MASK;
    }

    return 0;
}

/* serial_dma_rdint
 *
 * Serial DMA read interrupt
 */
static int serial_dma_rdint(void *context)
{
    ONCC_CHAN *pChan = (ONCC_CHAN *)context;
    int channel = pChan->serChannel;
    volatile fb_buffer_desc_t *currbmd = 0;
    UINT32 status = *rx_dma_status[channel];

    /* Acknowledge interrupt */
    *rx_dma_status[channel] |= status;

    if (status & DMA_CHAN_ABORT_IP) 
    {
        assert(0);
    }

    if (status & DMA_CLAER_ALL_INTERRUPTS & ~DMA_NORM_DONE_IP) 
    {
        if (status & DMA_BUF_NOT_RDY_IP) 
        {
            serial_counters[channel].noflow++;
        } else if (status & DMA_ERR_DONE_IP) 
        {
            assert(0);
        } 
    }

    currbmd = rx_bmd_in[channel];

    while (currbmd->upper_word.bits.full) 
    {
        volatile fb_buffer_desc_t *next_bmd;
        rx_note[channel]++;

        serial_counters[pChan->serChannel].rbytes += 
                                currbmd->upper_word.bits.buflen;
        next_bmd = currbmd + 1;
        if (next_bmd == rx_bmd_end[channel]) 
        {
            next_bmd = rx_bmd_start[channel];
        }
    
        if (next_bmd == rx_bmd_out[channel]) 
        {
            break;
        }
        currbmd = next_bmd;
    } 
    rx_bmd_in[channel] = currbmd;

    if (!(serial_status[channel].estat & EV_IPS) ) 
    {
        int in = rx_bmd_in[channel] - rx_bmd_start[channel];
        int out = rx_bmd_out[channel] - rx_bmd_start[channel];
        UINT32 count;

        if (in >= out)
            count = in - out;
        else
            count = in + NUM_RX_DMA_BUF - out;

        if (count > RX_BMD_HIWAT) 
        {
            //serial_status[channel].estat |= EV_IPS;
            pChan->rx_hw_flow = TRUE; 
            //serl_cb_event(channel, IPS_EVENT);
        
            if (!(serial_status[channel].estat & EV_IPU)) 
            {
                change_rx_flow(pChan);
            }
        }	    	    
    }

    return 0;
}

/* serial_dma_start 
 *
 * To be called to set up the DMA channels for use when O_DMA is used.  */
static void serial_dma_start(ONCC_CHAN *pChan)
{
    int channel = pChan->serChannel;
    int i;

    RING_BUFFER_RESET (&rx_buffer[channel]);       /* reset the receive buffer for channel */
    RING_BUFFER_RESET (&tx_buffer[channel]);       /* reset the send buffer for channel */

    /* Configure BMDs */
    for (i = 0 ; i < NUM_TX_DMA_BUF ; i++) {
        tx_bmds[channel][i].lower_word.bits.wrap = 0;
        tx_bmds[channel][i].lower_word.bits.intr = 0;
        tx_bmds[channel][i].lower_word.bits.last = 0;
        tx_bmds[channel][i].lower_word.bits.sbp  = 0;  
        tx_bmds[channel][i].upper_word.bits.status = 0;
        tx_bmds[channel][i].upper_word.bits.full   = 0;
        tx_bmds[channel][i].upper_word.bits.buflen = 0;
        txlengths[channel][i] = 0;
    }
    /* Last BMD wraps to front */
    tx_bmds[channel][i - 1].lower_word.bits.wrap = 1;

    /* Configure BMD pointers */
    tx_bmd_start[channel] = &tx_bmds[channel][0];
    tx_bmd_end[channel]   = &tx_bmds[channel][NUM_TX_DMA_BUF];
    tx_bmd_in[channel]    = tx_bmd_start[channel];
    tx_bmd_out[channel]   = tx_bmd_start[channel];

    for (i = 0 ; i < NUM_RX_DMA_BUF ; i++) {
	rx_bmds[channel][i].lower_word.bits.wrap = 0;
	rx_bmds[channel][i].lower_word.bits.intr = 0;
	rx_bmds[channel][i].lower_word.bits.last = 0;
	rx_bmds[channel][i].lower_word.bits.sbp  
	    = (unsigned int)&rx_buffer[channel].data[i * SIZE_RX_DMA_BUF];

	rx_bmds[channel][i].upper_word.bits.status = 0;
	rx_bmds[channel][i].upper_word.bits.full   = 0;
	rx_bmds[channel][i].upper_word.bits.buflen = SIZE_RX_DMA_BUF;
	
    }
    /* Last BMD wraps to front */
    rx_bmds[channel][i - 1].lower_word.bits.wrap = 1;

    /* Configure BMD pointers */
    rx_bmd_start[channel] = &rx_bmds[channel][0];
    rx_bmd_end[channel]   = &rx_bmds[channel][NUM_RX_DMA_BUF];
    rx_bmd_in[channel]    = rx_bmd_start[channel];
    rx_bmd_out[channel]   = rx_bmd_start[channel];
    rx_bmd_partial[channel]  = 0;

    /* Configure controller initial register configuration */
    switch(channel) {
    case 0:
	rx_dma_ctrl[channel]   = (UINT32*)NARM_DCR7_ADDR;    
	rx_dma_status[channel] = (UINT32*)NARM_DSIER7_ADDR;
	rx_dma_bmd[channel]    = (UINT32*)NARM_DBDP7_ADDR;

	tx_dma_ctrl[channel]   = (UINT32*)NARM_DCR8_ADDR;
	tx_dma_status[channel] = (UINT32*)NARM_DSIER8_ADDR;
	tx_dma_bmd[channel]    = (UINT32*)NARM_DBDP8_ADDR;
	break;
    case 1:
	rx_dma_ctrl[channel]   = (UINT32*)NARM_DCR9_ADDR;    
	rx_dma_status[channel] = (UINT32*)NARM_DSIER9_ADDR;
	rx_dma_bmd[channel]    = (UINT32*)NARM_DBDP9_ADDR;

	tx_dma_ctrl[channel]   = (UINT32*)NARM_DCR10_ADDR;
	tx_dma_status[channel] = (UINT32*)NARM_DSIER10_ADDR;
	tx_dma_bmd[channel]    = (UINT32*)NARM_DBDP10_ADDR;
	break;
    };

    *rx_dma_ctrl[channel]   = (*rx_dma_ctrl[channel] 
			       & ~(BURST_BANDWIDTH_MASK | BUS_BANDWIDTH_MASK))
			       | (FLY_BY_WRITE);
    *rx_dma_status[channel] = (DMA_CLAER_ALL_INTERRUPTS | DMA_NORM_DONE_IE 
			       | DMA_ERR_DONE_IE | DMA_CHAN_ABORT_IE);
    *rx_dma_bmd[channel]    = (UINT32)&rx_bmds[channel][0];

    *tx_dma_ctrl[channel]   = (*tx_dma_ctrl[channel] 
			       & ~(BURST_BANDWIDTH_MASK | BUS_BANDWIDTH_MASK))
			       | (FLY_BY_READ);
    *tx_dma_status[channel] = (DMA_CLAER_ALL_INTERRUPTS | DMA_NORM_DONE_IE 
			       | DMA_ERR_DONE_IE | DMA_CHAN_ABORT_IE);
    *tx_dma_bmd[channel]    = (UINT32)&tx_bmds[channel][0];

    /* Install DMA interrupt */
    NAInstallIsr(devDmaParas[channel].rxvector,
		 (NA_ISR_HANDLER)serial_dma_rdint,
		 (void *)pChan);

    NAInstallIsr(devDmaParas[channel].txvector,
		 (NA_ISR_HANDLER)serial_dma_wrint,
		 (void *)pChan);

    /* Tell serial channel we're using DMA w/ it */
    SER_REG(pChan->base_address, CTRL_REGA) |= (XMITR_DMA_ON | RECV_DMA_ON);

    /* Enable receive channel */
    *rx_dma_ctrl[channel] |= DMA_CHANNEL_ENABLE;

}

/* serial_dma_stop
 *
 * To be called when exiting a session where O_DMA was active.
 * Disables the DMA channel
 */
static int serial_dma_stop(ONCC_CHAN *pChan)
{
    int channel = pChan->serChannel;

    /* drain all data */ 
    netos_serlioctl(channel, TCDRAIN, 0); 

    /* Go back to FIFO mode */
    SER_REG(pChan->base_address, CTRL_REGA) &= ~(XMITR_DMA_ON | RECV_DMA_ON);

    /* Disable receive channels */
    *rx_dma_ctrl[channel] &= ~(DMA_CHANNEL_ENABLE);
    *tx_dma_ctrl[channel] &= ~(DMA_CHANNEL_ENABLE);

    /* Uninstall DMA interrupt */
    /* Don't need to with channel disabled */

    /* Sanitize buffers for interrupt/polling use */
    /* Should be in the same shape they were before use */

    return 0;
}

static int InsertUsersDataBufferToDMA (ONCC_CHAN *pChan, char *userBuffer, int szBuffer)
{
    int bmdidx, data_available, nbytes=0;
    int bytesToCopyPhase1;
    int channel = pChan->serChannel;
    int prevInterruptState;
    ring_buffer_t *queue = &tx_buffer[channel];
    volatile fb_buffer_desc_t *currbmd = tx_bmd_in[channel];
    volatile fb_buffer_desc_t *nextbmd;

    nextbmd = currbmd+1;

    if (nextbmd==tx_bmd_end[channel])
        nextbmd = tx_bmd_start[channel];

    _DISAB_INTS (prevInterruptState);

    /* Is DMA buffer descriptor available? */
    if (nextbmd == tx_bmd_out[channel])
        goto done_dma_wr;

    /* how much space is available?     */
    data_available = RING_BUFFER_SPACE(queue);

    if (data_available == 0)
        goto done_dma_wr;
    
    /*  insert only the number of bytes that will fit in    */
    nbytes = (data_available > szBuffer) ? szBuffer : data_available;

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    nbytes = (nbytes > NA_MAX_SKID_RATE)?NA_MAX_SKID_RATE:nbytes;
#endif

    if ((queue->in + nbytes) <= RING_BUFFER_SIZE)
    {
        /*  one copy will do the trick  */
        #ifdef _LITTLE_ENDIAN
        memcpy (queue->data + queue->in, userBuffer, nbytes);
        #else
        fmemcpy (queue->data + queue->in, userBuffer, nbytes);
        #endif
        currbmd->lower_word.bits.sbp = 
                        (unsigned int)&queue->data[queue->in];
        currbmd->upper_word.bits.buflen = nbytes;
        currbmd->upper_word.bits.full = 1;
        currbmd->upper_word.bits.status = 0;
        bmdidx = currbmd - tx_bmd_start[channel];
        txlengths[channel][bmdidx] = nbytes;
        currbmd = nextbmd;
    }
    else
    {
        /*  insert the data in two moves   */
        bytesToCopyPhase1 = RING_BUFFER_SIZE - queue->in;

        #ifdef _LITTLE_ENDIAN
        memcpy (queue->data + queue->in, userBuffer, bytesToCopyPhase1);
        #else
        fmemcpy (queue->data + queue->in, userBuffer, bytesToCopyPhase1);
        #endif
        currbmd->lower_word.bits.sbp = 
                        (unsigned int)&queue->data[queue->in];
        currbmd->upper_word.bits.buflen = bytesToCopyPhase1;
        currbmd->upper_word.bits.full = 1;
        currbmd->upper_word.bits.status = 0;
        bmdidx = currbmd - tx_bmd_start[channel];
        txlengths[channel][bmdidx] = bytesToCopyPhase1;

        currbmd = nextbmd;
        nextbmd++;

        if (nextbmd==tx_bmd_end[channel])
            nextbmd = tx_bmd_start[channel];

        if (nextbmd == tx_bmd_out[channel])
        {                        
            nbytes = bytesToCopyPhase1;
            goto done_dma_wr;    
        }
        else
        {
            #ifdef _LITTLE_ENDIAN
            memcpy (queue->data, userBuffer + bytesToCopyPhase1, nbytes - bytesToCopyPhase1);
            #else
            fmemcpy (queue->data, userBuffer + bytesToCopyPhase1, nbytes - bytesToCopyPhase1);
            #endif
            currbmd->lower_word.bits.sbp = (unsigned int)queue->data;
            currbmd->upper_word.bits.buflen = nbytes - bytesToCopyPhase1;
            currbmd->upper_word.bits.full = 1;
            currbmd->upper_word.bits.status = 0;
            bmdidx = currbmd - tx_bmd_start[channel];
            txlengths[channel][bmdidx] = nbytes - bytesToCopyPhase1;
            currbmd = nextbmd;
        }
    }

    done_dma_wr:
    tx_bmd_in[channel] = currbmd;
    queue->in = (queue->in + (unsigned int)nbytes) & RING_BUFFER_MASK;
    *tx_dma_ctrl[channel] |= DMA_CHANNEL_ENABLE;
    _RESTOR_INT(prevInterruptState);

    return nbytes;
}

/* serial_dma_write
 *
 * Queue data for transmit when O_DMA is configured
 */
    /* If TX BMD is free
     *     Set up BMD to transmit user buffer
     *     Wait for transmit to complete
     * else
     *     Return error
     */

    /* OR */

    /* If there are TX BMD(s) free
     *     Copy data into tx buffer
     *     Configure TX BMD(s) to transmit data
     * else
     *     Return error
     */
static int 
serial_dma_write(ONCC_CHAN *pChan, void * buffer, 
		 int length, int *bytesWritten) 
{
    int bytesToWrite = 0;
    int bytesDma = 0;
    int bytesLeft = length;
    char *data = (char *)buffer;

    while (bytesToWrite < length)
    {
        bytesDma = InsertUsersDataBufferToDMA(pChan, &data[bytesToWrite], bytesLeft);
        if (bytesDma > 0)
        {
            bytesToWrite += bytesDma;
            bytesLeft -= bytesDma;
        }
        else
            break;
    }

    *bytesWritten = bytesToWrite;  /* bytesWritten has number of type sent */

    return 0;
}

/* serial_dma_read
 *
 * read data when O_DMA is configured
 */
static int
serial_dma_read(ONCC_CHAN * pChan, char * buffer, int length, int *bytesRead)
{
    int prevInterruptState;
    volatile fb_buffer_desc_t *currbmd;
    int channel = pChan->serChannel;
    int ret = 0;

    /* While we have not fulfilled user requested data size
     *     Copy data from RX DMA buffers to user
     */

    currbmd = rx_bmd_out[channel];
    while (length) {
	int bmdlen;
	char *srcaddr;
	int tocopy;
		
        int dobreak = 0;
	/* Read blocking */
	while (currbmd == rx_bmd_in[channel]) {
	    // No more data to read right now
	    if (pChan->openmode & O_NONBLOCK) {
		ret = (*bytesRead) ? 0 : EAGAIN;
		dobreak = 1;
		break;
	    } else {
		// Block
		if (*bytesRead) {
		    dobreak = 1;
		    break; /* We've read data, no need to block */
		} else {
                    tx_thread_sleep(1);
		    currbmd = rx_bmd_out[channel];
		}
	    }
	}
	if (dobreak == 1) {
	    break;
	}

	bmdlen = currbmd->upper_word.bits.buflen - rx_bmd_partial[channel];
	srcaddr = (char *)
	    (currbmd->lower_word.bits.sbp + rx_bmd_partial[channel]);
	tocopy = (length > bmdlen) ? bmdlen : length;

    #ifdef _LITTLE_ENDIAN
	memcpy(buffer, srcaddr, tocopy);
    #else
	fmemcpy(buffer, srcaddr, tocopy);
    #endif
	buffer += tocopy;
	length -= tocopy;
	bmdlen -= tocopy;
	rx_bmd_partial[channel] += tocopy;
	*bytesRead += tocopy;
	
	/* After consuming a BMD, return it to the correct size and move out*/
	if (bmdlen == 0) {
	    currbmd->upper_word.bits.buflen = SIZE_RX_DMA_BUF;
	    currbmd->upper_word.bits.full = 0;
	    
	    currbmd++;

	    if (currbmd == rx_bmd_end[channel]) {
		currbmd = rx_bmd_start[channel];
	    }

	    rx_bmd_partial[channel] = 0;
	    rx_bmd_out[channel] = currbmd;
	}
    }
    
    /* We may have made it possible to proceed after an NRIP condition
     * Hit the NRIP bit to possibly resume transfer */
    _DISAB_INTS (prevInterruptState);
    *rx_dma_status[channel] = (*rx_dma_status[channel]
			       & ~(DMA_CLAER_ALL_INTERRUPTS)) 
                               | DMA_BUF_NOT_RDY_IP;
    _RESTOR_INT(prevInterruptState);

    /* Release flow control if necessary */
    //if(serial_status[channel].estat & EV_IPS) {
    if (pChan->rx_hw_flow == TRUE) {
	int in  = rx_bmd_in[channel]  - rx_bmd_start[channel] ;
	int out = rx_bmd_out[channel] - rx_bmd_start[channel] ;
	UINT32 count ;
	if(in >= out)
	    count = in - out ;
	else
	    count = in + NUM_RX_DMA_BUF - out ;

	if(count < RX_BMD_LOWAT) {
	    //_DISAB_INTS (prevInterruptState, prevFIQState);
	    //serial_status[channel].estat &= ~EV_IPS;
	    //_RESTOR_INT(prevInterruptState, prevFIQState);
	    pChan->rx_hw_flow = FALSE; 
	    //serl_cb_event(channel, IPS_EVENT);
	    // if not also stopped unconditionally
	    if(!(serial_status[channel].estat & EV_IPU)) {
		change_rx_flow(pChan) ;
	    }
	}
    }
    
    return ret;
}

/***********************************************************************
 *			DMA implementation funcs end
 **********************************************************************/

static int InsertUsersDataBuffer (ring_buffer_t *queue, char *userBuffer, int szBuffer)
{
    int data_available, nbytes, bytesToCopyPhase1;

    /* how much space is available?     */
    data_available = RING_BUFFER_SPACE(queue);
    
    /*  insert only the number of bytes that will fit in    */
    nbytes = (data_available > szBuffer) ? szBuffer : data_available;

    if ((queue->in + nbytes) <= RING_BUFFER_SIZE)
    {
        /*  one copy will do the trick  */
        #ifdef _LITTLE_ENDIAN
        memcpy (queue->data + queue->in, userBuffer, nbytes);
        #else
        fmemcpy (queue->data + queue->in, userBuffer, nbytes);
        #endif
    }
    else
    {
        /*  insert the data in two moves   */
        bytesToCopyPhase1 = RING_BUFFER_SIZE - queue->in;

        #ifdef _LITTLE_ENDIAN
        memcpy (queue->data + queue->in, userBuffer, bytesToCopyPhase1);
        memcpy (queue->data, userBuffer + bytesToCopyPhase1, nbytes - bytesToCopyPhase1);
        #else
        fmemcpy (queue->data + queue->in, userBuffer, bytesToCopyPhase1);
        fmemcpy (queue->data, userBuffer + bytesToCopyPhase1, nbytes - bytesToCopyPhase1);
        #endif
    }

    queue->in = (queue->in + (unsigned int)nbytes) & RING_BUFFER_MASK;

    return nbytes;
}

static int ExtractSerialDataIntoUsersBuffer (ring_buffer_t *queue, char *userBuffer, int szBuffer)
{
    int data_available, nbytes, bytesToCopyPhase1;

    /* how much data is ready?  */
    data_available = RING_BUFFER_LENGTH(queue);
    
    /*  extract only the number of bytes the user wants */
    nbytes = (data_available > szBuffer) ? szBuffer : data_available;

    if ((queue->out + nbytes) > RING_BUFFER_SIZE)
    {
        /*  extract the data in two moves   */
        bytesToCopyPhase1 = RING_BUFFER_SIZE - queue->out;

        #ifdef _LITTLE_ENDIAN
        memcpy (userBuffer, queue->data + queue->out, bytesToCopyPhase1);
        memcpy (userBuffer + bytesToCopyPhase1, queue->data, nbytes - bytesToCopyPhase1);
        #else
        fmemcpy (userBuffer, queue->data + queue->out, bytesToCopyPhase1);
        fmemcpy (userBuffer + bytesToCopyPhase1, queue->data, nbytes - bytesToCopyPhase1);
        #endif
    }
    else
    {
        /*  one copy will do the trick  */
        #ifdef _LITTLE_ENDIAN
        memcpy (userBuffer, queue->data + queue->out, nbytes);
        #else
        fmemcpy (userBuffer, queue->data + queue->out, nbytes);
        #endif
    }

    queue->out = (queue->out + (unsigned int) nbytes) & RING_BUFFER_MASK;

    return nbytes;
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccDummyCallback()                                        ***
 ***                                                                            ***
 *** Called by:      onccSioDevInit()                                           ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments:  Dummy callback routine. By default this driver installs dummy   ***
 *** callback routines which do nothing.  A higher layer library that wants to  ***
 *** use this driver (e.g. ttyDrv) will install its own callback routine using  ***
 *** the SIO_INSTALL_CALLBACK ioctl command.                                    ***
 **********************************************************************************
 **********************************************************************************/
static STATUS onccDummyCallback (void)
{
    return (ERROR);
}


/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  OptSet()                                                   ***
 ***                                                                            ***
 *** Called by:      onccIoctl(),onccInitChannel()                              ***
 ***                                                                            ***
 *** Comments: This routine sets up the hardware (serial Control Register A)    ***
 *** depending on the option argument.  If the hardware cannot support the      ***
 *** particular option then it ignores that part of the request.                ***
 *** Returns OK upon success, or EIO for invalid arguments.                     ***
 **********************************************************************************
 **********************************************************************************/
static STATUS OptSet (ONCC_CHAN * pChan, int newOpts)
{
    UINT32 reg;
    int prevInterruptState;
    STATUS status = OK;

    if ((pChan == NULL) || (newOpts & 0xfffffe00))
    {
        return EIO;
    }

    _DISAB_INTS (prevInterruptState);

    /* Get current control register settings */
    reg = SER_REG (pChan->base_address, CTRL_REGA);

    /* Reset current option settings */
    reg &= REGA_OPTIONS_MASK;
 
    /* Channel is always enabled */
    reg |= SER_CHANNEL_ENABLE;

    /* We don't support HUPCL */
    newOpts &= ~inHUPCL;

    /* Set databits */
    switch (newOpts & inCSIZE)
    {
    case inCS5:
        reg |= CTRL_FIVE_BITS;
        break;

    case inCS6:
        reg |= CTRL_SIX_BITS;
        break;

    case inCS7:
        reg |= CTRL_SEVEN_BITS;
        break;

    default:
    case inCS8:
        reg |= CTRL_EIGHT_BITS;
        break;
    }

    /* Set stopbits */
    if (newOpts & inSTOPB)
    {
        reg |= CTRL_TWO_STOP_BITS;
    }
    else
    {
        reg |= CTRL_ONE_STOP_BIT;
    }

    /* Set parity */
    if (newOpts & inPARENB) 
    { 
	if (newOpts & inXPAR) 
        { 
            /* Mark or space parity */
	    if (newOpts & inPARODD) 
            { 
		reg |= CTRL_MARK_PARITY;
	    } 
            else
            { 
		reg |= CTRL_SPACE_PARITY;
	    }
	} 
        else
        {
            /* Odd or even parity */
	    if (newOpts & inPARODD) 
            { 
		reg |= CTRL_ODD_PARITY;
	    } 
            else
            { 
		reg |= CTRL_EVEN_PARITY;
	    }
	}
    } 
    else 
    {
        reg &= CTRL_NO_PARITY;
    }
    
    /* Turn off hardware flowcontrol */
    if (((newOpts & SIO_HW_RTS_CTS_HANDSHAKING) == 0) &&
        (pChan->hw_flowc == TRUE))
    {
        /* Disable current rx flowcontrol state */
        if (pChan->rx_hw_flow == TRUE)
        {
            pChan->rx_hw_flow = FALSE;
            change_rx_flow(pChan);
        }

        /* Disable hardware flowcontrol itself */
        pChan->hw_flowc = FALSE;
        pChan->opMode &= ~OP_MODE_HW_HANDSHAKE;
        reg &= ~(CTS_XMIT | RTS_RECV);
    }

    /* Turn off software flowcontrol */
    if (((newOpts & SIO_SW_HANDSHAKING) == 0) &&
        (pChan->sw_flowc == TRUE))
    {
        /* Disable current rx flowcontrol state */
        if (pChan->rx_sw_flow == TRUE)
        {
            pChan->rx_sw_flow = FALSE;
            serial_status[pChan->serChannel].estat &= ~EV_IPS;
            change_rx_flow(pChan);
        }

        /* Disable current tx flowcontrol state */
        if (pChan->tx_sw_flow == TRUE)
        {
            pChan->tx_sw_flow = FALSE;
        }

        /* Disable software flowcontrol itself */
        pChan->sw_flowc = FALSE;
        pChan->opMode &= ~OP_MODE_SW_HANDSHAKE;
    }

    
    //TODO: Eventually rather than have flow control modes be
    //exclusive, we'd like to have them be able to be used in concert

    /* Turn on hardware flowcontrol */
    if ((newOpts & SIO_HW_RTS_CTS_HANDSHAKING) &&
        (pChan->hw_flowc == FALSE))
    {
        pChan->hw_flowc = TRUE;
        pChan->opMode  |= OP_MODE_HW_HANDSHAKE;
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
        reg |= (RTS_ON | RECV_CTS);
        naInterruptEnable (pChan->txlevel);          
#else
        reg |= (RTS_ON | CTS_XMIT);
#endif
    }

    /* Turn on software flowcontrol */
    if ((newOpts & SIO_SW_HANDSHAKING) &&
        (pChan->sw_flowc == FALSE))
    {
        if (pChan->openmode & O_DMA)
        {
            /* Software flowcontrol does not work in DMA mode */
            status = EIO;
            goto EXIT;
        }

        pChan->sw_flowc = TRUE;
        pChan->xon_ch   = current_termios[pChan->serChannel].c_cc[VSTART];
        pChan->xoff_ch  = current_termios[pChan->serChannel].c_cc[VSTOP];
        pChan->opMode  |= OP_MODE_SW_HANDSHAKE;
    }

    if (newOpts & (SIO_HW_RTS_CTS_HANDSHAKING | SIO_SW_HANDSHAKING))
    {
        pChan->opMode &= ~OP_MODE_NO_HANDSHAKE;
    }
    else
    {
       
       pChan->opMode  |= OP_MODE_NO_HANDSHAKE;
    }

#ifdef DEBUG_SERIAL
    printf("rdy= %d, bc=%d recvc=%d  \n",rrdyc,rbcc,recvc);
    rrdyc = 0;
    rbcc = 0;
    xonc = 0;
    xoffc = 0;
    recvc=0;
#endif


    /* Reset the device according to the new options */
    SER_REG (pChan->base_address, CTRL_REGA) = reg;

    /* Save copy of options */
    pChan->options = newOpts;

    if ( pChan->sw_flowc)
    {
        /* For software flow control setup character matching of xon/xoff 
           characters. */ 

        reg = SER_REG (pChan->base_address, CTRL_REGB);  /* get current register B settings */
        reg |= (RECV_DATA_MATCH1_ON | RECV_DATA_MATCH2_ON);     /* turn on Receive Data Match1 and 2 */
        SER_REG (pChan->base_address, CTRL_REGB) = reg;  /* reset the device according to new options */
        reg = SER_REG (pChan->base_address, RECV_MATCH_REG);  /* This sets the Receive Match Registor */
        reg = ((0x000000ff & XON) << 24) + ((0x000000ff & XOFF)<<16);
        SER_REG (pChan->base_address, RECV_MATCH_REG) = reg;

        /* SER_REG (pChan->base_address, STAT_REG) |= CLR_ALL_RX_CONDITIONS; */

#ifdef DEBUG_SERIAL
        printf("enable xmitr_rdy_irq \n");
#endif
    }
    else
    {
        reg = SER_REG (pChan->base_address, CTRL_REGB);  /* get current register B settings */
        reg &= ~(RECV_DATA_MATCH1_ON | RECV_DATA_MATCH2_ON);     /* turn off Receive Data Match1 and 2 */
        SER_REG (pChan->base_address, CTRL_REGB) = reg;  /* reset the device according to new options */
    }

    if (!(pChan->openmode & O_DMA))
    {
        /* After making possible changes to flow control we should
           restart the tx interrupt.
        */
        onccTxStartup((SIO_CHAN*) pChan);
    }

EXIT:
    _RESTOR_INT (prevInterruptState);

    return status;
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  get_baud_div()                                             ***
 ***                                                                            ***
 *** Called by:      onccInitChannel(), onccIoctl()                             ***
 ***                                                                            ***
 *** Comments: This routine returns the baud rate divisor which is required     ***
 *** to set up the hardware for the desired baudrate.  Also returns STATUS:     ***
 *** STATUS = to the minimum error for the baudrate using the divr value.       ***
 ***                                                                            ***
 *** This function calculates the optimum N value (Divr based on the values     ***
 *** of SysClk or Xtal Frequency.                                               ***
 **********************************************************************************
 **********************************************************************************/
struct baudTable {
    unsigned int value;
    unsigned int divr_or;
};
enum baudClkIdx {
    XTAL_IDX,
    SYS_IDX,
};

static struct baudTable Clocks[] = {
    { 1 /* Xtal placeholder */, BIT_RATE_CLK_EXT }, //XTAL_IDX
    { 1 /* Sysclk placeholder */, BIT_RATE_CLK_SYS }, //SYS_IDX
    {0,0},
};

static struct baudTable Clockmodes[] = {
    {16, BIT_RATE_X16},
    {8, BIT_RATE_X1 | BIT_TDCR_RDCR_X8 | BIT_RATE_RX_DPLL},
    {32, BIT_RATE_X1 | BIT_TDCR_RDCR_X32 | BIT_RATE_RX_DPLL},
    //{1, BIT_RATE_X1 | BIT_RATE_RX_DPLL},
    {0,0},
};

#if 1
static STATUS
get_baud_div(int baudrate, UINT32 *divr)
{
    const unsigned int ELEVEN_BIT_MAX = 2047;
    unsigned int Clk, bestClkIdx = 0;
    unsigned int clockMode, bestModeIdx = 0;
    unsigned int err, bestErr = 0xffffffff;
    unsigned int N, bestN = ELEVEN_BIT_MAX + 1;
    int clkindex = 0;
    int modeindex = 0;

    /* Iterate through clocks */
    while (Clocks[clkindex].value) {
	switch(clkindex) {
	case XTAL_IDX:
	    Clk = NAgetXtalFreq();
	    break;
	case SYS_IDX:
	    Clk = NAgetSysClkFreq();
	    break;
	default:
	    assert(0);
	    return 100; /* 100% error */
	};
	
	modeindex = 0;
	/* Iterate through divisors */
	while ( (clockMode = Clockmodes[modeindex].value) ) {

	    /* Compute N */
	    N = Clk / (clockMode * 2 * baudrate) - 1;

	    /* Round up? */
	    if ( (Clk % (clockMode * 2 * baudrate)) 
		 > (clockMode * baudrate)) 
	    {
		/* remainder is more than half */
		N++;
	    }

	    /* Compute err */
	    if (N > ELEVEN_BIT_MAX) {
		err = 0xffffffff;
	    } else {
		unsigned int realBaud = Clk / (clockMode * 2 * (N + 1)) ;
		if ((int)realBaud > baudrate) {
		    err = realBaud - baudrate;
		} else {
		    err = baudrate - realBaud;
		}
	    }

	    if (err < bestErr) {
		bestN = N;
		bestErr = err;
		bestModeIdx = modeindex;
		bestClkIdx = clkindex;
	    }

	    if (bestErr == 0) {
		break;
	    }
	    modeindex++;
	}

	if (bestErr == 0) {
	    break;
	}
	clkindex++;
    }

    if (bestN == ELEVEN_BIT_MAX + 1) {
	return 100;
    }

    if (bestErr == 0xffffffff) {
	return 100;
    }

    *divr = Clocks[bestClkIdx].divr_or 
	| Clockmodes[bestModeIdx].divr_or
	| bestN;

    return bestErr * 100 / baudrate;
    
}
#else
static STATUS get_baud_div (int baudrate, UINT32 *divr)
{
#if PROCESSOR == ns7520 || PROCESSOR == net50	/* TODO: Only needed until mercury driver available. */
    const int UART_TIMING_X1_MULT = 1;
    const int UART_TIMING_X8_MULT = 8;
    const int UART_TIMING_X16_MULT = 16;
    const int ELEVEN_BIT_MAX = 2047;
    unsigned int Nsysclk, Nxtal;
    unsigned int Esysclk, Extal;
	unsigned int clockMode = 16;

	if ((narm_read_reg(NARM_GSSR_REG, NARM_GSSR_ADDR, rev) >= NETA20UM) && (baudrate > 115200))
	{
		if (NAgetSysClkFreq() >= 55000000)
		{
			/*for 55MHZ clock, On 20UM chip, and high baud rate, TMODE =1 and TDCR/RDCR = 00/00 to lower the error */
			clockMode = UART_TIMING_X1_MULT;   // formally 1
			*divr = BIT_RATE_X1;
		}
		else
		{
			/*for 36MHZ and 46MHZ, On 20UM chip, and high baud rate, TMODE =1 and TDCR/RDCR = 01/01 to lower the error */
			clockMode = UART_TIMING_X8_MULT; // formally 8
			*divr = BIT_RATE_X1 | BIT_TDCR_RDCR_X8;
		}
	}
	else
	{
		clockMode = UART_TIMING_X16_MULT;   // formally 16
		*divr = BIT_RATE_X16;
	}
	#ifdef debug_serial
    printf("clockMode is: %d,", clockMode);
	#endif
    /*  compute the N value for sysclk  */
    Nsysclk = (NAgetSysClkFreq() / (clockMode * 2 * baudrate)) - 1;

    /*  check to round up   */
    if ( (NAgetSysClkFreq() % (clockMode * 2 * baudrate)) 
	 > (clockMode * baudrate))
    {
        /*  the remainder was greater than half the previous denominator    */
        Nsysclk++;
    }

    /*  The n parameter can not be greater than 11 bits of storage  */
    if (Nsysclk > ELEVEN_BIT_MAX)   // formally 2047
    {
        /*  error is 100 %  */
        Esysclk = baudrate;
    }
    else
    {
        /*  compute the delta using the sysclk -- delta should always be positive  */
        if ((NAgetSysClkFreq() / (clockMode * 2 * (Nsysclk + 1))) > baudrate)
        {
            Esysclk = (NAgetSysClkFreq() / (clockMode * 2 * (Nsysclk + 1))) - baudrate;
        }
        else
        {
            Esysclk = baudrate - (NAgetSysClkFreq() / (clockMode * 2 * (Nsysclk + 1)));
        }

    }

    /*  compute the N value for xtal  */
    if ((NAgetXtalFreq() / (clockMode * 2 * baudrate)) >= 1)  /* in case baudrate is > 1Mhz */
	Nxtal = (NAgetXtalFreq() / (clockMode * 2 * baudrate)) - 1;
    else
	Nxtal = 0;

    /*  check to round up   */
    if ( (NAgetXtalFreq() % (clockMode * 2 * baudrate)) 
	 > (clockMode * baudrate) )
    {
        /*  remainder exists.    */
        Nxtal++;
    }

    /*  The n parameter can not be greater than 11 bits of storage  */
    if (Nxtal > ELEVEN_BIT_MAX)   // formlly 2047
    {
        /*  error is 100 %  */
        Extal = baudrate;
    }
    else
    {
        /*  compute the delta using the xtal -- delta should always be positive  */
        if ((NAgetXtalFreq() / (clockMode * 2 * (Nxtal + 1))) > baudrate)
        {
            Extal = (NAgetXtalFreq() / (clockMode * 2 * (Nxtal + 1))) - baudrate;
        }
        else
        {
            Extal = baudrate - (NAgetXtalFreq() / (clockMode * 2 * (Nxtal + 1)));
        }
    }
	#ifdef debug_serial
	printf("@baudrate %d, Esysclk is %d, Extal is %d, ", baudrate, Esysclk, Extal);
	#endif
 
    /*  pick the best scenario  */
    if (Esysclk < Extal)
    {

        /*  sysclk error less than or equal to XTAL clock  */
        *divr |= BIT_RATE_CLK_SYS | Nsysclk;

        /*  return the error in percent */
		#ifdef debug_serial
		printf("Nsysclk is %d, Esysclk error in percent is %d\n",Nsysclk, (Esysclk * 100) / baudrate);
		#endif
        return (Esysclk * 100) / baudrate;
    }
    else
    {
        /*  sysclk error greater than XTAL clock  */
        *divr |= BIT_RATE_CLK_EXT | Nxtal;
        #ifdef debug_serial
		printf("Nxtal is %d, Extal error in percent is %d\n",Nxtal, (Extal * 100) / baudrate);
	#endif
        /*  return the error in percent */
        return (Extal * 100) / baudrate;
    }
#else
    return 0;
#endif
}
#endif


/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  get_char_gap_count()                                       ***
 ***                                                                            ***
 *** Called by:      onccInitChannel(), onccIoctl()                             ***
 ***                                                                            ***
 *** Comments: This routine returns the value for the receive character gap     ***
 *** timer. The following equation is from the NCC manual and is supposed to    ***
 *** result in a value which is 10 times the character period for the channel   ***
 *** bit rate.                                                                  ***
 *** CT = [(10*Fxtal)/(baudrate * 512)] - 1                                 ***
 **********************************************************************************
 **********************************************************************************/

static int
get_char_gap_count (int baudrate, unsigned int bitgap)
{
   UINT32 ct = MIN_GAP;

   if ((baudrate > MIN_BAUD_RATE) && (baudrate <= MAX_BAUD_RATE)) {
       ct = (UINT32)((bitgap * NAgetXtalFreq()) / (baudrate * 512)) - 1;
   }
   return (ct);
}




/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccInitChannel()                                          ***
 ***                                                                            ***
 *** Called by:      onccSioDevInit()                                           ***
 ***                                                                            ***
 *** Comments:  Hardware initialization of the NCC serial channel.       		***
 **********************************************************************************
 **********************************************************************************/
static void onccInitChannel (ONCC_CHAN * pChan)
{
    UINT32 divr;
    UINT32 reg;

    /*** Receive Match Register Configuration ***/
    reg = 0;
    SER_REG (pChan->base_address, RECV_MATCH_REG) = reg;

    /*** Receive Buffer Gap Timer Register Configuration ***/
    reg = 0;
    SER_REG (pChan->base_address, RECV_BUF_TIMER_REG) = reg;
    pChan->baudRateError = DEFAULT_BAUD_RATE_ERROR;

    /*** Bit-Rate (baud) Register Configuration ***/
    reg = BIT_GEN_ON | BIT_RATE_X16 | RECV_CLK_SRC_INT | XMIT_CLK_SRC_INT | RECV_CLK_OUT_OFF | XMIT_CLK_OUT_OFF | BIT_RATE_CLK_EXT;
    if (get_baud_div (pChan->baudRate, &divr) > pChan->baudRateError)
      pChan->baudRate = DEFAULT_BAUD_RATE;  /* if baudrate was invalid, then fix it */

    reg |= divr;   /* get_baud_div() returns default divr */
    SER_REG (pChan->base_address, BAUD_REG) = reg;

    /*** Receive Character Timer Register Configuration ***/
    reg = get_char_gap_count(pChan->baudRate, 
			     current_termios[pChan->serChannel].c_cc[VBITGAP]);
    SER_REG (pChan->base_address, RECV_CHAR_TIMER_REG) = ENABLE_TIMER | reg;

    /*** Control Register B Configuration ***/
    /* Match timer features are not used */
    reg = RECV_CHAR_GAP_TIMER_ON;
    SER_REG (pChan->base_address, CTRL_REGB) = reg;

    /*** Control Register A Configuration ***/
    /* Enable the serial channel */
    SER_REG (pChan->base_address, CTRL_REGA) = 0;  /* init ctrl reg, this turns off ints */
    OptSet (pChan, pChan->options);    /* go set up options in Reg A */

   // Any potrntial problem with baud rate and gap timers
   // will be fixed in the netos_serlopen
}

#ifdef SERIAL_FIQ
void
realSerFIQ_Handler(void)
{
    int i;

#if eosdebug
    static unsigned int fiq_counter = 0;
    fiq_counter++;
#endif

    for ( i = 0 ; i < NUM_NCC_SER_CHAN ; i++ ) {
	if (fiq_list[i]) {
	    onccIntRd(&onccChan[i]);
	}
    }
#if PROCESSOR == ns7520 || PROCESSOR == net50	/* TODO: Only needed until mercury driver available. */
    *NARM_T2SR = 0x40000000;
#endif
    return;
}
#else

void
realSerFIQ_Handler(void)
{
}
#endif

#if 0
void 
serFIQ_Handler(void) 
{
}
#else
extern void serFIQ_Handler(void);
#endif





static void predelay_fn(ULONG param)
{ 
    int prevInterruptState;
    ONCC_CHAN *pChan;

    pChan = (ONCC_CHAN *) param;
    assert(pChan != NULL);

    _DISAB_INTS (prevInterruptState);

    /* Remove flowcontrol */
    assert(pChan->tx_rtstoggle_flow);
    pChan->tx_rtstoggle_flow = FALSE;

    /* Start transmitter */
    onccTxStartup ((SIO_CHAN*) pChan);

    _RESTOR_INT (prevInterruptState);

}

static void postdelay_fn(ULONG param)
{
    int prevInterruptState;
    UINT32 reg;
    ONCC_CHAN *pChan;    

    pChan = (ONCC_CHAN *) param;
    assert(pChan != NULL);

    _DISAB_INTS (prevInterruptState);

    /* Only if we have eaten up all chars do we clear RTS */
    if (pChan->chars2Encapsuate == 0)
    {
        /*  Lower the RTS signal */
        reg = (SER_REG (pChan->base_address, CTRL_REGA) & (~RTS_ON));
        SER_REG (pChan->base_address, CTRL_REGA) = reg;
    }

    _RESTOR_INT (prevInterruptState); 
}

static void start_predelay_timer(ONCC_CHAN *pChan)
{
    UINT tx_retval;

    tx_retval = tx_timer_deactivate(&pChan->predelay_timer);
    assert(tx_retval == TX_SUCCESS);

    tx_retval = tx_timer_change(&pChan->predelay_timer, 
                                pChan->predelay_timeout,
                                0);
    assert(tx_retval == TX_SUCCESS);

    
    tx_retval = tx_timer_activate(&pChan->predelay_timer);
    assert(tx_retval == TX_SUCCESS);
}

static void stop_predelay_timer(ONCC_CHAN *pChan)
{
    UINT tx_retval;

    tx_retval = tx_timer_deactivate(&pChan->predelay_timer);
    assert(tx_retval == TX_SUCCESS);
}

static void start_postdelay_timer(ONCC_CHAN *pChan)
{
    UINT tx_retval;

    tx_retval = tx_timer_deactivate(&pChan->postdelay_timer);
    assert(tx_retval == TX_SUCCESS);

    tx_retval = tx_timer_change(&pChan->postdelay_timer, 
                                pChan->postdelay_timeout,
                                0);
    assert(tx_retval == TX_SUCCESS);

    
    tx_retval = tx_timer_activate(&pChan->postdelay_timer);
    assert(tx_retval == TX_SUCCESS);
}

static void stop_postdelay_timer(ONCC_CHAN *pChan)
{
    UINT tx_retval;

    tx_retval = tx_timer_deactivate(&pChan->postdelay_timer);
    assert(tx_retval == TX_SUCCESS);
}

static void disable_rtstoggle(ONCC_CHAN *pChan)
{
    int prevInterruptState;
    UINT32 reg;

    _DISAB_INTS (prevInterruptState);

    stop_postdelay_timer(pChan);        
    stop_predelay_timer(pChan);

    /*  Disable the remote loopback connection  */
    SER_REG (pChan->base_address, CTRL_REGA) &= ~LOCAL_LOOP;
 
    /*  Lower the RTS signal */
    reg = (SER_REG (pChan->base_address, CTRL_REGA) & (~RTS_ON));
    SER_REG (pChan->base_address, CTRL_REGA) = reg;

    pChan->chars2Encapsuate = 0;

    if (pChan->tx_rtstoggle_flow)
    {
        pChan->tx_rtstoggle_flow = FALSE;
        onccTxStartup ((SIO_CHAN*) pChan);            
    }

    /*  clear the flag */
    pChan->opMode &= ~OP_MODE_RTS_TX_ACTIVE;

    _RESTOR_INT (prevInterruptState);
}


/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccIoctl()                                                ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 ***                                                                            ***
 *** Comments: This routine handles the IOCTL messages from the user.           ***
 *** RETURNS: OK on success, EIO on device error, ENOSYS on unsupported         ***
 *** request.  Supported requests are: SIO_BAUD_SET/GET, SIO_HW_OPTS_SET/GET,   ***
 *** SIO_MODE_SET/GET, SIO_AVAIL_MODES_GET.                                     ***
 **********************************************************************************
 **********************************************************************************/
static STATUS onccIoctl (SIO_CHAN * pSioChan, int request, int arg)
{
    STATUS status;           /* status to return */
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;
    UINT32 baud_divr;        /* baud rate divisor */
    UINT32 reg;              /* temporary register */
    int prevInterruptState;

    status = ENOSYS;

    if (request == SIO_BAUD_ERROR_SET)  
    {
        /* Set the acceptable baudrate error  */
        pChan->baudRateError = *(int*)arg;
        status = OK;
    }

    if (request == SIO_BAUD_ERROR_GET)
    {
        /* Get the acceptable baudrate error (1% is default value) */
        status = OK;
        *(int *) arg = pChan->baudRateError;
    }

    if (request == SIO_BAUD_SET)
    {
        reg = (UINT32) *(int*)arg;

        /* Returns EIO if invalid baudrate */
        status = get_baud_div (*(int*)arg, &baud_divr);

        if (status <= pChan->baudRateError)
        {
            _DISAB_INTS (prevInterruptState);

            reg = SER_REG (pChan->base_address, BAUD_REG) 
		& ~( 
		    (BIT_RATE_DIV_MASK) | 
		    (BIT_RATE_CLK_SYS) | 
		    (BIT_RATE_X1) |
		    (BIT_TDCR_RDCR_X8) | 
		    (BIT_TDCR_RDCR_X16) | 
		    (BIT_RATE_RX_DPLL) | 
		    (BIT_TDCR_RDCR_X32) 
		    );

            /* Disable serial port*/
	    SER_REG (pChan->base_address, CTRL_REGA) &= ~0x80000000;

            /* Change baudrate */
	    SER_REG (pChan->base_address, BAUD_REG) = baud_divr | reg;

	    /* Enable serial port */
	    SER_REG (pChan->base_address, CTRL_REGA) |=0x80000000;

        /* Set character gap timer */
        reg = get_char_gap_count(*(int*)arg, 
                  current_termios[pChan->serChannel].c_cc[VBITGAP])
            * 2 + 10;

        /* If the gap is too big, set to the max the 10bits can hold, LZ */
        reg = (reg > 0x3ff) ? 0x3ff:reg;
        SER_REG (pChan->base_address, RECV_CHAR_TIMER_REG) = ENABLE_TIMER | reg;

            _RESTOR_INT (prevInterruptState);

            pChan->baudRate = *(int*)arg;
    
	    /* Fall through to mode setting below if necessary */
#ifdef SERIAL_FIQ
	    if (pChan->openmode & O_DMA) 
	    {
		/* Go back to the correct mode when we close */
		if (pChan->baudRate >= 115200) {
		    prevmode[pChan->serChannel] = SIO_MODE_FIQ;
		} else {
		    prevmode[pChan->serChannel] = SIO_MODE_INT;
		}
	    } else 
	    if (pChan->baudRate >= 115200) 
            {
		if (pChan->channelMode != SIO_MODE_FIQ) 
                {
		    request = SIO_MODE_SET;
		    arg = SIO_MODE_FIQ;
		}
	    } 
            else 
            {
		if (pChan->channelMode != SIO_MODE_INT) 
                {
		    request = SIO_MODE_SET;
		    arg = SIO_MODE_INT;
		}
	    }
#endif
	    status = OK;
        }
        else
        {
            /* Baudrate out of range error */
            status = ERANGE;
        }
    }

    if (request == SIO_BAUD_GET)
    {
        status = OK;
        *(int *) arg = pChan->baudRate;
    }

    /* Note: The SIO_MODE_SET ioctl should only be issued from
       within this function.  TODO: make this ioctl impossible
       to do. */
    if (request == SIO_MODE_SET)
    {
        _DISAB_INTS (prevInterruptState);

        if ((arg != SIO_MODE_POLL)
	    && (arg != SIO_MODE_INT)
	    && (arg != SIO_MODE_DMA)
#ifdef SERIAL_FIQ
	    && (arg != SIO_MODE_FIQ) 
#endif
	    )
        {
            status = EIO;
        }
        else
        {
	    /* Disable receive interrupt handler */
	    NADisableIsr (pChan->rxlevel);
	    /* NADisableIsr (pChan->txlevel); */

            /* Disable all serial interrupts */
	    SER_REG (pChan->base_address, CTRL_REGA) &= REGA_INTFLAGS_MASK;

#ifdef SERIAL_FIQ
	    if (arg != SIO_MODE_FIQ) 
            {
                /* Remove serial from fiq list */
		if (fiq_list[pChan->serChannel]) 
                {
		    fiq_list[pChan->serChannel] = 0;
		    fiq_ports--;
		}
		
		if (fiq_ports == 0) 
                {
		    /* Disable the fiq timer. */
		    NADisableFastIsr(TIMER2_INT);
		    NAUninstallFastIsr(TIMER2_INT);
#if PROCESSOR == ns7520	|| PROCESSOR == net50 /* TODO: Only needed until mercury driver available. */
		    *NARM_T2CR &= 0x7fffffff;  
#endif
		    fiq_up = 0;
		}
	    }
#endif

	    if (arg != SIO_MODE_DMA) {
		if (dmamode[pChan->serChannel]) {
		    serial_dma_stop(pChan);
		    dmamode[pChan->serChannel] = 0;
		}
	    }
            status = OK;
            pChan->channelMode = arg;
            if (arg == SIO_MODE_INT)
            {
                /* Enable receive interrupt only, transmit interrupt is
                   enabled in tx_startup() */
                NAEnableIsr (pChan->rxlevel);
                SER_REG (pChan->base_address, CTRL_REGA) &= REGA_INTFLAGS_MASK;
                SER_REG (pChan->base_address, CTRL_REGA) |= (RECV_IRQ_MASK);
            }

#ifdef SERIAL_FIQ
#ifdef STATISTICAL_PROFILER
#error "profiler and serial_fiq cannot be on together"
#endif
	    if (arg == SIO_MODE_FIQ) 
	    {
		int InterruptFreq = 23040 / 16; /* 16 char times at 230400 */
		int timerCount;

		if (fiq_up == 0) 
                {
		    fiq_up = 1;

                    /* Disable/remove NetOS fiq handler */
		    NADisableIsr (TIMER2_INT);
		    NAUninstallIsr (TIMER2_INT);
		    
                    /* Install our own fiq handler */
		    NAInstallFastIsr(TIMER2_INT, serFIQ_Handler);

		    timerCount = (NAgetXtalFreq() / (8 * InterruptFreq)) - 1;
#if PROCESSOR == ns7520 || PROCESSOR == net50	/* TODO: Only needed until mercury driver available. */
		    if (timerCount > 511)
		    {
			timerCount=
			    (NAgetXtalFreq() / (4096 * InterruptFreq)) - 1;
			*NARM_T2CR |= (1 << 28);
		    }
		    *NARM_T2CR |= timerCount;
		    
		    /* Disable the timer. */
		    *NARM_T2CR &= 0x7fffffff;  
		    /* Turn on fast ints and enable ints. */
		    *NARM_T2CR |= 0x60000000;  
		    /* Enable the timer. */
		    *NARM_T2CR |= 0x80000000;  
#endif
		    NAEnableFastIsr(TIMER2_INT);
		}

		if (fiq_list[pChan->serChannel] == 0) 
                {
		    fiq_list[pChan->serChannel] = 1;
		    fiq_ports++;
		}
	    }
#endif
	    if (arg == SIO_MODE_DMA) {
		if (!dmamode[pChan->serChannel]) {
		    serial_dma_start(pChan);
		    dmamode[pChan->serChannel] = 1;
		}
	    }
        }

        _RESTOR_INT (prevInterruptState);

    }

    if (request == SIO_MODE_GET)
    {
        status = OK;
        *(int *) arg = pChan->channelMode;
    }

    if (request == SIO_AVAIL_MODES_GET)
    {
        status = OK;
        *(int *) arg = SIO_MODE_INT | SIO_MODE_POLL | SIO_MODE_DMA;
    }

    if (request == SIO_HW_OPTS_SET)
    {
        status = OK;
        if (pChan->options != *(int*)arg)
        {
            status = OptSet (pChan, *(int*)arg);
        }
    }

    if (request == SIO_HW_OPTS_GET)
    {
        status = OK;
        *(int *)arg = pChan->options;
    }

    if (request == SIO_TERM_ECHO)
    {
        if (pChan->channelMode != SIO_MODE_INT
#ifdef SERIAL_FIQ
	    && pChan->channelMode != SIO_MODE_FIQ
#endif
	    )
        {
            status = EIO;
        }
        else
        {
            status = OK;
            if ((pChan->openmode & O_ACCMODE) == O_RDWR)
                pChan->echo = *(int *)arg;
            else
                status = EIO;
        }
    }

    if (request == SIO_TERM_SEND_CR)
    {
        if (pChan->channelMode != SIO_MODE_INT
#ifdef SERIAL_FIQ
	    && pChan->channelMode != SIO_MODE_FIQ
#endif
	    )
        {
            status = EIO;
        }
        else
        {
            status = OK;
            if (*(int *)arg != TERM_SEND_CR_OFF)
            {
                pChan->opMode  |= OP_MODE_INSERT_CR;
            }
            else
            {
                pChan->opMode  &= ~OP_MODE_INSERT_CR;
            }
        }
    }

    if (request == SIO_GET_STATUS_DCD)
    {
        status = OK;
        if (SER_REG (pChan->base_address, STAT_REG) & DCD_STATE)
            *(int *)arg = 1;
        else
            *(int *)arg = 0;
    }

    if (request == SIO_SET_STATE_DTR)
    {
        status = OK;
        if ((*(int *)arg) == 0)
            SER_REG (pChan->base_address, CTRL_REGA) &= (~DTR_ON);
        else
            SER_REG (pChan->base_address, CTRL_REGA) |= DTR_ON;
    }

    if (request == SIO_GET_STATUS_DSR)
    {
        status = OK;
        if (SER_REG (pChan->base_address, STAT_REG) & DSR_STATE)
            *(int *)arg = 1;
        else
            *(int *)arg = 0;
    }

    if (request == SIO_GET_STATUS_CTS)
    {
        status = OK;
        if (SER_REG (pChan->base_address, STAT_REG) & CTS_STATE)
            *(int *)arg = 1;
        else
            *(int *)arg = 0;
    }

    if (request == SIO_SET_STATE_RTS)
    {
        if (pChan->opMode & OP_MODE_RTS_TX_ENCAPSULATION)
        {
            /* Don't change the RTS state when you are in this mode */
            status = EIO;
        }
        else if (pChan->hw_flowc)
        {
            /* Don't change the RTS state when hardware flow is set */
            status = OK;
        }
        else
        {
            status = OK;
            if ((*(int *)arg) == 0)
            {
                SER_REG (pChan->base_address, CTRL_REGA) &= (~RTS_ON);
            }
            else
            {
                SER_REG (pChan->base_address, CTRL_REGA) |= RTS_ON;
            }
        }
    }

    /*  Support for clearing error conditions   */
    if (request == SIO_CLEAR_STATUS_ERROR)
    {
        if (*(int *)arg & SIO_ERROR_OVERRUN)
        {
            SER_REG (pChan->base_address, STAT_REG) |=  RECV_OVRRUN_ERR;
        }

        if (*(int *)arg & SIO_ERROR_FRAMING)
        {
            SER_REG (pChan->base_address, STAT_REG) |=  RECV_FRAME_ERR;
        }

        if (*(int *)arg & SIO_ERROR_PARITY)
        {
            SER_REG (pChan->base_address, STAT_REG) |=  RECV_PARITY_ERR;
        }

        status = OK;
    }

    /*  Support for monitoring error conditions */
    if (request == SIO_GET_STATUS_ERROR)
    {
        *(int *)arg = 0;
        reg = SER_REG (pChan->base_address, STAT_REG);

        if (reg & RECV_OVRRUN_ERR)
        {
            *(int *)arg |= SIO_ERROR_OVERRUN;
        }

        if (reg & RECV_FRAME_ERR)
        {
            *(int *)arg |= SIO_ERROR_FRAMING;
        }

        if (reg & RECV_PARITY_ERR)
        {
            *(int *)arg |= SIO_ERROR_PARITY;
        }
        
        status = OK;
    }

    /*  Watch for activity  */
    if (request == SIO_WATCH_FOR_EVENTS)
    {
        status = OK;
        if (pChan->channelMode != SIO_MODE_INT)
        {
            status = EIO;
        }
        else
        {
            if (arg == 0)
            {
                /*  when the argument is NULL -- stop this activity */
                SER_REG (pChan->base_address, CTRL_REGA) &= (~RECV_SELECT_IRQS);
                SER_REG (pChan->base_address, CTRL_REGA) |= pChan->priorState;
                pChan->priorState = 0;
                pChan->cbSelect = NULL;
            }
            else
            {
                pChan->cbSelect = (int(*)(int))(*(int *)arg);
                pChan->priorState = SER_REG (pChan->base_address, CTRL_REGA) & RECV_SELECT_IRQS;
                SER_REG (pChan->base_address, CTRL_REGA) |= (RECV_SELECT_IRQS);

                /*  pass back an indication if there is data in the buffer  */
                *(int *)arg = RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]);
            }
        }
    }

    /*  this mode activates the RTS signal only during active transmissions */
    if (request == SIO_RTS_XMIT_ENCAPSULATION)
    {
        _DISAB_INTS (prevInterruptState);

        if ((*(int *)arg) == 0)
        {
            /*  turn off the RTS encapsulation  */
            if (pChan->opMode & OP_MODE_RTS_TX_ENCAPSULATION)
            {
                pChan->opMode  &= ~OP_MODE_RTS_TX_ENCAPSULATION;
                disable_rtstoggle(pChan);
                status          = OK;
            }
            else
            {
                /*  only allowed with no handshaking    */
                status          = EIO;
            }
        }
        else
        {
            /*  this can only be openned in the No handshake mode   */
            if (pChan->opMode & OP_MODE_NO_HANDSHAKE)
            {
                if ((pChan->opMode & OP_MODE_RTS_TX_ENCAPSULATION) == 0)
                {
                    pChan->opMode          |= OP_MODE_RTS_TX_ENCAPSULATION;
                    status                  = OK;
                    /* Begin by lowering RTS */
                    reg = SER_REG (pChan->base_address, CTRL_REGA) & (~RTS_ON);
                    SER_REG(pChan->base_address, CTRL_REGA) = reg;
                    pChan->chars2Encapsuate = 0;
                }

                /* Always set to predelay and postdelay timeouts */
                pChan->predelay_timeout = current_termios[pChan->serChannel].c_cc[VPREDELAY];
                pChan->postdelay_timeout  = current_termios[pChan->serChannel].c_cc[VPOSTDELAY];
            }
            else
            {
                /*  only allowed with no handshaking    */
                status          = EIO;
            }
        }
        _RESTOR_INT (prevInterruptState);
    }

    return (status);
}

static STATUS
local_tcseta(SIO_CHAN *pSioChan, struct termios *termios_p)
{
    ONCC_CHAN *pChan = (ONCC_CHAN*)pSioChan;
    STATUS status = EIO;
    STATUS tmpstatus = OK;
    int opts = 0;
    tcflag_t cflags_set = 0;
    tcflag_t iflags_set = 0;
    tcflag_t xflags_set = 0;

    /* do SIO_BAUD_SET if necessary  */
    if ( (termios_p->baud != current_termios[pChan->serChannel].baud)
	 || (termios_p->c_cc[VBITGAP] 
	     != current_termios[pChan->serChannel].c_cc[VBITGAP]) 
       )
    {
	current_termios[pChan->serChannel].c_cc[VBITGAP] = 
	    termios_p->c_cc[VBITGAP];

	tmpstatus = onccIoctl(pSioChan, SIO_BAUD_SET, (int)&termios_p->baud);
	if (tmpstatus == ERANGE) {
	    return status;
	}
	current_termios[pChan->serChannel].baud = termios_p->baud;
	status = OK;
    }

    /* do SIO_HW_OPTS_SET if necessary */
    switch (termios_p->c_cflag & CSIZE) {
    case CS5:
	opts |= inCS5;
	cflags_set |= CS5;
	break;
    case CS6:
	opts |= inCS6;
	cflags_set |= CS6;
	break;
    case CS7:
	opts |= inCS7;
	cflags_set |= CS7;
	break;
    case CS8:
	opts |= inCS8;
	cflags_set |= CS8;
	break;
    }
    
    if (termios_p->c_cflag & CSTOPB) {
	opts |= inSTOPB;
	cflags_set |= CSTOPB;
    }

    if (termios_p->c_cflag & PARENB) {
	opts |= inPARENB;
	cflags_set |= PARENB;
	if (termios_p->c_cflag & PARODD) {
	    opts |= inPARODD;
	    cflags_set |= PARODD;
	}
	if (termios_p->c_xflag & XPAR) {
	    opts |= inXPAR;
	    xflags_set |= XPAR;
	}
    }

    /* Partial and arbitrary flow control is not allowed in xk, only 
     * RTS/CTS, or full software or nothing */
    if ( (termios_p->c_cflag & CRTSCTS) == CRTSCTS ) {
	opts |= SIO_HW_RTS_CTS_HANDSHAKING;
	cflags_set |= CRTSCTS;
    }  
    
    if ( (termios_p->c_iflag & (IXON | IXOFF)) == (IXON | IXOFF) ) {
	opts |= SIO_SW_HANDSHAKING;
	iflags_set |= IXON | IXOFF;
    } else if ( !((termios_p->c_cflag & CRTSCTS) == CRTSCTS) ) {
	opts |= SIO_NO_HANDSHAKING;
    }

    tmpstatus = onccIoctl(pSioChan, SIO_HW_OPTS_SET, (int)&opts);
    
    if (tmpstatus != OK) {
	return status;
    }

    current_termios[pChan->serChannel].c_cflag 
	&= ~(CSIZE | CSTOPB | PARENB | PARODD | CRTSCTS);
    current_termios[pChan->serChannel].c_cflag |= cflags_set;
    cflags_set = 0;

    current_termios[pChan->serChannel].c_iflag &= ~(IXON | IXOFF);
    current_termios[pChan->serChannel].c_iflag |= iflags_set;
    iflags_set = 0;

    current_termios[pChan->serChannel].c_xflag &= ~(XPAR);
    current_termios[pChan->serChannel].c_xflag |= xflags_set;
    xflags_set = 0;

    

    /* do SIO_TERM_SEND_CR if necessary: ONLCR */
    if (( (termios_p->c_oflag 
	  ^ current_termios[pChan->serChannel].c_oflag) & ONLCR ) 
	 && !(pChan->openmode & O_DMA)) 
    {
	if (termios_p->c_oflag & ONLCR) {
	    opts = TERM_SEND_CR_ON;
	} else {
	    opts = TERM_SEND_CR_OFF;
	}

	tmpstatus = onccIoctl(pSioChan, SIO_TERM_SEND_CR, (int)&opts);
	if (tmpstatus != OK) {
	    return status;
	}
	current_termios[pChan->serChannel].c_oflag ^= ONLCR;
    }

    /* do SIO_RTS_XMIT_ENCAPSULATION if necessary: CRTSTOGGLE */
    if ( ( ( (termios_p->c_cflag 
	  ^ current_termios[pChan->serChannel].c_cflag) & CRTSTOGGLE ) ||
         (termios_p->c_cc[VPREDELAY] != current_termios[pChan->serChannel].c_cc[VPREDELAY]) ||
         (termios_p->c_cc[VPOSTDELAY] != current_termios[pChan->serChannel].c_cc[VPOSTDELAY]) )   
	 && !(pChan->openmode & O_DMA)) 
    {
        /* Set predelay and postdelay that RTS Toggle will use */
        current_termios[pChan->serChannel].c_cc[VPREDELAY] = termios_p->c_cc[VPREDELAY];
        current_termios[pChan->serChannel].c_cc[VPOSTDELAY] = termios_p->c_cc[VPOSTDELAY];


	if (termios_p->c_cflag & CRTSTOGGLE) {
	    opts = SIO_RTS_XMIT_ENCAPSULATION;
	} else {
	    opts = 0;
	}
	tmpstatus = onccIoctl(pSioChan, SIO_RTS_XMIT_ENCAPSULATION, (int)&opts);
	if (tmpstatus != OK) {
	    return status ;
	}
	current_termios[pChan->serChannel].c_cflag ^= CRTSTOGGLE;
    }

    /* Parity flags, we don't do anything, but allow them to be set, so that
     * we can at least do 0xff escaping */
    if (!(pChan->openmode & O_DMA)) 
    {
        current_termios[pChan->serChannel].c_iflag &= ~(PARMRK | IGNPAR | ISTRIP);
        current_termios[pChan->serChannel].c_iflag |= 
        termios_p->c_iflag & (PARMRK | IGNPAR | ISTRIP);
    }

    /* XALTPIN */
    if (termios_p->c_xflag & XALTPIN) {
	current_termios[pChan->serChannel].c_xflag |= XALTPIN;
    } else {
	current_termios[pChan->serChannel].c_xflag &= ~XALTPIN;
    }
    
    /* XFORCEDCD */
    if (termios_p->c_xflag & XFORCEDCD) {
	current_termios[pChan->serChannel].c_xflag |= XFORCEDCD;
    } else {
	current_termios[pChan->serChannel].c_xflag &= ~XFORCEDCD;
    }

    /* XBREAK */
    if (termios_p->c_xflag & XBREAK) {
	current_termios[pChan->serChannel].c_xflag |= XBREAK;
    } else {
	current_termios[pChan->serChannel].c_xflag &= ~XBREAK;
    }

    if (termios_p->c_cc[VSTART] 
	!= current_termios[pChan->serChannel].c_cc[VSTART] )
    {
	pChan->xon_ch
	    = current_termios[pChan->serChannel].c_cc[VSTART]
	    = termios_p->c_cc[VSTART];
    }

    if (termios_p->c_cc[VSTOP] 
	!= current_termios[pChan->serChannel].c_cc[VSTOP] )
    {
	pChan->xoff_ch
	    = current_termios[pChan->serChannel].c_cc[VSTOP]
	    = termios_p->c_cc[VSTOP];
    }

    /* TODO */

    /* INPCK?? */

    status = OK; /* Must have finished okay */
    return status;
}

/*
 * activeSignals
 *
 * Given the channel number, this function will return a mask for the
 * defines that tells us which pins are currently allocated to be used
 * by the serial device.
 *  */
uint32
activeSignals(int channel)
{
    uint32 reg;
    unsigned char DIR;
    unsigned char MODE;
    uint32 sigs = 0;

#if PROCESSOR == ns7520	/* TODO: Only needed until mercury driver available. */
    if (channel == 0) {
	reg = ReadReg(NARM_PORTA) | 0xff;
    } else {
	unsigned char CSF;
	reg = ReadReg(NARM_PORTC) | 0xff;
	// CSF needs to be 1 for all pins but RI, where it should be 0
	CSF = (reg >> 8) & 0xff;
	// RI needs CSF=0, all other signals need CSF=1
	reg &= CSF ^ NARM_PORTC_RI;
    }
    DIR = (reg >> 16) & 0xff;
    MODE = (reg >> 24) & 0xff;

    reg &= DIR ^ 0xff; // All signals need DIR=0
    reg &= MODE; // All signals need MODE=1

    // Map PORTC values to Serial Status values
    if (reg & NARM_PORTC_RI) {
	sigs |= RI_STATE;
    }
    if (reg & NARM_PORTC_DSR) {
	sigs |= DSR_STATE;
    }
    if (reg & NARM_PORTC_CTS) {
	sigs |= CTS_STATE;
    }
    if (reg & NARM_PORTC_DCD) {
	sigs |= DCD_STATE;
    }
#elif PROCESSOR == net50
    if (channel == 0) {
	reg = ReadReg(NARM_PORTA) | 0xff;
    } else {
	unsigned char CSF;
	reg = ReadReg(NARM_PORTB) | 0xff;
	// CSF needs to be 1 for all pins but RI, where it should be 0
	CSF = (reg >> 8) & 0xff;
	// RI needs CSF=0, all other signals need CSF=1
	reg &= CSF ^ NARM_PORTC_RI;
    }
    DIR = (reg >> 16) & 0xff;
    MODE = (reg >> 24) & 0xff;

    reg &= DIR ^ 0xff; // All signals need DIR=0
    reg &= MODE; // All signals need MODE=1

    // Map PORTC values to Serial Status values
    if (reg & NARM_PORTC_RI) {
	sigs |= RI_STATE;
    }
    if (reg & NARM_PORTC_DSR) {
	sigs |= DSR_STATE;
    }
    if (reg & NARM_PORTC_CTS) {
	sigs |= CTS_STATE;
    }
    if (reg & NARM_PORTC_DCD) {
	sigs |= DCD_STATE;
    }
#endif
    

    return sigs;
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  digiIoctl()                                                ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 ***                                                                            ***
 *** Comments: This routine handles the IOCTL messages from the user.           ***
 *** RETURNS: OK on success, EIO on device error, ENOSYS on unsupported         ***
 *** request.  
 **********************************************************************************
 **********************************************************************************/
static STATUS digiIoctl (SIO_CHAN * pSioChan, int request, int arg)
{
    STATUS status;           /* status to return */
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;
    struct termios *termios_p;
    int duration, queue_selector, action, prevInterruptState, opt;
    char c;
    int *done;
    struct serial_cntr_t *cntrs;
    struct serial_status_t *serl_status;
    uint32 active;
    struct sigchange_stats_t *sigchange;
    struct serial_buffer_t *bufs;
    unsigned char mout;
    unsigned char prev;

    status = (STATUS)ENOSYS;

    switch (request) {
    case TCGETA: 
	status = OK;

	termios_p = (struct termios*)arg;
	memcpy(termios_p, &current_termios[pChan->serChannel], sizeof(current_termios[pChan->serChannel]));
	break; 
    case TCSETA:
	termios_p = (struct termios*)arg;
	status = local_tcseta(pSioChan, termios_p);
	break;
    case TCSENDBREAK:
	status = OK; 

	duration = (int)arg;

	serl_cb_event(pChan->serial_channel, TXB_EVENT);
        _DISAB_INTS(prevInterruptState);
	//DISABLE_INTS; //protect estat
        serial_status[pChan->serChannel].estat |= EV_TXB;
	//pChan->status->estat |= EV_TXB;
        _RESTOR_INT(prevInterruptState);
	//RESTORE_INTS;
	pChan->breaktime = duration;
	pChan->breakstatus = BRK_REQUESTED;
	break;
    case TCDRAIN:
	status = OK;
    /* DO NOT REMOVE! */
#ifndef CELLULAR_ENABLED
	/* wait until transmitting buffer is empty */
	while (!RING_BUFFER_EMPTY(&tx_buffer[pChan->serChannel]))  
        {
            tx_thread_sleep (1);
        }

	/* Wait for hardware to stop transmitting */	
	while ( (SER_REG( pChan->base_address, STAT_REG) & XMITR_EMPTY) == 0 ) {
	    tx_thread_sleep(1);
	}
#endif
	break;
    case TCFLUSH:
	status = OK;
	if (pChan->openmode & O_DMA) {
	    status = ENOSYS;
	    break;
	}
	queue_selector = (enum flush_opts)arg;
	if (queue_selector & TCIFLUSH) { /* Throw away input */
	    RING_BUFFER_RESET(&rx_buffer[pChan->serChannel]);

	    check_rx_flow(pChan);
	} 

	if (queue_selector & TCOFLUSH) { /* Throw away output */
	    RING_BUFFER_RESET(&tx_buffer[pChan->serChannel]);
	}

	assert(!(arg & ~TCIOFLUSH ));
	if (arg & ~TCIOFLUSH) {
	    status = EIO;
	}
	break;
    case TCFLOW: {
	unsigned short estat_pre = serial_status[pChan->serChannel].estat;
	status = OK; 
	if (pChan->openmode & O_DMA) {
	    status = ENOSYS;
	    break;
	}

	action = (enum flow_opts)arg;
	switch (action) {
	case TCOOFF:
	    pChan->tx_user_flow = FALSE;
	    serial_status[pChan->serChannel].estat &= ~EV_OPU;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_OPU )
                serl_cb_event(pChan->serial_channel, OPU_EVENT);
	    onccTxStartup(pSioChan);
	    break;
	case TCOON:
	    pChan->tx_user_flow = TRUE;
	    serial_status[pChan->serChannel].estat |= EV_OPU;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_OPU )
                serl_cb_event(pChan->serial_channel, OPU_EVENT);
	    break;
	case TCIOFF:
	    pChan->rx_user_flow = FALSE;
	    serial_status[pChan->serChannel].estat &= ~EV_IPU;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_IPU )
                serl_cb_event(pChan->serial_channel, IPU_EVENT);

#ifdef FLOW_REWRITE
	    change_rx_flow(pChan);
#else
	    if (pChan->hw_flowc) {
		UINT32 reg;
		reg = SER_REG(pChan->base_address, CTRL_REGA);
		if (!pChan->rx_hw_flow) {
		    reg |= RTS_ON;
		}
		
		SER_REG (pChan->base_address, CTRL_REGA) = reg;
		//pChan->rx_hw_flow = FALSE;
#ifdef DEBUG_SERIAL
		printf("enable int \n");
#endif
	    } else if (pChan->sw_flowc) {
		_DISAB_INTS (prevInterruptState);
		/* waiting for XMITR_RDY */
		while (!(XMITR_RDY_IRQ 
			 & (SER_REG (pChan->base_address, STAT_REG))));
		    
		
		/* send out xoff */
		SER_REG_BYTE(pChan->base_address, DATA_REG) = (pChan->xon_ch);  
		_RESTOR_INT(prevInterruptState);
	    }
#endif // FLOW_REWRITE
	    break;
	case TCION:
	    pChan->rx_user_flow = TRUE;
	    serial_status[pChan->serChannel].estat |= EV_IPU;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_IPU )
                serl_cb_event(pChan->serial_channel, IPU_EVENT);
	    
#ifdef FLOW_REWRITE
	    change_rx_flow(pChan);
#else
	    if (pChan->hw_flowc) {
		UINT32 reg;
		reg = SER_REG(pChan->base_address, CTRL_REGA);
		reg &= ~RTS_ON;

		SER_REG(pChan->base_address, CTRL_REGA) = reg;
	    } else if (pChan->sw_flowc) {
		_DISAB_INTS (prevInterruptState);
		/* waiting for XMITR_RDY */
		while (!(XMITR_RDY_IRQ 
			 & (SER_REG (pChan->base_address, STAT_REG))));
		
		/* send out xoff */
		SER_REG_BYTE(pChan->base_address, DATA_REG) = (pChan->xoff_ch);  
		_RESTOR_INT(prevInterruptState);
	    }
#endif // FLOW_REWRITE
	    break;
        case TCIAOFF: // pattern alarm exit flow control
            pChan->rx_alarm_flow = FALSE;
            change_rx_flow(pChan);
            break;
	case TCOSOFF:
            pChan->tx_sw_flow = FALSE;
            serial_status[pChan->serChannel].estat &= ~EV_OPS;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_OPS)
                serl_cb_event(pChan->serial_channel, OPS_EVENT);
            // Resume transmitting data
            SER_REG (pChan->base_address, CTRL_REGA) |= (XMITR_HALF_FULL_IRQ);
#if CHANGE_TX_ISR_STATE_IN_DRIVER
            NAEnableIsr (pChan->txlevel);
#endif
            break;
	case TCOSON:
            pChan->tx_sw_flow = TRUE;
            serial_status[pChan->serChannel].estat |= EV_OPS;
	    if ( (estat_pre ^ serial_status[pChan->serChannel].estat) & EV_OPS)
                serl_cb_event(pChan->serial_channel, OPS_EVENT);
            break;
	case TCISOFF:
	case TCISON:
	case TCOXOFF:
	case TCOXON:
	case TCIXOFF:
	case TCIXON:
	    //TODO - all the extra flow states
	default:
	    status = EIO;
	    break;
	}

	break;
    }
    case TCSENDNOW:
	status = OK;
	if (pChan->openmode & O_DMA) {
	    status = ENOSYS;
	    break;
	}
	c = (char)arg;
	
	_DISAB_INTS (prevInterruptState);
	/* waiting for XMITR_RDY */
        while (!(XMITR_RDY_IRQ & (SER_REG (pChan->base_address, STAT_REG))));

        /* send out char */
        SER_REG_BYTE(pChan->base_address, DATA_REG) = c;  
	_RESTOR_INT (prevInterruptState);
	
	serl_cb_event(pChan->serial_channel, TXI_EVENT);
	break;
    case TCGETCOUNTERS:
	status = OK;

	cntrs = (struct serial_cntr_t*)arg;
	memcpy(cntrs, &serial_counters[pChan->serChannel], 
	       sizeof(struct serial_cntr_t));
	break;
    case TCGETSTATS:
	status = OK;
	
	serl_status = (struct serial_status_t*)arg;
	serl_status->mstat = 0;

	active = activeSignals(pChan->serChannel);

	opt = SER_REG (pChan->base_address, STAT_REG);
	opt &= active;

	if (current_termios[pChan->serChannel].c_xflag & XALTPIN) {
	    /* Swap DCD and DSR */
	    if (opt & DCD_STATE)
		serl_status->mstat |= MS_DSR;

	    if (opt & DSR_STATE)
		serl_status->mstat |= MS_DCD;
	} else {
	    if (opt & DCD_STATE)
		serl_status->mstat |= MS_DCD;

	    if (opt & DSR_STATE)
		serl_status->mstat |= MS_DSR;
	}

	if (current_termios[pChan->serChannel].c_xflag & XFORCEDCD) {
	    serl_status->mstat |= MS_DCD;
	}

	if (opt & RI_STATE)
	    serl_status->mstat |= MS_RI;
	    
	if (opt & CTS_STATE)
	    serl_status->mstat |= MS_CTS;

	opt = SER_REG (pChan->base_address, CTRL_REGA);

	if (opt & DTR_ON)
	    serl_status->mstat |= MS_DTR;

	if (opt & RTS_ON)
	    serl_status->mstat |= MS_RTS;

	serl_status->estat = serial_status[pChan->serChannel].estat;

	//memcpy(serl_status, &serial_status[pChan->serChannel], 
	//	sizeof(struct serial_status_t));
	break;
    case TCGETSIGCHANGE:
	status = OK;

	sigchange = (struct sigchange_stats_t *)arg;
	memcpy(sigchange, &serial_sigchange[pChan->serChannel], 
	       sizeof(struct sigchange_stats_t));
	break;
    case TCSETSIGNALS:
    
        status = OK;

	opt = SER_REG (pChan->base_address, CTRL_REGA);
	prev = 0;

	if (opt & DTR_ON)
	    prev |= MS_DTR;
	
	if (opt & RTS_ON)
	    prev |= MS_RTS;

	mout = (unsigned char)arg;
	opt = (mout & MS_DTR) ? 1 : 0;

	status = onccIoctl(pSioChan, SIO_SET_STATE_DTR, (int)&opt);
	if ( (status == OK) && 
	     !(current_termios[pChan->serChannel].c_cflag & CRTS) 
	   ) 
	{
	    opt = (mout & MS_RTS) ? 1 : 0;
	    status = onccIoctl(pSioChan, SIO_SET_STATE_RTS, (int)&opt);
	}

	if ( (prev ^ mout) & MS_DTR ) { /* DTR changed */
	    serial_sigchange[pChan->serChannel].dtr_counter++;
	    serl_cb_event(pChan->serial_channel, DTR_DELTA);

	    serial_status[pChan->serChannel].mstat ^= MS_DTR;
	}

	if ( (prev ^ mout) & MS_RTS ) { /* RTS changed */
	    serial_sigchange[pChan->serChannel].rts_counter++;
	    serl_cb_event(pChan->serial_channel, RTS_DELTA);

	    serial_status[pChan->serChannel].mstat ^= MS_RTS;
	}

	break;
    case TCGETBUFFERS:
	status = OK;
	if (pChan->openmode & O_DMA) {
	    status = ENOSYS;
	    break;
	}
	bufs = (struct serial_buffer_t *)arg;
	bufs->rxbuf = RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]);
	bufs->txbuf = RING_BUFFER_LENGTH(&tx_buffer[pChan->serChannel]);
	break;
    case TCTXDONE:
	status = OK;

	done = (int *)arg;
	*done = 0;
	if ( (SER_REG( pChan->base_address, STAT_REG) & XMITR_EMPTY) &&
	     (RING_BUFFER_EMPTY(&tx_buffer[pChan->serChannel])) ) {
	    *done = 1;
	}
	break;
    case ISOPEN:
        *(int*)arg = serial_isopen_int(pChan->serChannel);
        status = OK;
        break;
    case DIPSWITCHMODE:
        *(int*)arg = DipSwitchMode_int(pChan->serChannel);
        status = OK;
        break;
    default:
	assert(0);
	/* Return ENOSYS: unsupported request */
	//status = onccIoctl(pSioChan, request, arg);
	break;
    }

    return (status);
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccTxStartup()                                            ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 ***                                                                            ***
 *** Comments: Call interrupt level character output routine and enable         ***
 *** interrupt.  Function returns: OK on success, ENOSYS if the device is       ***
 *** polled-only, or EIO on hardware error.                                     ***
 **********************************************************************************
 **********************************************************************************/
static int onccTxStartup (SIO_CHAN * pSioChan)
{
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;

    if (pChan->channelMode == SIO_MODE_INT
#ifdef SERIAL_FIQ
	|| pChan->channelMode == SIO_MODE_FIQ
#endif
	)
    {
        //SER_REG (pChan->base_address, CTRL_REGA) |= XMITR_RDY_IRQ;
	SER_REG (pChan->base_address, CTRL_REGA) |= XMITR_HALF_FULL_IRQ;

#if CHANGE_TX_ISR_STATE_IN_DRIVER
        /* enable transmitter interrupts */
        NAEnableIsr (pChan->txlevel);
#endif
        return (OK);
    }

    return (ENOSYS);
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccCallbackInstall()                                      ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments: This routine installs interrupt callbacks for transmitting and   ***
 *** receiving characters. Function returns OK on success, or ENOSYS for an     ***
 *** unsupported callback type.                                                 ***
 **********************************************************************************
 **********************************************************************************/
static int onccCallbackInstall (SIO_CHAN * pSioChan, int callbackType, STATUS (*callback) (void), void *callbackArg)
{
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;

    switch (callbackType)
    {
    case SIO_CALLBACK_GET_TX_CHAR:
        pChan->getTxChar = callback;
        pChan->getTxArg = callbackArg;
        return (OK);

    case SIO_CALLBACK_PUT_RCV_CHAR:
        pChan->putRcvChar = callback;
        pChan->putRcvArg = callbackArg;
        return (OK);

    default:
        return (ENOSYS);
    }
}

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
static void onccHandleCTSChange(ONCC_CHAN * pChan)
{
    if (pChan->hw_flowc) 
    {
        /* current CTS state on? */
        if (SER_REG (pChan->base_address, STAT_REG) & CTS_STATE)
        {
            if (pChan->openmode & O_DMA)
                /* resume the transmit DMA */
                SER_REG(pChan->base_address, CTRL_REGA) |= XMITR_DMA_ON;
            else
                digiIoctl((SIO_CHAN *)pChan, TCFLOW, TCOOFF);
        }
        else
        {
                /* pause the transmit DMA */
            if (pChan->openmode & O_DMA)
                SER_REG(pChan->base_address, CTRL_REGA) &= ~XMITR_DMA_ON;
            else
            {
                digiIoctl((SIO_CHAN *)pChan, TCFLOW, TCOON);
                SER_REG (pChan->base_address, CTRL_REGA) &= 
                    ~(XMITR_RDY_IRQ | XMITR_HALF_FULL_IRQ);
            }
        }
    }
}
#endif

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccIntWr()                                                ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments: This routine handles write interrupts from the NCC.       		***
 **********************************************************************************
 **********************************************************************************/
static void onccIntWr (ONCC_CHAN * pChan)
{
    char outChar;
    UINT32 reg;
    int prevInterruptState, status;
    int bytesToWrite;

    status = SER_REG (pChan->base_address, STAT_REG);

#ifndef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    if ( (XMITR_RDY_IRQ & status) == 0)
    {
        return;
    }
#endif

    /* acknowledge the interrupt */
    _DISAB_INTS (prevInterruptState);
    SER_REG (pChan->base_address, STAT_REG) |= CLR_ALL_TX_CONDITIONS;

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    if (RECV_CTS_IRQ & status)
        tx_event_flags_set( &cntrl_flags, NA_CTS_CHANGE, TX_OR);                

    if ( (XMITR_RDY_IRQ & status) == 0)
    {
        _RESTOR_INT (prevInterruptState); 
        return;        
    }
#endif

    if (pChan->openmode & O_DMA)
    {
        _RESTOR_INT (prevInterruptState); 
        return;        
    }

    bytesToWrite = RING_BUFFER_LENGTH(&tx_buffer[pChan->serChannel]);
    if (bytesToWrite > 0)
    {
        if ( !_TX_FLOW_CONTROLLED(pChan) ) /* tx_*_flow */
        {
            if ((pChan->opMode & OP_MODE_RTS_TX_ENCAPSULATION) && (pChan->chars2Encapsuate == 0))
            {
                if ((pChan->opMode & OP_MODE_RTS_TX_ACTIVE) == 0)
                {
                    /* Cancel postdelay timer */
                    stop_postdelay_timer(pChan);

                    /* Activate RTS toggle mode */
                    pChan->opMode |= OP_MODE_RTS_TX_ACTIVE;

                    /*  Raise RTS */
                    reg = SER_REG (pChan->base_address, CTRL_REGA) | RTS_ON;
                    SER_REG (pChan->base_address, CTRL_REGA) = reg;
                
                    /*  During RTS toggle transmission enable local loopback 
                        connection */
                    SER_REG (pChan->base_address, CTRL_REGA) |= LOCAL_LOOP;

                    /* If necessary activate predelay timer */
                    if (pChan->predelay_timeout)
                    {
                        pChan->tx_rtstoggle_flow = TRUE;
                        start_predelay_timer(pChan);
                        goto exit;
                    }
                }
            }

            while (bytesToWrite > 0)
            {
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
                status = 0;

                if (pChan->hw_flowc)
                {
                    if (CTS_STATE & SER_REG (pChan->base_address, STAT_REG))
                    {
                        if (XMITR_RDY_IRQ & SER_REG (pChan->base_address, STAT_REG))
                            status = 1;
                    }
                    else
                    {
                        tx_event_flags_set (&cntrl_flags,NA_CTS_CHANGE,TX_OR);
                        onccHandleCTSChange(pChan);
                    }                
                }
                else if (XMITR_RDY_IRQ & SER_REG (pChan->base_address, STAT_REG))
                {
                    status = 1;
                }

                if (status)
#else
                if (XMITR_RDY_IRQ & SER_REG (pChan->base_address, STAT_REG))
#endif
                {
#if 0
		    bytesToWrite--;
                    /* remove a char from transmit queue */
                    RING_BUFFER_REMOVE(&tx_buffer[pChan->serChannel], outChar);

                    /* write char to transmitter FIFO */
                    SER_REG_BYTE (pChan->base_address, DATA_REG) = outChar;
		    serial_counters[pChan->serChannel].tbytes++;

                    SER_REG (pChan->base_address, STAT_REG) |= XMITR_RDY_IRQ;

                    if (pChan->opMode & OP_MODE_RTS_TX_ACTIVE)
                    {
                        /*  This character should be looped back    */
                        pChan->chars2Encapsuate++;
                    }
#else
		    ring_buffer_t *q;
		    int tosend;
		    int sent = 0;

		    q = &tx_buffer[pChan->serChannel];
		    tosend = RING_BUFFER_LENGTH(q);
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
                    if (pChan->hw_flowc)
                    {
                        if (tosend > NA_BYTES_IN_EACH_LINE) 
                                tosend = NA_BYTES_IN_EACH_LINE;
                    }
                    else if (tosend > 4) 
                        tosend = 4;
#else
		    if (tosend > 4) tosend = 4;
#endif
		    switch(tosend) {
		    case 4:
			//Can do in one op?
			if ( (((int)&q->data[q->out] & 0x3) == 0) && //Aligned?
			     ((q->out + 4) <= RING_BUFFER_SIZE) ) 
			{
			    UINT32 data = *(UINT32*)(&q->data[q->out]);
                            q->out = (q->out + 4) & RING_BUFFER_MASK;

			    SER_REG(pChan->base_address, DATA_REG) = data;
			    sent = 4;
			    break;
			}
			//else fall through
		    case 2:
			//Can do in one op?
			if ( (((int)&q->data[q->out] & 0x1) == 0) && //Aligned?
			     ((q->out + 2) <= RING_BUFFER_SIZE) ) 
			{
			    UINT16 data = *(UINT16*)(&q->data[q->out]);
                            q->out = (q->out + 2) & RING_BUFFER_MASK;

			    SER_REG_SHORT(pChan->base_address, DATA_REG) = data;
			    sent = 2;
			    break;

			}
			//else fall through
		    case 3: /* Send one, next round will use 2 above */
		    case 1:
			/* remove a char from transmit queue */
			RING_BUFFER_REMOVE (&tx_buffer[pChan->serChannel], outChar);
			
			/* write char to transmitter FIFO */
			SER_REG_BYTE (pChan->base_address, DATA_REG) = outChar;
			sent = 1;
			break;
		    default:
			break;
		    }
		    serial_counters[pChan->serChannel].tbytes += sent;
		    bytesToWrite -= sent;
		    
		    if (pChan->opMode & OP_MODE_RTS_TX_ACTIVE)
		    {
                        /*  This character should be looped back    */
			pChan->chars2Encapsuate += sent;
		    }
		    /* SER_REG (pChan->base_address, STAT_REG) 
                       |= XMITR_RDY_IRQ | XMITR_HALF_FULL_IRQ; */
#endif
 
                }
                else
                {
                    break;
                }
            }
        }

#ifdef SERIAL_ACTIVITY_LED_ENABLED
	led_mark_activity( SERIAL_ACTIVITY_LED0 );
#endif
    }

exit:

    if ((RING_BUFFER_LENGTH(&tx_buffer[pChan->serChannel]) == 0) 
	|| _TX_FLOW_CONTROLLED(pChan) ) 
    {
#if CHANGE_TX_ISR_STATE_IN_DRIVER
        NADisableIsr (pChan->txlevel);
#endif

        reg = SER_REG (pChan->base_address, CTRL_REGA) & 
	    ~(XMITR_RDY_IRQ | XMITR_HALF_FULL_IRQ);

        SER_REG (pChan->base_address, CTRL_REGA) = reg;
     }

    _RESTOR_INT (prevInterruptState); 
} 

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccIntRd()                                                ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments: This routine handles read interrupts from the NCC.        		***
 **********************************************************************************
 **********************************************************************************/
static void onccIntRd (ONCC_CHAN * pChan)
{
    UINT32 status;
    unsigned int space;
    int nread;
    int i;
#ifdef ALARMS_ENABLED
    int alarmret = PM_FLOW_CONTROL_OFF;
#endif
    int flowdiff = 0;
    int prevInterruptState;
    int xon_found = 0;

#if eosdebug
    int linecount = 0;
    int datalinecount = 0;
#endif

#if 0
    // I disabled the match stuff because it was not effectively doing
    // anything anymore.
    int match_found = 0;
#endif

#if 0
    // We used this code to trigger gpio so we could watch interrupt
    // behavior on a scope.
    {
        unsigned int porta;

        porta = ReadReg(NARM_PORTA);

        porta ^= 0x04;
        porta |= 0x01;
        
        WriteReg(NARM_PORTA, porta);
    }
#endif

    _DISAB_INTS (prevInterruptState);

#if eosdebug
    if (RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]) 
	> rx_buf_maxcount1[pChan->serChannel] ) 
    {
	rx_buf_maxcount1[pChan->serChannel] = 
	    RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]);
    }
#endif


    while (1)
    {
        status = (SER_REG(pChan->base_address, STAT_REG));

        if (status & (RX_ERR_CONDITION | RECV_BREAK))
        {
            if (status & RECV_OVRRUN_ERR)
            {
                serial_counters[pChan->serChannel].norun++;
            }
            if (status & RECV_FRAME_ERR)
            {
                serial_counters[pChan->serChannel].nframe++;
            }
            if (status & RECV_PARITY_ERR)
            {
                serial_counters[pChan->serChannel].nparity++;
            }
            if (status & RECV_BREAK)
            {
                // More than one break / ms(tick?) doesn't even make sense
                if ( (tx_time_get() - pChan->lastbreak) > 2)
                {
                    // It's been long enough since the last break,
                    // process another.
                    
                    serial_counters[pChan->serChannel].nbreak++;
                    
                    // If break reporting is desired, we do what we can
                    if (current_termios[pChan->serChannel].c_xflag & XBREAK) {
                        space = RING_BUFFER_SPACE(&rx_buffer[pChan->serChannel]);
                        if(current_termios[pChan->serChannel].c_iflag 
                           & IGNBRK) 
                        {
                            ;
                        } else if(current_termios[pChan->serChannel].c_iflag 
                                  & BRKINT) 
                        {
                            if(!(serial_status[pChan->serChannel].estat 
                                 & EV_RXB))
                            {
                                serial_status[pChan->serChannel].estat 
                                    |= EV_RXB;
                                serl_cb_event(pChan->serial_channel,RXB_EVENT);
                            }
                        } else if(!(current_termios[pChan->serChannel].c_iflag 
                                    & PARMRK)) 
                        {
                            // PARMRK not set, break becomes 00
                            if (space < 1) {
                                pChan->rx_overflow+=1;
                                serial_counters[pChan->serChannel].noflow+=1;
                            } else {
                                RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], 
                                                0x00);
                            }
                        } else {
                            if (space < 3) {
                                pChan->rx_overflow+=3;
                                serial_counters[pChan->serChannel].noflow+=3;
                            } else {
                                // encode as FF 10 00 (DOSMODE) or FF 00 00
                                RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], 
                                                0xff);
                                RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], 
                                   (current_termios[pChan->serChannel].c_iflag 
                                    & DOSMODE) 
                                                ? 0x10 
                                                : 0x00);
                                RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], 
                                                0x00);
                            }
                        }
		    }
                }
                pChan->lastbreak = tx_time_get();
            }
	}

        if ((status & RECV_RDY) == 0)
        {
            // There appears to be a bug where a close condition
            // can occur and not show up for a long time.  So to
            // avoid this situation we attempt the clear of the close
            // condition whenever the RECV_RDY bit is not set.

#ifdef DEBUG_SERIAL
            rbcc++;
#endif

#if eosdebug
            linecount++;
#endif

#if 0
            // I disabled the match stuff because it was not effectively doing
            // anything anymore.  Also there appears to be some hardware issue
            // with it in the 7520.
            if ((pChan->sw_flowc) && ((MATCH1_TRUE | MATCH2_TRUE) & (status)))
            {
                /* probably never use this line hardware bug */
                match_found = 1;
            }
#endif
            // Set RECV_CLOSED so condition is later cleared
            status |= RECV_CLOSED;
        }

        // Clear any status conditions
        if (status & (RECV_CLOSED | RX_ERR_CONDITION | RECV_BREAK)) 
        {
            SER_REG(pChan->base_address, STAT_REG) = status;
            status = SER_REG(pChan->base_address, STAT_REG);
        }

        if ((status & (RECV_CLOSED | RECV_RDY | RECV_HALF_FULL | RECV_FULL)) == 0)
        {
            // nothing to process
            break;
        }

        if (status & RECV_RDY)
        {
            char *dataptr;
            UINT32 data;

            assert((status & RECV_CLOSED) == 0);

#if eosdebug
            datalinecount++;
#endif
            nread = (status & NUM_DATA_BYTES) >> 20;

            if (nread == 0)
            {
                // RXFDB returns 0 when 4 bytes are available
                nread = 4;
            }

            // Signal a RX_DATA EVENT
            rx_note[pChan->serChannel]++;

            // Read the data
            data = SER_REG(pChan->base_address, DATA_REG);
            dataptr = (char *) &data;

            if ((pChan->opMode & OP_MODE_RTS_TX_ENCAPSULATION) && 
                (pChan->opMode & OP_MODE_RTS_TX_ACTIVE))
            {
                while (nread)
                {
                    nread--;

                    /*  in this mode we are just digesting data */
                    assert(pChan->chars2Encapsuate != 0);
                    pChan->chars2Encapsuate--;
                
                    if (pChan->chars2Encapsuate == 0)
                    {
                        /*  Disable the loopback connection  */
                        SER_REG (pChan->base_address, CTRL_REGA) &= ~LOCAL_LOOP;

                        /*  Clear the RTS transmit active flag */
                        pChan->opMode &= ~OP_MODE_RTS_TX_ACTIVE;


                        /* Completed Transmission */
                        if (pChan->postdelay_timeout)
                        {
                            /* Lower the RTS signal after some delay */
                            start_postdelay_timer(pChan);
                        }
                        else
                        {
                            /*  Lower the RTS signal */
                            status = (SER_REG (pChan->base_address, CTRL_REGA) & (~RTS_ON));
                            SER_REG (pChan->base_address, CTRL_REGA) = status;
                        }

                        break;
                    }
                }

                if (nread == 0)
                {
                    continue;
                }
            }


#if eosdebug
	    nreads[pChan->serChannel][nread]++;
#endif
            // Update stats
	    serial_counters[pChan->serChannel].rbytes += nread;

#ifdef ALARMS_ENABLED
	    if((patternAlarmEnabled != NULL) && 
	       (patternAlarmEnabled[pChan->serial_channel]==PATTERN_ALARM_ENABLED)) 
            {
                // Look for a pattern match in the data
                if (pChan->serial_channel >= 0 
                    && pChan->serial_channel < max_serial_ports())
                {
                    alarmret = patternAlarmNewData(pChan->serial_channel, 
                                                   dataptr, 
                                                   nread);
                }
	    }
#endif

            // Look at each character
            for (i = 0; i < nread; i++)
            {
                if (pChan->sw_flowc)
                {
                    if (*(dataptr+i) == pChan->xoff_ch)   
                    {                                 
                        //pChan->flow_controled= TRUE;
			pChan->tx_sw_flow = TRUE;
			serial_status[pChan->serChannel].estat |= EV_OPS;
			serl_cb_event(pChan->serial_channel, OPS_EVENT);
                        xon_found = 0;
                        continue;
                    }
                    else if (*(dataptr+i) == pChan->xon_ch)
                    {
                        //pChan->flow_controled=FALSE;
			pChan->tx_sw_flow = FALSE;
			serial_status[pChan->serChannel].estat &= ~EV_OPS;
			serl_cb_event(pChan->serial_channel, OPS_EVENT);
                        xon_found = 1;
                        continue;
                    }
                }

                space = RING_BUFFER_SPACE(&rx_buffer[pChan->serChannel]);

		if ( ((current_termios[pChan->serChannel].c_iflag 
		       & (PARMRK | IGNPAR | ISTRIP) ) == (PARMRK) )
		     && ( *(dataptr + i) == 0xff ))
		{
                    // Need to insert 2 characters
                    if (space < 2)
                    {
                        // Update overflow stats
                        pChan->rx_overflow+=2;
                        serial_counters[pChan->serChannel].noflow+=2;
                        continue;
                    }
                    else
                    {
                        RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], 0xff);
                        space--;
                    }
		}
                
                
                if (space == 0)
                {
                    // Update overflow stats
                    pChan->rx_overflow++;
                    serial_counters[pChan->serChannel].noflow++;
                }
                else
                {
                    RING_BUFFER_ADD(&rx_buffer[pChan->serChannel], *(dataptr+i));
                }

            } /* end of for loop */

        } /* if RECV_RDY */
    
    } /* end of while loop */
    
    /* Clear any pending signal interrupt conditions */
    SER_REG(pChan->base_address, STAT_REG) |= (RECV_DCD | RECV_RI | RECV_DSR | RECV_CTS); 

#if eosdebug
    if (RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel])  
	> rx_buf_maxcount2[pChan->serChannel] ) 
    {
	rx_buf_maxcount2[pChan->serChannel] = 
	    RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]);
    }
#endif

    /*  when buffer is almost full and hardware interrupt is used, disable interrupt    */
    if ((pChan->hw_flowc) && (RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]) > (RING_BUFFER_SIZE - 256)))
    {
#ifdef DEBUG_SERIAL
        printf("Disable int \n");
#endif

        //pChan->flow_controled= TRUE;
	pChan->rx_hw_flow = TRUE; 
	//serial_status[pChan->serChannel].estat |= EV_IPH;
	//serl_cb_event(pChan->serial_channel, IPH_EVENT);

#if eosdebug
	iph_up_ctr[pChan->serChannel]++;
#endif

#ifdef FLOW_REWRITE
	flowdiff = 1;
#else
        reg = SER_REG(pChan->base_address, CTRL_REGA);
	reg &= ~RTS_ON;
        SER_REG(pChan->base_address, CTRL_REGA) = reg;
#endif //FLOW_REWRITE
    }


    i= RING_BUFFER_LENGTH(&rx_buffer[pChan->serChannel]);

    if (( pChan->sw_flowc) && (i > (SIZE_OF_BUF-SW_BUF_THRESH_HOLD)) )
    {
        /* too much in the buffer */
        pChan->rx_sw_flow = TRUE;  
	serial_status[pChan->serChannel].estat |= EV_IPS;
	serl_cb_event(pChan->serial_channel, IPS_EVENT);

#ifdef FLOW_REWRITE
	flowdiff = 1;
#else
        /* waiting for XMITR_RDY */
        while (!(XMITR_RDY_IRQ & (SER_REG (pChan->base_address, STAT_REG))));

        /* send out xoff */
        SER_REG_BYTE(pChan->base_address, DATA_REG) = (pChan->xoff_ch);  
#endif //FLOW_REWRITE
    }

#ifdef FLOW_REWRITE
#ifdef ALARMS_ENABLED
    if (alarmret > PM_FLOW_CONTROL_OFF) {
	pChan->rx_alarm_flow = TRUE;
	flowdiff = 1;
    }
#endif

    if (flowdiff) {
	change_rx_flow(pChan);
    }
#endif

    if ( ( pChan->sw_flowc) && (xon_found))
    {
        // Resume transmitting data
        //SER_REG (pChan->base_address, CTRL_REGA) |= (XMITR_RDY_IRQ);
	SER_REG (pChan->base_address, CTRL_REGA) |= (XMITR_HALF_FULL_IRQ);
#if CHANGE_TX_ISR_STATE_IN_DRIVER
        NAEnableIsr (pChan->txlevel);
#endif
#ifdef DEBUG_SERIAL
        xonc++;
#endif
    }

#if eosdebug
    lines[pChan->serChannel][linecount]++;
    datalines[pChan->serChannel][datalinecount]++;

    if (linecount > 8) {
	morelines[pChan->serChannel]++;
    }
#endif

    _RESTOR_INT (prevInterruptState); 

#if 0
    // We used this code to trigger gpio so we could watch interrupt
    // behavior on a scope.
    {
        unsigned int porta;

        porta = ReadReg(NARM_PORTA);

        porta &= ~0x01;
        
        WriteReg(NARM_PORTA, porta);
    }
#endif
#ifdef DOTNETMF
    switch ( pChan->serChannel )
	{
	case 0:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM1);
		break;
	case 1:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM2);
		break;
	case 2:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM3);
		break;
	case 3:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM4);
		break;
    }
#endif
}


#if 0 // Moved into TCFLOW as TCIAOFF
#if !defined(MG_ENABLED)
/* A function provided to allow the pattern alarm functionality to tell us
 * that is now able to continue receiving data
 */
void
patternAlarmExitFlow(int port)
{
    ONCC_CHAN *pChan = &onccChan[port];
    pChan->rx_alarm_flow = FALSE;
    change_rx_flow(pChan);
}
#endif /* PLATFORM != mg */
#endif

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccPollInput()                                            ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments: Function returns OK if a character arrived, EIO on device error, ***
 *** EAGAIN if the input buffer is empty, ENOSYS if the device is interrupt-    ***
 *** only.                                                                      ***
 *** Our hardware has no flow control but ASIC has a 9 or 10 byte FIFO (manual  ***
 *** says it's 16 bytes but this does not appear to be true in actual practice).***
 *** Therefore as long as we get to this routine at least once every 8-9 ms     ***
 *** (for 9600 baud), then we should be OK.  As you can see this routine only   ***
 *** returns 1 char at a time.  So, I'm ASSuming that the caller will           ***
 *** repeatedly call us until we return EAGAIN (buffer empty).  That's what     ***
 *** I'd do if I were them and I have no documentation to describe specific     ***
 *** action of caller.                                                          ***
 **********************************************************************************
 **********************************************************************************/
static int onccPollInput (SIO_CHAN * pSioChan, char *thisChar)
{
    UINT32 reg;
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;

    reg = SER_REG (pChan->base_address, STAT_REG);  /* read port status register */
    if (reg & RECV_RDY)
    {                                  /* reading char auto clears RECV_RDY */
        *thisChar = SER_REG_BYTE (pChan->base_address, DATA_REG);
        /* need to clear remaining RX conditions */
        /* so the next char can move up in FIFO  */
        SER_REG (pChan->base_address, STAT_REG) = CLR_ALL_RX_CONDITIONS;

        if (reg & RX_ERR_CONDITION)
        {
            return (EIO);
        }
        else
        {
            return (OK);
        }
    }
    return (EAGAIN);
}


/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccPollOutput()                                           ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments: Function returns OK if a character was sent, EIO on device error,***
 *** EAGAIN if the output buffer is full, ENOSYS if the device is               ***
 *** interrupt-only.                                                            ***
 **********************************************************************************
 **********************************************************************************/
static int onccPollOutput (SIO_CHAN * pSioChan, char outChar)
{
    ONCC_CHAN *pChan = (ONCC_CHAN *) pSioChan;

    if (SER_REG (pChan->base_address, STAT_REG) & XMITR_RDY_IRQ)
    {
        SER_REG_BYTE (pChan->base_address, DATA_REG) = outChar;
        return (OK);
    }

    return (EAGAIN);
}

/* driver functions */
static SIO_DRV_FUNCS onccSioDrvFuncs =
{
#ifdef NET_OS_SPECIFIC
    (int (*)(SIO_CHAN *, int, void *)) onccIoctl,
#else
    (int (*)(void)) onccIoctl,
#endif
    onccTxStartup,
    onccCallbackInstall,
    onccPollInput,
    onccPollOutput
};

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  onccSioDevInit()                                           ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 ***                                                                            ***
 *** Comments: This routine initializeS some SIO_CHAN function pointers and     ***
 *** then resets the serial port in a quiescent state.  Before this routine is  ***
 *** called, the BSP must already have initialized all the device addresses,    ***
 *** etc. in the ONCC_CHAN structure.  (Reference WRS template & example.)      ***
 **********************************************************************************
 **********************************************************************************/
static void onccSioDevInit (ONCC_CHAN * pChan)
{
    int prevInterruptState;
    
    _DISAB_INTS (prevInterruptState);

    /* initialize the driver function pointers in the SIO_CHAN */
    pChan->sio.pDrvFuncs = &onccSioDrvFuncs;

    /* set the non BSP-specific constants */
    pChan->getTxChar = &onccDummyCallback;
    pChan->putRcvChar = &onccDummyCallback;

    //pChan->channelMode = SIO_MODE_INT;            /* undefined */
    onccInitChannel (pChan);

    _RESTOR_INT (prevInterruptState);
}




/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  sysSerialChanGet()                                         ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 *** Function Calls: None                                                       ***
 ***                                                                            ***
 *** Comments:                                                                  ***
 *** routine returns a pointer to the SIO_CHAN device associated with a         ***
 *** specified serial channel.  It is called by usrRoot() to obtain pointers    ***
 *** when creating the system serial devices '/tyCo/x'.  It is also used by the ***
 *** WDB agent to locate its serial channel.                                    ***
 **********************************************************************************
 **********************************************************************************/

static ONCC_CHAN *sysSerialChanGet (int channel)
{
    int i;

    for (i = 0; i < (int)indexOfAvailableOnccStructures; i++)
    {
        if (onccChan[i].port == channel)
        {
            return &onccChan[i];
        }
    }

    return NULL;
}





/*
 *  Function: netos_serl_enter
 *
 *  Description: First level initialization. Called before
 *               kernel starts.
 *
 *  called by: ANSI layer _enter() (DDIFirstLevelInitalize())
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */

int netos_serlenter (int channel)
{
    int idx;

    if (indexOfAvailableOnccStructures < NUM_NCC_SER_CHAN)
    {
        /*  get the index of the next serial channel available -- increment for the next time   */
        idx                       = indexOfAvailableOnccStructures++;

        onccChan[idx].rxlevel     = devParas[idx].rxintLevel;
        onccChan[idx].txlevel     = devParas[idx].txintLevel;

        /* Eventually, this will come from non-volatile storage */
        onccChan[idx].baudRate    = DEFAULT_BAUD_RATE;
        onccChan[idx].options     = CONSOLE_OPTIONS;

        /* System port address for this serial channel */
        onccChan[idx].port        = channel;
        onccChan[idx].serChannel  = idx;

        /* initialize register base address */
#ifdef NET_OS_SPECIFIC
        onccChan[idx].base_address = (volatile UINT8*)(NCC_SER_BASE + channel * CH2_OFFSET);
#else
        onccChan[idx].base_address = (UINT8*)(NCC_SER_BASE + channel * CH2_OFFSET);
#endif

        /*  initialize UART */
        onccSioDevInit (&onccChan[idx]);
        return EXIT_SUCCESS;
    }

    return EINVAL;
}

static void 
serl_monitor(unsigned long dummy) 
{
    ONCC_CHAN *pChan;
    SIO_CHAN *pSioChan;
    struct serial_status_t status;
    int i =  dummy; // Just to use dummy
    int prevInterruptState;

    while (1) {
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA 
        int sleep_time = SERL_POLL_RATE;
#endif
	for (i = 0 ; i < NUM_NCC_SER_CHAN ; i++) {
	    unsigned char mprev;
	    unsigned char mdiff;

	    pChan = &onccChan[i];
	    pSioChan = (SIO_CHAN*)pChan;

	    mprev = serial_status[pChan->serChannel].mstat;

	    digiIoctl(pSioChan, TCGETSTATS, (int)&status);

	    mdiff = mprev ^ status.mstat;

	    if (mdiff & MS_DSR) {
		serial_sigchange[pChan->serChannel].dsr_counter++;
		serl_cb_event(pChan->serial_channel, DSR_DELTA);
	    }
	    if (mdiff & MS_DCD) {
		serial_sigchange[pChan->serChannel].dcd_counter++;
		serl_cb_event(pChan->serial_channel, DCD_DELTA);
	    }
	    if (mdiff & MS_RI) {
		serial_sigchange[pChan->serChannel].ri_counter++;
		serl_cb_event(pChan->serial_channel, RI_DELTA);
	    }
	    if (mdiff & MS_CTS) {
		serial_sigchange[pChan->serChannel].cts_counter++;
		serl_cb_event(pChan->serial_channel, CTS_DELTA);
	    }
	    if (mdiff & MS_RTS) {
		serial_sigchange[pChan->serChannel].rts_counter++;
		serl_cb_event(pChan->serial_channel, RTS_DELTA);
	    }
	    if (mdiff & MS_DTR) {
		serial_sigchange[pChan->serChannel].dtr_counter++;
		serl_cb_event(pChan->serial_channel, DTR_DELTA);
	    }

	    if (rx_note[pChan->serChannel] != rx_noted[pChan->serChannel]) {
		rx_noted[pChan->serChannel] = rx_note[pChan->serChannel];
		serl_cb_event(pChan->serial_channel, RX_DATA);
#if NETWORK_STACK == TRECK
		if (pChan->checkMask & NA_SELECT_READ)
		    tfDeviceSelectNotify(NA_SELECT_READ, pChan->select_context);
#endif
#ifdef SERIAL_ACTIVITY_LED_ENABLED
		led_mark_activity( SERIAL_ACTIVITY_LED0 );
#endif
	    }
	
	    if (pChan->breakstatus == BRK_ACTIVE) {
		pChan->breaktime -= SERL_POLL_RATE;
		if (pChan->breaktime <= 0) {
                    _DISAB_INTS(prevInterruptState);
		    //DISABLE_INTS;
                    SER_REG (pChan->base_address, CTRL_REGA) &= ~SEND_BREAK;
		    //pChan->serl_regs->ctrl_a &= ~BRK;
		    //pChan->status->estat &= ~EV_TXB;
                    serial_status[pChan->serChannel].estat &= ~EV_TXB;
		    //RESTORE_INTS;
                    _RESTOR_INT(prevInterruptState);
                    serl_cb_event(pChan->serial_channel, TXB_EVENT);

		    pChan->breaktime = 0;
		    pChan->breakstatus = BRK_IDLE;
		    //tx_event_flags_set( &pChan->blocking_flags, BRK_BLOCKING, 
                    //TX_OR);
		}
	    }

            if (RING_BUFFER_EMPTY(&tx_buffer[pChan->serChannel])
                && (SER_REG(pChan->base_address, STAT_REG) & XMITR_EMPTY)) 
            {
                if (pChan->breakstatus == BRK_REQUESTED) {
		    // Start a break
                    _DISAB_INTS(prevInterruptState);
		    //DISABLE_INTS;
                    SER_REG (pChan->base_address, CTRL_REGA) |= SEND_BREAK;
		    //pChan->serl_regs->ctrl_a |= BRK;
                    _RESTOR_INT(prevInterruptState);
		    //RESTORE_INTS;
                    
		    pChan->breakstatus = BRK_ACTIVE;
		}
            }

#if NETWORK_STACK == TRECK
	    if ( RING_BUFFER_LENGTH(&tx_buffer[pChan->serChannel])
		 < (RING_BUFFER_SIZE / 2) ) 
	    {
		if (pChan->checkMask & NA_SELECT_WRITE)
		    tfDeviceSelectNotify(NA_SELECT_WRITE, pChan->select_context);
	    }
#endif

	    serial_status[pChan->serChannel].mstat = status.mstat;
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA 
            if ((pChan->hw_flowc) && 
               (!(SER_REG (pChan->base_address, STAT_REG) & CTS_STATE)))
            {
                sleep_time = 1;
            }
            
            onccHandleCTSChange(pChan);            
#endif
	}
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
        tx_event_flags_get( &cntrl_flags, NA_CTS_CHANGE, 
                    TX_OR_CLEAR, &actual_flags, sleep_time);
#else 
	tx_thread_sleep(SERL_POLL_RATE); 
#endif
    }
}


/*
 *  Function: netos_serl_init
 *
 *  Description: Second level initialization, after kernel is loaded.
 *               Complete any initialization needed by the driver.
 *
 *  called by: DDISecondLevelInitialize()
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int netos_serlinit (int channel)
{
    static int thread_started = 0;
    ONCC_CHAN *pChannel;
    struct termios tios;
    UINT tx_retval;
    extern void serial_init(int, struct termios *);

    pChannel = (ONCC_CHAN  *) sysSerialChanGet (channel);
    if (pChannel != NULL)
    {        
	pChannel->serial_channel = serial_channel(MAJOR_NETOS_SERL, channel);

        tx_retval = tx_timer_create(&pChannel->predelay_timer, "predelay",
                                    predelay_fn, (ULONG) pChannel,
                                    1, 0, TX_NO_ACTIVATE);

        assert(tx_retval == TX_SUCCESS);

        tx_retval = tx_timer_create(&pChannel->postdelay_timer, "postdelay",
                                    postdelay_fn, (ULONG) pChannel,
                                    1, 0, TX_NO_ACTIVATE);

        assert(tx_retval == TX_SUCCESS);
                                    
        /*  install receive interrupt function  */
        NAInstallIsr (devParas[pChannel->serChannel].rxvector, (NA_ISR_HANDLER)onccIntRd, (void*) pChannel);

        /*  install send interrupt function */
        NAInstallIsr (devParas[pChannel->serChannel].txvector, (NA_ISR_HANDLER) onccIntWr, (void*) pChannel);

	onccIoctl ((SIO_CHAN*)pChannel, SIO_MODE_SET, SIO_MODE_INT);

	memset( &tios, 0, sizeof(tios) );

#ifndef NET_OS_SPECIFIC
    //netos driver should come up with no flow control
	tios.c_iflag = IXON | IXOFF;
#endif
	tios.c_oflag = 0 ;
	tios.c_cflag = CS8 | CREAD ;
	tios.c_lflag = 0;
	tios.c_cc[VSTART] = XON;
	tios.c_cc[VSTOP] = XOFF;
	tios.baud = 9600 ;

	
	serial_init(pChannel->serial_channel, &tios);

        RING_BUFFER_RESET (&rx_buffer[pChannel->serChannel]);       /* reset the receive buffer for channel */
        RING_BUFFER_RESET (&tx_buffer[pChannel->serChannel]);       /* reset the send buffer for channel */

	digiIoctl( (SIO_CHAN*)pChannel, TCSETA, (int) &tios );
#if 0
	if (channel == 0)
	    onccIoctl ((SIO_CHAN*)pChannel, SIO_MODE_SET, SIO_MODE_FIQ);
	else
	    onccIoctl ((SIO_CHAN*)pChannel, SIO_MODE_SET, SIO_MODE_INT);
#endif

	/* Start up monitoring thread, for signal deltas etc. */
	/* Only one thread for all ports, serl_monitor() */
	if (!thread_started) {
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
            tx_event_flags_create(&cntrl_flags, "Flags for Serial Control");
#endif 

#ifdef NET_OS_SPECIFIC
        tx_thread_create(
                             &serl_monitor_thread,
                             "Serial Monitor",
                             serl_monitor,
                             0,         //dummy
                             &serl_monitor_stack,
                             MONITOR_STACK_SIZE,        // stack size
                             15,        // arbitrary priority
                             15,        // arbitrary threshold
                             10,        // preemption from inetd_epoch
                             TX_AUTO_START );
#else	 	    
	    tx_thread_create(
			     NULL,	//Dynamically allocate thread
			     "Serial Monitor",
			     serl_monitor,
			     0,		//dummy
			     NULL,	//dynamically allocate stack
			     4096,	// stack size
			     14,	// priority
			     14,	// threshold
			     10,        // timeslice
			     TX_AUTO_START );
#endif	    
	    thread_started = 1;
	}

        return (EXIT_SUCCESS);
    }

    return EBADF;
}


/*
 *  Function: netos_serlopen()
 *
 *  Description: open a new session on a channel
 *
 *  called by: open (), creat () in DDI
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *      flags        a bit field formed by orring one or more
 *                   of O_RDONLY, O_WRONLY, O_RDWR, O_NONBLOCK
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */

int netos_serlopen (int channel, unsigned flags)
{
    ONCC_CHAN  *pChannel;
#ifdef FLOW_REWRITE
#ifndef NET_OS_SPECIFIC  /* variable "openflowed" was set but never used */
    int openflowed = 0;
#endif
#endif
    pChannel = sysSerialChanGet (channel);
    if (pChannel == NULL)
    {
        return EBADF;
    }

    if (pChannel->opened)                /* no need to open if its already open */
    {
        return EBUSY;
    }

#ifdef NET_OS_SPECIFIC  
    /* force DMA for baudrate more than 230k */
    if (pChannel->baudRate > 230400)
    {
        flags |= O_DMA;
    }
#endif

    /* Fail if unsupported flags are requested. */
    if (flags & ~(O_ACCMODE|O_DMA|O_NONBLOCK|O_NOFLUSH))
    {
	return EINVAL;
    }

    /* Only one of these is allowed. */
    if (((flags & O_ACCMODE) != O_RDONLY)
	&& ((flags & O_ACCMODE) != O_WRONLY)
        && ((flags & O_ACCMODE) != O_RDWR))
    {
        return EINVAL;
    }

    /*  Lock serial channel resource, so HDLC can't open a channel */
    if (!GetResource(&serl_resource_lock[pChannel->serChannel], pChannel))
    {
        return EACCES;    /* HDLC channel open */
    }

    /* Turn on rx interrupt unless it is write only */
    if (flags & O_DMA) {
	    prevmode[pChannel->serChannel] = pChannel->channelMode;
        onccIoctl ((SIO_CHAN*)pChannel, 
		   SIO_MODE_SET, 
		   SIO_MODE_DMA);
    }
    else 
    {
        if ((flags & O_ACCMODE) != O_WRONLY)
        {
            //onccIoctl ((SIO_CHAN*) pChannel, SIO_MODE_SET, SIO_MODE_INT);
	    //Keep the mode we're configured for
        }
    }

    pChannel->opened         = 1;        /* Updated the opened flag */
    pChannel->openmode       = flags;    /* updated the opened mode */
    pChannel->echo           = 0;        /* initialized the echo, sendcr and overflow flag */
    //pChannel->opMode         = 0;
    pChannel->rx_overflow    = 0;

    //pChannel->flow_controled = FALSE;    /* initialized the flow control to False - free to send */
    /* No flow control statuses active on a port open */

#ifdef FLOW_REWRITE
#ifndef NET_OS_SPECIFIC  /* variable "openflowed" was set but never used */
    if (pChannel->rx_hw_flow || pChannel->rx_sw_flow) {
	openflowed = 1;
    }
#endif
#endif

    if (!(flags & O_DMA) && !(flags & O_NOFLUSH) )
    {
        RING_BUFFER_RESET (&rx_buffer[pChannel->serChannel]);       /* reset the receive buffer for channel */
        RING_BUFFER_RESET (&tx_buffer[pChannel->serChannel]);       /* reset the send buffer for channel */
        check_rx_flow(pChannel);
    }

    /* create semaphore here */
    if ((flags & O_ACCMODE) == O_RDWR)
    {
        _OPEN_SEMA(&r_sem[pChannel->serChannel]);
        _OPEN_SEMA(&w_sem[pChannel->serChannel]);
    }
    else if ((flags & O_ACCMODE) == O_RDONLY)
    {
        _OPEN_SEMA(&r_sem[pChannel->serChannel]);
    }
    else if ((flags & O_ACCMODE) == O_WRONLY)
    {
        _OPEN_SEMA(&w_sem[pChannel->serChannel]);
    }

    //onccIoctl ((SIO_CHAN *) pChannel, SIO_BAUD_SET, (int)(&pChannel->baudRate));

    return EXIT_SUCCESS;
}

/*
 *  Function: netos_serlclose()
 *
 *  Description: close session on a channel
 *
 *  called by: close () in DDI
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int netos_serlclose (int channel)
{
    unsigned openmode;
    ONCC_CHAN  *pChannel;

    pChannel = sysSerialChanGet (channel);
    if (pChannel == NULL)
    {
        return EBADF;
    }

    if (!pChannel->opened)                     /* check to see if device is already closed */
    {
        return EBADF;
    }
#ifdef NET_OS_SPECIFIC
    /* netos is okay with just the drain forever behavior */
    digiIoctl ((SIO_CHAN *) pChannel, TCDRAIN, 0);
#else
    /* Block for transmit data to drain */
    close_logic(MAJOR_NETOS_SERL, channel);      
#endif

    if (pChannel->openmode & O_DMA) {
        onccIoctl ((SIO_CHAN*)pChannel, 
		   SIO_MODE_SET, 
		   prevmode[pChannel->serChannel]);
    }

    /* Clean up access semaphores */
    openmode = pChannel->openmode;        /* reads the device mode */
    if ((openmode & O_ACCMODE) == O_RDWR) /* if device is read and write */
    {
        _GET_SEMA(&w_sem[pChannel->serChannel]);
        _CLOSE_SEMA(&w_sem[pChannel->serChannel]);

        _GET_SEMA(&r_sem[pChannel->serChannel]);
        _CLOSE_SEMA(&r_sem[pChannel->serChannel]);
    }
    else if ((openmode & O_ACCMODE) == O_RDONLY)
    {
        _GET_SEMA(&r_sem[pChannel->serChannel]);
        _CLOSE_SEMA(&r_sem[pChannel->serChannel]);
    }
    else if ((openmode & O_ACCMODE) == O_WRONLY)
    {
        _GET_SEMA(&w_sem[pChannel->serChannel]);
        _CLOSE_SEMA(&w_sem[pChannel->serChannel]);
    }

    #ifndef NET_OS_SPECIFIC /*statment with no effect in NETOS*/
    ReleaseResource(&serl_resource_lock[pChannel->serChannel], pChannel);
    #endif
    pChannel->opened    = 0;    /* updates the opened flag */
    pChannel->openmode  = 0;    /* updates the openmode flag */

    serl_cb_event(pChannel->serial_channel, CLOSE_EVENT);
    
    return EXIT_SUCCESS;
}

/*
 * Function: netos_serlread()
 *
 * Description: read data from driver
 *
 * Called by: DDI read
 *
 * Input : channel - 0 for port com1, 1 for port com2
 *         buffer - data to be stored
 *         length - size of data buffer
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */

int netos_serlread (int channel, void * buffer, int length, int *bytesRead)
{
    ONCC_CHAN  *pChannel;
    int err_code = EXIT_SUCCESS;
    int prevInterruptState;

    pChannel = sysSerialChanGet (channel);
    if (pChannel == NULL)
    {
        return EBADF;
    }

    if (!pChannel->opened)                     /* make sure device is opened */
    {
        return EBADF;
    }

    if (pChannel->openmode & O_WRONLY)         /* make sure device is not write only */
    {
        return EINVAL;
    }

    *bytesRead = 0;
    _GET_SEMA(&r_sem[pChannel->serChannel]);                /* get the read semaphore */

    if (pChannel->openmode & O_DMA) {
	err_code = serial_dma_read(pChannel, 
				   (char *)buffer, length, bytesRead);
	PUT_SEMA(&r_sem[pChannel->serChannel]);
	return err_code;
    }

    /*  per PPP Ping problem -- reported by Softworks   */
    _DISAB_INTS (prevInterruptState);

    if (pChannel->openmode & O_NONBLOCK)   /* check to see if its non blocking */
    {
        if (RING_BUFFER_EMPTY(&rx_buffer[pChannel->serChannel]))  /* if buffer is empty return immediately */
        {
            PUT_SEMA(&r_sem[pChannel->serChannel])
            
            /*  per PPP Ping problem -- reported by Softworks   */
            _RESTOR_INT (prevInterruptState);
            return EAGAIN;
        }
    }
    else
    {                                 /* in blocking mode */
        /*  per PPP Ping problem -- reported by Softworks   */
        _RESTOR_INT (prevInterruptState);
        while (RING_BUFFER_EMPTY(&rx_buffer[pChannel->serChannel]))  /* wait for buffer to have data */
        {
            tx_thread_sleep(1);  /* block */
        }

        /*  per PPP Ping problem -- reported by Softworks   */
        _DISAB_INTS (prevInterruptState);
    }

    _RESTOR_INT (prevInterruptState);
    *bytesRead = ExtractSerialDataIntoUsersBuffer (&rx_buffer[pChannel->serChannel], buffer, length);

    check_rx_flow(pChannel);

    /* echo back */
    if (pChannel->echo && (pChannel->openmode & O_RDWR))  /* if echo is needed, echo back */
    {
        echo (pChannel->echo, pChannel->serChannel, buffer, *bytesRead);
    }

#ifdef PORTBUFFERING_ENABLED
    if (*bytesRead) { // Perhaps add a check for PB on here as well?
        if (pChannel->serial_channel >= 0 
            && pChannel->serial_channel < max_serial_ports())
        {
            iPBuff_SaveRxData(pChannel->serial_channel, *bytesRead, buffer);
        }
    }
#endif

    PUT_SEMA(&r_sem[pChannel->serChannel])                /* releases semaphore when done */
    return err_code;
}


/*
 * Function: netos_serlwrite()
 *
 * Description: write data to driver
 *
 * Called by: DDI write
 *
 * Input : channel - 0 for port com1, 1 for port com2
 *         buffer - data to be sent
 *         length - size of data
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */

int netos_serlwrite (int channel, void * buffer, int length, int *bytesWritten)
{
    ONCC_CHAN  *pChannel;
    int err_code = EXIT_SUCCESS;
    int remain = length;                  /* remain has number of bytes that needs to be sent */
    int pos=0, nbyte, max_free_space;

    pChannel = sysSerialChanGet (channel);
    if (pChannel == NULL)
    {
        return EBADF;
    }

    if (!pChannel->opened)                     /* make sure device is opened */
    {
        return EBADF;
    }

    *bytesWritten = 0;

    _GET_SEMA(&w_sem[pChannel->serChannel]);                /* get the write semaphore */
    if (pChannel->openmode & O_DMA) {
	err_code = serial_dma_write(pChannel, 
				    buffer, length, bytesWritten);
	PUT_SEMA(&w_sem[pChannel->serChannel]);
	return err_code;
    }

    if (pChannel->breakstatus != BRK_IDLE) {
        if (pChannel->openmode & O_NONBLOCK) {
            PUT_SEMA(&w_sem[pChannel->serChannel]);
            return EAGAIN;
        } else {
            // Block for break to finish
            while (pChannel->breakstatus != BRK_IDLE) {
                tx_thread_sleep(1);
            }
        }
    }

    while (remain > 0)                    /* while there are still chars to be sent */
    {
        /* we have at least max_free_space at this point */
        
        max_free_space = RING_BUFFER_SPACE(&tx_buffer[pChannel->serChannel]);  /* calculate number of bytes  */
        if (max_free_space == 0)
        {
            if (pChannel->openmode & O_NONBLOCK) /* non-blocking */
            {
                onccTxStartup ((SIO_CHAN*) pChannel);  /* starts sending and returns immediately  */
                *bytesWritten = length - remain;  /* calculate bytesWritten */
                PUT_SEMA(&w_sem[pChannel->serChannel])  /* release the write semaphore */

                /* This cannot return EAGAIN because nonBlocking will not work with that.
                   The application programmer has to use the number of byte written to find
                   out if the write operation is successful. */
                if (*bytesWritten == 0) {
                    return EAGAIN;
                } else {
                    return EXIT_SUCCESS;  
                }
            }
            else
            {                            /* blocking mode */
                /* better put a delay here */
                while (RING_BUFFER_FULL(&tx_buffer[pChannel->serChannel]))
                {
                    onccTxStartup ((SIO_CHAN*) pChannel);
                    tx_thread_sleep (1);
                }

                /* we have at least max_free_space at this point */
                max_free_space = RING_BUFFER_SPACE(&tx_buffer[pChannel->serChannel]);
            }
        }

        /* figure out how many bytes we should put into the buffer */
        nbyte = (remain <= max_free_space)? remain : max_free_space;
        remain -= nbyte;

        /* copy data into buffer */
        if (pChannel->opMode & OP_MODE_INSERT_CR) /* sends char return character */
        {
            while (nbyte)
            {
                if (((char*)buffer)[pos] == '\n')
                {
                    /* we need an extra space on tx_buffer */
                    max_free_space = RING_BUFFER_SPACE(&tx_buffer[pChannel->serChannel]);
                    if (max_free_space > nbyte) /* it is OK to put the extra char */
                    {
                        RING_BUFFER_ADD(&tx_buffer[pChannel->serChannel], '\r');
                    }
                    else
                    {
                        if (pChannel->openmode & O_NONBLOCK) /* non-blocking */
                        {
                            onccTxStartup ((SIO_CHAN*) pChannel);
                            remain += nbyte;
                            *bytesWritten = length - remain;
                            PUT_SEMA(&w_sem[pChannel->serChannel])
                            if (*bytesWritten == 0)
                                return EAGAIN;
                            else
                                return EXIT_SUCCESS;
                        }
                        else
                        {
                            /* better put a delay here */
                            while (max_free_space <= nbyte)
                            {
                                onccTxStartup ((SIO_CHAN*) pChannel);
                                tx_thread_sleep (1);
                                max_free_space = RING_BUFFER_SPACE(&tx_buffer[pChannel->serChannel]);
                            }
                            
                            RING_BUFFER_ADD(&tx_buffer[pChannel->serChannel], '\r');
                        }
                    }
                }

                RING_BUFFER_ADD(&tx_buffer[pChannel->serChannel], ((char*)buffer)[pos++]);
                nbyte--;
            }
        }
        else
        {
            int result;

            result  = InsertUsersDataBuffer(
	                  (ring_buffer_t *)&tx_buffer[pChannel->serChannel], 
			  (char *)buffer + pos, 
			  nbyte);
            nbyte                      -= result;
            pos                        += result;
        }

        /* turn on the transmitter */
        onccTxStartup ((SIO_CHAN*) pChannel);
    }
    
    /* if it is blocking, we got here because all the data is sent */
    *bytesWritten = length;

    PUT_SEMA(&w_sem[pChannel->serChannel])

    return err_code;
}


/*
 * Function: netos_serlioctl()
 *
 * Description This function calls onccIoctl to sets the serial parameters
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              err_code returned from onccIoctl
 */

int netos_serlioctl (int channel, int request, char *args)
{
    ONCC_CHAN  *pChannel;

    pChannel = sysSerialChanGet (channel);
    if (pChannel == NULL)
    {
        return EBADF;
    }

#if 0 /* Can no longer make this check, non-data opens won't set 'opened' */
    if (!pChannel->opened )/*make sure device is opened*/
    {
        return EBADF;
    }
#endif

    return digiIoctl ((SIO_CHAN *) pChannel, request, (int) args);
}


#if NETWORK_STACK == TRECK
/*
 * Function: netos_serlpoll()
 *
 * Description This function is called to indicate that a device select is
 * in progress.  It will be called two times.  The first time, context will
 * be non-zero, and checkMask indicates the fd_set bits that are requested.
 * The function must return the bits that the device satisfies.
 * The second time it is called, context is zero.  It must return the checkMask
 * bits that are satisfied. 
 * In the time between the two calls, if any event listed in the checkMask
 * becomes satisfied, the driver must call tfDeviceSelectNotify with the context.
 *
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 */
int netos_serlpoll(int channel, int insert_remove, int *checkMask, ulong context)
{
    int ret = 0;
    
    ONCC_CHAN *pChannel = sysSerialChanGet (channel);
#ifndef NET_OS_SPECIFIC
    assert(pChannel);
#endif
    if (pChannel == NULL)
	return EBADF;

    // save checkMask and context.  We only support one select at a time
    // in this driver.
    if (insert_remove == NA_SELECT_INSERT)
    {
	pChannel->select_context = context;
	pChannel->checkMask = *checkMask;
    }
    else
    {
	pChannel->select_context = 0;
	pChannel->checkMask = 0;
    }
    if ((*checkMask & NA_SELECT_READ) 
	     && (!RING_BUFFER_EMPTY(&rx_buffer[pChannel->serChannel])))
     	    ret |= NA_SELECT_READ; 
    if ((*checkMask & NA_SELECT_WRITE) 
	    && (RING_BUFFER_LENGTH(&tx_buffer[pChannel->serChannel])
		 < (RING_BUFFER_SIZE / 2)))
    	    ret |= NA_SELECT_WRITE; 
    // if ((*checkMask & NA_SELECT_EXCEPTION) && device has an exception)
    //	    ret |= NA_SELECT_EXCEPTION; 
    *checkMask = ret;
    return 0;
}
#endif /* NETWORK_STACK == TRECK */






/*
 * Function: echo()
 *
 * Description: whenever a byte is receive, write it back.
 *
 * Called by: netos_serlread()
 *
 * Input : arg - TERM_ECHO_ON or TERM_ECHO_OFF, TERM_ECHO_TRANSLATE
 *         channel - 0 for port com1, 1 for port com2
 *         buffer - data to be sent
 *         length - size of data
 *
 * Return: None
 *
 */

static void echo (int arg, int channel, void *buffer, int len)
{
    int dontcare;
    int pos, i;
    char *ptr;
    static int backslashRstate = 0;

    if (arg == TERM_ECHO_TRANSLATE)
    {
        ptr = (char*) buffer;
        pos = 0;
        i   = 0;

        if ((len > 0) && (backslashRstate == 1))
        {
            backslashRstate = 0;

            if (*ptr != '\n')
            {
                /*  send the \r */
                netos_serlwrite (channel, "\r", 1, &dontcare);
            }
        }

        while (i < len)
        {
            i = pos;

            while (i < (len - 1))
            {
                /*  point to an instance of "\r\n"  */
                if ((ptr[i] == '\r') && (ptr[i + 1] == '\n'))
                {
                    break;
                }
                else
                {
                    i++;
                }
            }
            
            if (i < (len - 1))
            {
                if ((i - pos) > 0)
                {
                    /*  i points to an instance of "\r\n" -- echo before the i-th character */
                    netos_serlwrite (channel, ptr + pos, (i - pos), &dontcare);
                }

                /*  jump in front of the /r character   */
                i++;
                pos = i;
            }
            else 
            {
                if (ptr[i] == '\r')
                {
                    /*  the last character is a \r -- save this state to detect a \n    */
                    backslashRstate = 1;

                    /*  echo all but the \r */
                    netos_serlwrite (channel, ptr+pos, (i - pos), &dontcare);
                }
                else
                {
                    /*  this is the last one    */
                    netos_serlwrite (channel, ptr+pos, (i - pos) + 1, &dontcare);
                }
                return;
            }
        }
    }
    else
    {
        netos_serlwrite (channel, buffer, len, &dontcare);
    }
}

#if 0
/*
 * Function: isatty()
 *
 * Returns: 1 - if the device is an open serial port, 0 - otherwise
 *
 */
int isatty(int desc)
{
    extern int NAserialComPortFds[];

    if (desc == NAserialComPortFds[0])
    {
        /*  yes -- it's /com/0   */
        if (onccChan[0].opened)
        {
            return 1;
        }
    }
    else if (desc == NAserialComPortFds[1])
    {
        /*  yes -- it's /com/1  */
        if (onccChan[1].opened)
        {
            return 1;
        }
    }

    return 0;
}
#endif

#if !defined(MG_ENABLED) && !defined(SP2PORT_ENABLED)

#if 0 // Moved to serial_help.cc
int 
max_serial_ports(void)
{
    /* Allow bcomm to tell us how many serial ports there are */
    return bcomm.bc_ports;
}
#endif

#endif /* PLATFORM != mg && sp2port*/

/*
 * Function: serial_isopen()
 *
 * Description: Allows the system to query whether a port is being held open
 *
 * Input : Channel number of the port in the system, 0-based
 *
 * Return: 1 - Port is open, 0 - Port is not open
 *
 */
static int
serial_isopen_int(int chan)
{
    if (chan < 0 || chan > max_serial_ports() ) {
	return 1; //Best say that an invalid port is always open
    }
    return onccChan[chan].opened;
}
