#ifdef NET_OS_SPECIFIC
#undef NDS_SPECIFIC
#define NONCACHED_ALLOC 1 /* FIXME: Correct caching. Really, not uncached */
#define SHORT_CIRCUIT_DATA 0
#include "nammu.h"
#include "nainvalidate.h"
#define MCInstallIsr(a, b, c, d) naIsrInstall(a, b, c)

/* #define NA_SERIAL_ENABLE_GAP_TIMER_ARM9_SYNCHRONIZATION_ERRATA */
#ifdef NA_SERIAL_ENABLE_GAP_TIMER_ARM9_SYNCHRONIZATION_ERRATA
#define MERC_TIMER_ERRATA_WORKAROUND_ENABLED
#include "gpiomux_def.h"
#endif

/* turn it on whenever you like to control CTS in software */
#define NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
#else
#define NDS_SPECIFIC 1
#define NONCACHED_ALLOC 1 /* FIXME: Correct caching. Really, not uncached */
#define BSP_ARM9
#define SHORT_CIRCUIT_DATA 0
#endif

/* MERC_TIMER_ERRATA_WORKAROUND_ENABLED This works around a problem in
the 9750, 9360, and 9775 where if a start bit edge enters the UART
during a 150 ns window after either the character gap timer or the
buffer gap timer fires and is processed, the start bit is missed
causing a loss of synchronization with incoming data (usually seen as
a Frame Error).

The fix is to add 2 D flip flops in series with Rx clocked by the baud
clock which is output on RI. This guarantees that the 150 ns window
cannot happen at the same time a start bit edge enters the UART as
long as the timers are multiples of the baud clock.  Timers must be
set such that the result of their formulas are whole numbers and must
be multiples of the baud clock.  Some baud rates, therefore cannot be
supported (134 is an example).

This only works for 16x clock and TMODE must be off (TMODE turns on
intracharacter timing adjustments that make the work around fail).
This causes us to lose low and high rates.  RICS_BRG must also used.

RI is typically recovered by moving it to a GPIO pin with
corresponding changes to this driver.  Finally, the entire fix can be
ignored if both timers are turned off and the FIFO emptied through
another mechanism such as by an error, buffer full, etc.
*/

#include "mercury_serl.h"
#include "errno.h"
#include "string.h"
#include "tx_api.h"
#include "NA_ISR.h"
#include "sysClock.h"
#include "ddi.h"
#include "mcpll.h"
#include "netosIo.h"

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

#if NDS_SPECIFIC

#include "assert.h"
#include "alarm_if.hh"
#include "nammu.h"
#include "nainvalidate.h"
#include "reg_def.h"

#else

#include "Npttypes.h"

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 NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
#define NA_CTS_CHANGE      1

static TX_EVENT_FLAGS_GROUP cntrl_flags;
static ULONG actual_flags;
static int tx_serl_int (void *handle);

#ifdef NS9750
/* This reflects the skid rate, set it accordingly */
#define TX_BMD_SIZE     16
#endif 
#endif  /* NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA */

#endif /* NDS_SPECIFIC */

#include "netosIo.h"


#include "mcreg_def.h"
#include "mcdma_def.h"

#if !NDS_SPECIFIC
#include "mc_isr.h"
#endif

#if !NONCACHED_ALLOC
#include "noncache.h"
#endif

#include "bum_reg.h"

#if NDS_SPECIFIC
#include "hw_strapping.h"
#else
#endif

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

#include "gpio.h"
#ifdef SERIAL_4_RX_LEDS_ENABLED
#include "led_driver.h"
#endif


static int serial_isopen_int(int chan);

/*
 * Serial channel resource lock (used by serial driver & HDLC driver
 */
int serl_resource_lock[4] = {0,0,0,0};

/*
 * The following routones provide simple resource lock mechanism
 */
int GetResource(int *resource, void *owner);
int ReleaseResource(int *resource, void *owner);

/* Function to notify event handling routines of event occurance */
extern void serl_cb_event(int, enum events);
static int max_merc_ports(void);
static int rx_dma_int(void *handle);
static int tx_dma_int(void *handle);

#define EXIT_SUCCESS    0
#define OK      EXIT_SUCCESS
#define ERROR       (-1)

#define INTERRUPT_SAVE_AREA int interrupt_save
#define DISABLE_INTS        interrupt_save = NASetIRQ(IRQ_DISABLED)
#define RESTORE_INTS        NASetIRQ(interrupt_save)

/* Unfortunately, the serl regs are not contiguous in memory so we can't 
 * just map an array 
 */
static merc_serl_t * const serl_regs[NUM_MERC_SERL_PORTS] = {
    MERCURY_REGS_0,
    MERCURY_REGS_1,
    MERCURY_REGS_2,
    MERCURY_REGS_3,
};

/* BMD layout:
 *  Chan 0 BMDs
 *    - RX BMDs
 *    - TX BMDs
 *  Chan 1 BMDS.
 *  ...
 */
#if !NONCACHED_ALLOC
static merc_bmd_t *bmds;
static uint8 *rx_bufs;
static uint8 *tx_data_buf;
#else
static merc_bmd_t bmds[NUM_MERC_SERL_PORTS *  NUM_BMDS_PER_CHANNEL * 2 ];
/* Align this DMA buffer to the start of a cache line to avoid any cache
 * coherency issues.
 */
#if defined(NDS_SPECIFIC) || defined(NETOS_GNU_TOOLS)
static uint8 rx_bufs[NUM_MERC_SERL_PORTS * RX_BUF_SIZE] __attribute__ ((aligned(32)));
#else
#pragma alignvar (32)
static uint8      rx_bufs[NUM_MERC_SERL_PORTS * RX_BUF_SIZE];
#endif /* #if defined(NDS_SPECIFIC) || defined(NETOS_GNU_TOOLS) */

static uint8 tx_data_buf[NUM_MERC_SERL_PORTS * TX_BUF_SIZE];
#endif

/* Physical addresses of buffer bases, used to move from phys back to
   virtual */
static uint8 *rx_phys;
static uint8 *tx_phys;

#define RXSRC_ADDR(x)	((x)->src_addr - rx_phys + rx_bufs)
#define TXSRC_ADDR(x)	((x)->src_addr - tx_phys + tx_data_buf)

static uint32 txlengths[NUM_MERC_SERL_PORTS][NUM_BMDS_PER_CHANNEL];

static struct circ_queue tx_buffer[NUM_MERC_SERL_PORTS];

static dfrag dfrags[NUM_MERC_SERL_PORTS][N_DFRAG];
static rxmsg rxmsgs[NUM_MERC_SERL_PORTS][N_RXMSG];

static merc_dma_t * const dma_bmd_regs = DMA_BUFFER_DESC_PTR_BASE;
static merc_dma_t * const dma_ctrl_regs = DMA_CONTROL_REGS_BASE;
static merc_dma_t * const dma_status_regs = DMA_STATUS_REGS_BASE;

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

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

static merc_port_t ports[NUM_MERC_SERL_PORTS];

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
static int serl_tx_intr[NUM_MERC_SERL_PORTS] =
{
    SER2TX_INTERRUPT,
    SER1TX_INTERRUPT,
    SER3TX_INTERRUPT,
    SER4TX_INTERRUPT    
};
#endif

int netos_serlioctl(int channel, int request, char *args);
static VOID rtsdown_fn(ULONG);
static VOID rtsup_fn(ULONG);
static void serl_monitor(void);
static void assemble_messages(merc_port_t *); 


static struct termios tios_support = {
    SUPPORTED_IFLAGS,
    SUPPORTED_OFLAGS,
    SUPPORTED_CFLAGS,
    SUPPORTED_LFLAGS,
    SUPPORTED_XFLAGS,
    {
    1, /* VSTART */
    1, /* VSTOP */
    1, /* VLNEXT */
    1, /* VXSTART */
    1, /* VXSTOP */
    1, /* VPREDELAY */
    1, /* VPOSTDELAY */
    0, /* VBITGAP */
    },
    1, /* baud */
};

#if ENABLE_TESTPAT_CHECKER
/* STOP ALL SERIAL DMA NOW! */
void serial_stop(void)
{
    int i;
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS;	// Turn off ints and leave them off!
    for (i = 0; i < max_merc_ports(); ++i)
    {
        ports[i].serl_regs->ctrl_a &= ~CE;
    }
}
#endif



// Given a particular flow control state as expressed in the status varables
// this function will take the actions necessary to enable/disable the 
// transmitter
//
// Called by 
//  serial_monitor
//  sp_int_handler rx handling
//  tcflow processing
static void
change_tx_flow(merc_port_t *port) 
{
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS; // Protect estat from other tasks and the int
    if ( port->status->estat & EV_OPALL ) {
    /* Disable transmitter */
    port->serl_regs->flow_ctrl |= FS_XOFF;
    } else {
    /* Enable transmitter, if state is we think we were flow controlled
     * by software.
     * Unfortunately there WILL be possible interactions with the hardware
     * where flow control can be turned off even though we don't want
     * it to be because of the shared nature of the state UGH!. */
    if ((port->serl_regs->flow_ctrl & FS_HW) == 0) {
        port->serl_regs->flow_ctrl &= ~FS_XOFF;
    }
    }
    RESTORE_INTS;
}

/* We usually send XOFF characters from interrupt context.  We don't
 * want to incur additional unknown latencies by leaving interrupt
 * context and trying to perform this action in a thread or something.
 * Therefore, we just busy-wait through the send process
 *
 * TODO: Eventually, the force register will be fixed.  When that
 * happens, we'll be able to get rid of this kludgy approach.  Another
 * way to do this would be to use a timer to make the waiting more
 * efficient */
static void
SendXoff(merc_port_t *port)
{
  INTERRUPT_SAVE_AREA;
  //Verify we're not in the middle of sending something
  assert( !(port->serl_regs->force_reg & (TX_DIS | TX_IDLE)) );

  /* Wait for FORCE_EN to be zero, so any previous characters are sent */
  while (port->serl_regs->force_reg & FORCE_EN)
    ;

  DISABLE_INTS; //guarantee we wont' get any ints.
  /* Tell the hardware we want to force a char */
  port->serl_regs->force_reg = TX_DIS;

  /* Wait for the hw to go idle */
  while ( !(port->serl_regs->force_reg & TX_IDLE) )
    ;

  port->serl_regs->force_reg = port->current_termios->c_cc[VSTOP] | FORCE_EN;
  RESTORE_INTS;
}

/* XON characters are never sent from the interrupt, we still want to
 * send them in a "timely" fashion.  This will most likely devolve to
 * a busy wait due to priorities but we at least allow for it to
 * possibly work */
static void
SendXon(merc_port_t *port)
{
  netos_serlioctl(port->channel, TCSENDNOW, 
          (char*)port->current_termios->c_cc[VSTART]);
}

// Given a particular flow control state as expressed in the status varables
// this function will take the actions necessary to enable/disable the 
// remote transmitter
//
// Called by:
//  sp_int_handler rx handling
//  netos_serlread
//  pattern alarm processing
//  tcflow processing
static void
change_rx_flow(merc_port_t *port)
{
    unsigned int MCR;
    INTERRUPT_SAVE_AREA;

    if (port->current_termios->c_cflag & CMDMOUT) {
	// Change DTR/RTS accordingly
	DISABLE_INTS; //Protect MCR from other tasks
        MCR = port->serl_regs->ctrl_a;
	if (port->status->estat & EV_IPALL) {
	    if (port->current_termios->c_cflag & CDTR) {
		MCR &= ~DTR;
	    }
	    if (port->current_termios->c_cflag & CRTS) {
		MCR &= ~RTS;
	    }
	} else {
	    if (port->current_termios->c_cflag & CDTR) {
		MCR |= DTR;
	    }
	    if (port->current_termios->c_cflag & CRTS) {
		MCR |= RTS;
	    }
	}
	port->serl_regs->ctrl_a = MCR;
	RESTORE_INTS;
    }

#if 1 
    if (port->current_termios->c_iflag & IXOFF) {
	if (port->status->estat & EV_IPALL) {
	    SendXoff(port);
	} else {
	    SendXon(port);
	}
    }
#endif
}

/* ---------------------------------------------------------------------- 
 *      Initialization and Configuration
 * ---------------------------------------------------------------------- */

/* 
 * config_match_flow_reg - This function determines the appropriate
 * match character configuration and modifies the DMA read completion
 * variables to provide proper handling of completed buffers.
 *
 * There is a capability to match four characters.  The priority of matched
 * characters is as follows.
 *
 * XOFF     - Simplify removal & For low skid times
 * FF       - Simplify expansion in PARMRK mode
 * XXOFF    - Simplify removal & For low skid times
 * XON      - Simplify removal & reasonably fast restart
 * XXON     - Simplify removal & reasonably fast restart
 * LNEXT    - faster/easier recognition
 *
 * Unfortunately the hardware does not support LNEXT character
 * recognition or IXANY.  This requires us to actually do software flow
 * control in software (oh my!)  occasionally. :-) 
 */
#define NMATCHREGS 4

static void
config_match_flow_reg(merc_port_t * port,
		      struct termios *tios, 
		      uint32 *ctrl_b,
		      uint32 *match_reg_p, 
		      uint32 *flow_ctrl_p)
{
    unsigned int    mccount = 0;
    unsigned int    int_match_mask = 0;
    unsigned int    thread_match_mask = 0;
    scan_t      int_scan_type = SCAN_NONE;
    scan_t      thread_scan_type = SCAN_NONE;

    static unsigned int mask_vals[] = {DMA_MATCH1, DMA_MATCH2, 
				       DMA_MATCH3, DMA_MATCH4};
    /* TODO: Turn this on eventually to let the hardware do the software
     * flow control.  For now, since we need to do it occasionally in software
     * let's just do all of it there so we can ensure that mechanism works.
     */
    int         hw_does_sw = 0;

    if ( tios->c_cc[VLNEXT] || (tios->c_iflag & IXANY) ) {
	hw_does_sw = 0;
    }

    // XOFF
    if (tios->c_iflag & IXON) {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, tios->c_cc[VSTOP] );
	    int_match_mask |= mask_vals[mccount];
	    if ( hw_does_sw ) 
	    {
		*flow_ctrl_p |= FLOW(mccount + 1, FLOW_XOFF);
	    }
        
	    // do not set to LASTONLY if already set to ONMATCH
	    if (int_scan_type == SCAN_NONE) {
		int_scan_type = SCAN_LASTONLY;
	    }

	    mccount++;
	} else {
	    int_scan_type = SCAN_ALWAYS;
	}
    }

    // FF
    if ( ((tios->c_xflag & XEDATA) == 0) 
	 && ((tios->c_iflag & (IGNPAR|PARMRK|ISTRIP)) == PARMRK) ) 
    {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, 0xff);
	    thread_match_mask |= mask_vals[mccount];
        
	    if (thread_scan_type == SCAN_NONE) {
		thread_scan_type = SCAN_LASTONLY;

		mccount++;
	    }
	} else {
	    thread_scan_type = SCAN_ALWAYS ;
	}
    }

    // XXOFF
    if (tios->c_xflag & XIXON) {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, tios->c_cc[VXSTOP] );
	    int_match_mask |= mask_vals[mccount];
	    if ( hw_does_sw ) 
	    {
		*flow_ctrl_p |= FLOW(mccount + 1, FLOW_XOFF);
	    }
        
	    // do not set to LASTONLY if already set to ONMATCH
	    if (int_scan_type == SCAN_NONE) {
		int_scan_type = SCAN_LASTONLY;
	    }

	    mccount++;
	} else {
	    int_scan_type = SCAN_ALWAYS;
	}
    }

    // XON
    if (tios->c_iflag & IXON) {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, tios->c_cc[VSTART] );
	    int_match_mask |= mask_vals[mccount];
	    if ( hw_does_sw ) 
	    {
		*flow_ctrl_p |= FLOW(mccount + 1, FLOW_XON);
	    }
        
	    // do not set to LASTONLY if already set to ONMATCH
	    if (int_scan_type == SCAN_NONE) {
		int_scan_type = SCAN_LASTONLY;
	    }

	    mccount++;
	} else {
	    int_scan_type = SCAN_ALWAYS;
	}
    }


    // XXON
    if (tios->c_xflag & XIXON) {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, tios->c_cc[VXSTART] );
	    int_match_mask |= mask_vals[mccount];
	    if ( hw_does_sw ) 
	    {
		*flow_ctrl_p |= FLOW(mccount + 1, FLOW_XON);
	    }
        
	    // do not set to LASTONLY if already set to ONMATCH
	    if (int_scan_type == SCAN_NONE) {
		int_scan_type = SCAN_LASTONLY;
	    }

	    mccount++;
	} else {
	    int_scan_type = SCAN_ALWAYS;
	}
    }

    // LNEXT
    if (tios->c_cc[VLNEXT]) {
	if (mccount < NMATCHREGS) {
	    *match_reg_p |= RDMB(mccount + 1, tios->c_cc[VLNEXT] );
	    int_match_mask |= mask_vals[mccount];
	    int_scan_type = SCAN_ONMATCH;
	    mccount++;
	} else {
	    int_scan_type = SCAN_ALWAYS;
	}
    }

    // ISTRIP - not a character but does change scanning
    if ( tios->c_iflag & ISTRIP ) {
        thread_scan_type = SCAN_ALWAYS;
    }

    /* The thread always have to do at least as much work as the interrupt */
    if (thread_scan_type < int_scan_type) {
	thread_scan_type = int_scan_type;
    }
    thread_match_mask |= int_match_mask;

    *ctrl_b |= thread_match_mask << 16;


    port->int_scan_type = int_scan_type ;
    port->thread_scan_type = thread_scan_type ;
    port->int_match_mask = int_match_mask ;
    port->thread_match_mask = thread_match_mask ;
}

static void
config_buf_gap(uint32 *buffer_gap_timer_p
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
                ,uint32 divisor
#endif
               ) 
{
    unsigned int Clk = NAgetSysOscFreq();
    unsigned int Btime = BUFFER_GAP_TIME * Clk / (512 * 1000) - 1; /* 512 from manual; 1000 ms/sec */
    assert((Btime & ~BT_MASK) == 0);

#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    Btime += 1;  // Take the register correction out for now

    // Calculate buffer timer granularity that will provide multiples
    // of the output baud clock
    {
        int powers = 8;
        uint32 divided_div = divisor;
        // pull out a power of two, up to eight times
        for (powers = 8 ; (powers > 0) && !(divided_div & 1) ; powers--) {
            divided_div >>= 1;
        }
        // after pulling out enough powers of two, divided_div is the
        // allowed granularity, find closest legal Btime, we're not
        // too picky so pick the closest shorter value.
        Btime = Btime / divided_div * divided_div;

        // If that's zero, we need something.
        if (Btime == 0) Btime = divided_div;

        //TODO: perhaps bail w/ an error if we cannot come within a
        //certain percent error of the desired target??
    }

    Btime -= 1; // Put register correction back in
#endif

    *buffer_gap_timer_p = TRUN | Btime;
}

/* 
 * config_char_gap
 *
 * Calculate and provide values for the character gap timer using the
 * baud and other information provided by the termios structure.  The
 * current algorithm chooses the timeout as the minimum of one
 * millisecond or four character times.
 *
 * tios - Termios structure containing configuration parameters being set
 * char_gap_timer_p - Pointer to variable to update with the settings that
 * should be set in the register.  
 * frame_size - Number of bits in a character 
 */
static void 
config_char_gap(struct termios *tios,
        uint32 *char_gap_timer_p,
        int frame_size
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
                ,uint32 divisor
#endif
                )
{
    unsigned int Clk = NAgetSysOscFreq();
    unsigned int CTtime = 0;
#define MINTIME 1 /* ms */
    unsigned int CTchars = 0;
#define NCHARS 4 /* If you change this, update/uncomment the calc below */

    /* Value for 1 ms */
    CTtime = MINTIME * Clk / (4 * 1000) - 1; /* 4 from manual; 1000 ms/sec */

#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    CTtime += 1; // Take out register correction
    {
        uint32 cfact = 0;
        if (divisor % 2) {
            // divisor is odd
            cfact = 2 * divisor;
        } else {
            cfact = divisor / 2;
        }
        // Find the closest multiple of cfact.
        if ( (CTtime % cfact) < (cfact / 2) ) {
            CTtime = CTtime / cfact * cfact;
        } else {
            CTtime = (CTtime / cfact + 1) * cfact;
        }

        // Need to have some value
        if (CTtime == 0) CTtime = cfact;

        //TODO: perhaps bail w/ an error if we cannot come within a
        //certain percent error of the desired target??
    }
    CTtime -= 1; // Put register correction back in
#endif

    /* CTchars = NCHARS * frame_size * Clk / (4 * tios->baud) - 1; */
    CTchars = frame_size * Clk / tios->baud - 1;

#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    CTchars += 1; // Take out register correction
    {
        uint32 cfact = 0;
        if (divisor % 2) {
            // divisor is odd
            cfact = 2 * divisor;
        } else {
            cfact = divisor / 2;
        }
        // Find the closest multiple of cfact.
        if ( (CTchars % cfact) < (cfact / 2) ) {
            CTchars = CTchars / cfact * cfact;
        } else {
            CTchars = (CTchars / cfact + 1) * cfact;
        }

        // Need to have some value
        if (CTchars == 0) CTchars = cfact;

        //TODO: perhaps bail w/ an error if we cannot come within a
        //certain percent error of the desired target??
    }
    CTchars -= 1; // Put register correction back in
#endif

    assert((CTtime & ~CT_MASK) == 0);
    assert((CTchars & ~CT_MASK) == 0);

    *char_gap_timer_p = TRUN | ((CTtime < CTchars) ? CTtime : CTchars);
}

struct baudTable {
    unsigned int value;
    unsigned int reg_or;
};
enum baudClkIdx {
    SYSOSC_IDX,
    BBUS_IDX,
};

static struct baudTable Clocks[] = {
    { 1 /* SysOsc placeholder */, XTAL1_CLK_SRC },
#ifndef MERC_TIMER_ERRATA_WORKAROUND_ENABLED 
    // 9750 products can't use BCLK due to gap timer/start bit UART errata.
    { 1 /* Bbus placeholder */,  BCLK_CLK_SRC  }, 
#endif
    {0,0},
};

static struct baudTable Clockmodes[] = {
    { 16, TDCR_16X | RDCR_16X },
#ifndef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    // work around only works for 16x because TMODE has to be off.
    { 8,  TDCR_8X  | RDCR_8X  },
    { 32, TDCR_32X | RDCR_32X },
#endif
    {0,0},
};


static int
config_bit_rate_reg(struct termios *tios,
            uint32 *bit_rate_reg_p)
{
    unsigned int Clk, bestClkIdx = 0;
    unsigned int clockMode, bestModeIdx = 0;
    unsigned int err, bestErr = 0xffffffff;
    unsigned int N, bestN = NMASK + 1;
    int clkindex = 0;
    int modeindex = 0;
    unsigned int BP;

    MCReadPLLBypassStatus((unsigned int *)&BP);

    /* Iterate through clocks */
    while (Clocks[clkindex].value) {
	if (clkindex == SYSOSC_IDX && BP) {
	    clkindex++;
	    continue;  /* Can't use SysOsc with bypass */
	}

	switch(clkindex) {
	case SYSOSC_IDX:
	    Clk = NAgetSysOscFreq();
	    break;
	case BBUS_IDX:
	    Clk = NAgetBbusClkFreq();
	    break;
	default:
	    assert(0);
	    return 100; /* 100% error */
	};
    
	modeindex = 0;
	/* Iterate through divisors */
	while ( (clockMode = Clockmodes[modeindex].value) ) {

	    /* Compute N */
	    N = Clk / (clockMode * tios->baud) - 1;

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

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

#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
            if (N == 0) {
                // Won't work, doesn't give us enough time to avoid
                // the error condition.
                err = 0xffffffff;
            }
#endif

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

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

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

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

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

    *bit_rate_reg_p |= Clocks[bestClkIdx].reg_or 
	| Clockmodes[bestModeIdx].reg_or
	| bestN;

    return bestErr * 100 / tios->baud;
}

static int
rtsTCharTime(merc_port_t * port)
{
    int word_size = 5;
    int frame_size;

    switch(port->current_termios->c_cflag & CSIZE) {
    case CS8:
	word_size++;
    case CS7:
	word_size++;
    case CS6:
	word_size++;
    case CS5:
	break;
    }

    frame_size = 
     word_size + 2 + ((port->current_termios->c_cflag & CSTOPB) ? 1 : 0);

    if (port->current_termios->c_cflag & PARENB)
	++frame_size;

    return ((frame_size * 1000) / port->current_termios->baud) + 1;
}

static int
reconfig_hw(merc_port_t * port, struct termios *tios)
{
    INTERRUPT_SAVE_AREA;
    uint32 ctrl_a = CE | ERXDMA | ETXDMA 
	| (port->serl_regs->ctrl_a & (DTR | RTS)) ;
    uint32 ctrl_b = UART_MODE | TENC(NRZ) | RENC(NRZ) | RCGT | RBGT;
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    // TMODE must be off for timing of fix to work
    // RXEXT ouputs baud clock on RI which clocks the flip flops
    // RICS_BRG is also required otherwise the DPLL is used and the
    // fix won't work.
    uint32 bit_rate_reg = EBIT | TICS_BRG | RICS_BRG | RXEXT;
#else
    uint32 bit_rate_reg = EBIT | TMODE | TICS_BRG | RICS_DPLL;
#endif

    uint32 char_gap_timer = 0;
    uint32 buffer_gap_timer = 0;
    uint32 match_reg = 0;
    uint32 flow_ctrl = port->serl_regs->flow_ctrl & FLOW_STATE;
    int word_size = 5;
    int frame_size;
    int pct_err;

    switch(tios->c_cflag & CSIZE) {
    case CS8:
	word_size++;
    case CS7:
	word_size++;
    case CS6:
	word_size++;
    case CS5:
	ctrl_a |= WORD_LEN(word_size);
    };

    if (tios->c_cflag & CSTOPB) {
	ctrl_a |= TWO_STOP_BITS;
    }

    frame_size = word_size + 2 + ((tios->c_cflag & CSTOPB) ? 1 : 0);

    if (tios->c_cflag & PARENB) {
        frame_size += 1;
	if (tios->c_xflag & XPAR) { /* Mark/space parity? */
	    if (tios->c_cflag & PARODD) { 
		ctrl_a |= MARK_PARITY;
	    } else /* !PARODD */ { 
		ctrl_a |= SPACE_PARITY;
	    }
	} else /* !XPAR */ {
	    if (tios->c_cflag & PARODD) { 
		ctrl_a |= ODD_PARITY;
	    } else /* !PARODD */ { 
		ctrl_a |= EVEN_PARITY;
	    }
	}
    }

    /* Hardware flow control */
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    if (tios->c_cflag & CCTS) {
	    ctrl_a |= CTS_CHANGE;
    }
#else
    if (tios->c_cflag & CCTS) {
	    ctrl_a |= CTSTX;
    }
#endif

    /* Config_match_reg does software flow control configuration */
    config_match_flow_reg(port, tios, &ctrl_b, &match_reg, &flow_ctrl);

    pct_err = config_bit_rate_reg(tios, &bit_rate_reg);

    if (pct_err > 5) {
	return -1;
    }
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    config_char_gap(tios, &char_gap_timer, frame_size, 
                    (bit_rate_reg & NMASK) + 1);
    config_buf_gap(&buffer_gap_timer, 
                   (bit_rate_reg & NMASK) + 1);
#else
    config_char_gap(tios, &char_gap_timer, frame_size);
    config_buf_gap(&buffer_gap_timer);
#endif

    /* RTS Toggle correctly supported in hardware */ 
    if ( tios->c_cflag & CRTSTOGGLE ) {
	/* 
	 * If no delays on either side use RTSTX, otherwise must 
	 * use timers 
	 */
	if ( (tios->c_cc[VPREDELAY] == 0) && 
	 (tios->c_cc[VPOSTDELAY] == 0) ) {
	    ctrl_a |= RTS;
	    ctrl_b |= RTSTX;
	}
    }

    /* Update signals to new flow control */
    if (!(port->status->estat & EV_IPALL)) {
	if (tios->c_cflag & CDTR) {
	    ctrl_a |= DTR; /* Turn ON DTR */
	}
	if (tios->c_cflag & CRTS) {
	    ctrl_a |= RTS; /* Turn ON RTS */
	}
    }
    
    /* Set ctrl_b first so that any possible set of RTSTX doesn't have
     * a transient on RTS by the set of RTS in ctrl_a */

    DISABLE_INTS;
    port->serl_regs->ctrl_b = ctrl_b;
    port->serl_regs->ctrl_a = ctrl_a;
    port->serl_regs->bit_rate_reg = bit_rate_reg;
    port->serl_regs->buffer_gap_timer = buffer_gap_timer;
    port->serl_regs->char_gap_timer = char_gap_timer;
    port->serl_regs->match_reg = match_reg;
    port->serl_regs->flow_ctrl = flow_ctrl;
    RESTORE_INTS;

    return 0;
}

/*
 *  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)
{
#if !NONCACHED_ALLOC
    static int firsttime = 1;
#endif
    merc_port_t * port = &ports[channel];
    merc_serl_t * sregs;
    merc_bmd_t * bmd;
    int bufidx = 0, i = 0;
    int dmachan;

    switch (channel) {
    case 0:
	dmachan = 1;
	break;
    case 1:
	dmachan = 0;
	break;
    default:
	dmachan = channel;
    };

    /*  Lock serial channel resource, so HDLC can't open a channel */
    /* If async serial has a channel, it has it forever, this never releases */
    if (!GetResource(&serl_resource_lock[channel], port))
    {
        assert(0);
        return EACCES;    /* HDLC channel open */
    }

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    if (port->configured) {
	/* enter called twice for some reason */
	assert(0);
	return OK;
    }

    port->channel = channel;
    port->configured = 1;

#if !NONCACHED_ALLOC
    if (firsttime) {
        /* Allocate important data areas */
        int i = sizeof(merc_bmd_t) * NUM_MERC_SERL_PORTS 
	    * NUM_BMDS_PER_CHANNEL * 2;

	bmds = nonCachedMalloc(i);
	memset(bmds, 0, i);
    
	i = NUM_MERC_SERL_PORTS * RX_BUF_SIZE;
	rx_bufs = nonCachedMalloc(i);
	memset(rx_bufs, 0, i);
    
	i = NUM_MERC_SERL_PORTS * TX_BUF_SIZE;
	tx_data_buf = nonCachedMalloc(i);
	memset(tx_data_buf, 0, i);
    
	firsttime = 0;
    }
#endif

    rx_phys = MyVaToPhys(rx_bufs);
    tx_phys = MyVaToPhys(tx_data_buf);

    tx_buffer[channel].buf = (char*)&tx_data_buf[channel * TX_BUF_SIZE];

    /* Fill in the merc_port_t struct.  Threadx members wait until serlinit */
    port->serl_regs     = serl_regs[channel];
    port->current_termios   = &current_termios[channel];
    port->counters      = &serial_counters[channel];
    port->status        = &serial_status[channel];
    port->sigchange         = &serial_sigchange[channel];

    port->write_fn      =  0;

    /* Start DMA setup by setting wrap flags */
    bmds[(2 * channel  + 1) * NUM_BMDS_PER_CHANNEL - 1].flags   = DMA_WRAP; 
    bmds[(channel + 1) * 2 * NUM_BMDS_PER_CHANNEL - 1].flags    = DMA_WRAP;

    /* Rest of the merc_port_t members */

    port->rx_dma_ctrl       = &dma_ctrl_regs[dmachan * 2].un.reg;
    port->rx_dma_status     = &dma_status_regs[dmachan * 2].un.reg;
    port->rx_dma_bmd        = &dma_bmd_regs[dmachan * 2].un.reg;

    port->rx_dma_stalled    = 0;
    port->rx_dma_oflowon    = 0;

    port->rx_bmd_start      = 
	NAVaToUncachedVa(&bmds[channel * 2* NUM_BMDS_PER_CHANNEL]);
    port->rx_bmd_end        = 
	NAVaToUncachedVa(&bmds[(2*channel + 1) *NUM_BMDS_PER_CHANNEL]);
    port->rx_bmd_in     = port->rx_bmd_start;
    port->rx_bmd_out        = port->rx_bmd_start;
    port->rx_bmd_scan       = port->rx_bmd_start;

    port->rx_bmd_offset     = 0;
    port->rx_bmd_lsr        = 0;

    port->dfrag_start       = &dfrags[channel][0];
    port->dfrag_end     = &dfrags[channel][N_DFRAG];
    port->dfrag_in       
	= port->dfrag_out   = port->dfrag_start;

    for (i = 0 ; i < N_DFRAG ; i++) {
	port->dfrag_start[i].next = &port->dfrag_start[i+1];
    }
    port->dfrag_start[i - 1].next = &port->dfrag_start[0];

    port->avail_dfrag = N_DFRAG - 1;

    port->rxmsg_start       = &rxmsgs[channel][0];
    port->rxmsg_end     = &rxmsgs[channel][N_RXMSG];
    port->rxmsg_in       
	= port->rxmsg_out   = port->rxmsg_start;

    for (i = 0 ; i < N_RXMSG ; i++) {
	port->rxmsg_start[i].next = &port->rxmsg_start[i+1];
    }
    port->rxmsg_start[i - 1].next = &port->rxmsg_start[0];

    port->avail_rxmsg = N_RXMSG - 1;

    port->rxd_count     = 0;
    port->rxflags       = 0;
    port->rxlnext       = 0;
    port->int_rxlnext       = 0;
    port->rqbytes       = 0;
    port->rdbytes       = 0;

    port->tx_dma_ctrl       = &dma_ctrl_regs[dmachan * 2 + 1].un.reg;
    port->tx_dma_status     = &dma_status_regs[dmachan * 2 + 1].un.reg;
    port->tx_dma_bmd        = &dma_bmd_regs[dmachan*2+1].un.reg;

    port->tx_bmd_start      = 
	NAVaToUncachedVa(&bmds[(2*channel + 1) *NUM_BMDS_PER_CHANNEL]);
    port->tx_bmd_end        = 
	NAVaToUncachedVa(&bmds[(channel + 1)* 2*NUM_BMDS_PER_CHANNEL]);
    port->tx_bmd_in     = port->tx_bmd_start;
    port->tx_bmd_out        = port->tx_bmd_start;

    port->tx_buffer     = &tx_buffer[channel];
    port->tx_buffer_scan    = 0;

    port->monitor_chains    = 0; /* netos_serlwrite starts out chaining */

    port->int_flags     = 0;

#ifdef SW_MEI_ENABLED
    port->dipswitches   = DIPSWITCH_232;
#endif

    /* Configure initial serial registers */
    sregs = port->serl_regs;

    /* config endian config reg for big-endian*/
    // *(unsigned long *) MC_BUM_ECR_ADDRESS |= 1 << (channel+2);  
    
#if NDS_SPECIFIC
    switch (channel)    /* remove the master reset bit */
    {
    case 0: narm_write_reg(MC_BUM_MRR_REG, MC_BUM_MRR_ADDRESS, ser2, 0); 
	break;
    case 1: narm_write_reg(MC_BUM_MRR_REG, MC_BUM_MRR_ADDRESS, ser1, 0); 
	break;
    case 2: narm_write_reg(MC_BUM_MRR_REG, MC_BUM_MRR_ADDRESS, ser3, 0);
	break;
    case 3: narm_write_reg(MC_BUM_MRR_REG, MC_BUM_MRR_ADDRESS, ser4, 0); 
	break;
    default:
	break;
    }

    /* Take DMA out of reset as well */
    narm_write_reg(MC_BUM_MRR_REG, MC_BUM_MRR_ADDRESS, dma, 0); 
#else
    switch (channel)    /* remove the master reset bit */
    {
    case 0: narm_write_reg(NA_BUM_MRR_REG, NA_BUM_MRR_ADDRESS, ser2, 0); 
            narm_write_reg(NA_BUM_ECR_REG, NA_BUM_ECR_ADDRESS, ser2, 0); 
	break;
    case 1: narm_write_reg(NA_BUM_MRR_REG, NA_BUM_MRR_ADDRESS, ser1, 0); 
            narm_write_reg(NA_BUM_ECR_REG, NA_BUM_ECR_ADDRESS, ser1, 0); 
	break;
    case 2: narm_write_reg(NA_BUM_MRR_REG, NA_BUM_MRR_ADDRESS, ser3, 0);
            narm_write_reg(NA_BUM_ECR_REG, NA_BUM_ECR_ADDRESS, ser3, 0); 
	break;
    case 3: narm_write_reg(NA_BUM_MRR_REG, NA_BUM_MRR_ADDRESS, ser4, 0); 
            narm_write_reg(NA_BUM_ECR_REG, NA_BUM_ECR_ADDRESS, ser4, 0); 
	break;
    default:
	break;
    }

    /* Take DMA out of reset as well */
    narm_write_reg(NA_BUM_MRR_REG, NA_BUM_MRR_ADDRESS, dma, 0); 
#endif

    /* Basic 9600 8N1 configuration, DMA, sw flow */
    sregs->ctrl_a = CE | WORD_LEN(8) | ERXDMA | ETXDMA ;

    sregs->ctrl_b = UART_MODE | TENC(NRZ) | RENC(NRZ);

    /* A basic config.  With default mercury dev board and Digi products
     * should be 9600 baud. Overwritten by real values once known */
    sregs->bit_rate_reg     = EBIT | TMODE | XTAL1_CLK_SRC | TDCR_16X |
	RDCR_16X | TICS_BRG | RICS_DPLL | 47;

#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    sregs->bit_rate_reg     = EBIT | TMODE | XTAL1_CLK_SRC | TDCR_16X |
	RDCR_16X | TICS_BRG | RICS_BRG | 47;
#else
    sregs->bit_rate_reg     = EBIT | TMODE | XTAL1_CLK_SRC | TDCR_16X |
	RDCR_16X | TICS_BRG | RICS_DPLL | 47;
#endif

    sregs->buffer_gap_timer = 0;
    sregs->char_gap_timer   = 0; 
    sregs->match_reg        = RDMB(1, XON) | RDMB(2, XOFF);
    sregs->flow_ctrl        = SW_XON |
	FLOW(1, FLOW_XON) |
	FLOW(2, FLOW_XOFF);

    /* Configure and fill out initial BMD lists */
    bufidx = 0;
    for (bmd = port->rx_bmd_start ; bmd != port->rx_bmd_end ; bmd++ ) {
	bmd->src_addr = MyVaToPhys(&rx_bufs[channel * RX_BUF_SIZE + bufidx]);
	bmd->length = RX_BMD_SIZE - CH_INSERT_SIZE; 
	bmd->dst_addr = 0; /* Unused */

	bufidx += RX_BMD_SIZE;

	NABeforeDMA(NA_DEVICE_TO_MEMORY_DMA,
		    NULL, 
		    RXSRC_ADDR(bmd), 
		    bmd->length);
    }

    /* Don't need a loop for tx because the settings will be calculated as
     * we fill the transmit buffers */

    /* Configure initial DMA registers */
    *port->rx_dma_ctrl = FLY_BY_WR | BTE_FOUR_OPS; // Set CE later

    *port->rx_dma_status = NCIE | ECIE | NRIE | CAIE | PCIE;
    *port->rx_dma_bmd = (uint32)MyVaToPhys(port->rx_bmd_start);

    *port->tx_dma_ctrl = FLY_BY_RD | BTE_FOUR_OPS ; // Set CE later
    *port->tx_dma_status = NCIE | ECIE | NRIE | CAIE | PCIE;
    *port->tx_dma_bmd = (uint32)MyVaToPhys(port->tx_bmd_start);
    
    return OK;
}

#define MONITOR_STACK_SIZE 0x800
static TX_THREAD serl_monitor_thread;
static char serl_monitor_stack[MONITOR_STACK_SIZE];

/*
 *  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)
{
    INTERRUPT_SAVE_AREA;
    static int thread_started = 0;
    merc_port_t * port = &ports[channel];
    struct termios tios;

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    port->serial_channel = serial_channel(MAJOR_NETOS_SERL, channel);

    /* Remaining threadx member configuration left over from serlenter */
    tx_mutex_create(&port->tx_mutex, "Mercury Serial Transmit Mutex", 
		    TX_INHERIT);
    tx_mutex_create(&port->rx_mutex, "Mercury Serial Receive Mutex", 
		    TX_INHERIT);
    tx_mutex_create(&port->assemble_mutex, "Mercury Serial Assemble Mutex", 
		    TX_INHERIT);

    tx_event_flags_create(&port->blocking_flags, "Flags for Serial blocking");

    tx_timer_create(&port->rtsup_timer, "RTS Up timer", rtsup_fn, 
		    (ULONG)port, 
		    1,
		    0,
		    TX_NO_ACTIVATE);
    tx_timer_create(&port->rtsdown_timer, "RTS Down timer",rtsdown_fn,
		    (ULONG)port,
		    1,
		    0,
		    TX_NO_ACTIVATE);
    
    /* Initial termios config values */
    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 ;

#ifdef NDS_SPECIFIC
    /* Reconcile with NV termios settings */
    extern void serial_init(int, struct termios *);
    serial_init(port->serial_channel, &tios);
#endif /* NDS_SPECIFIC */

    netos_serlioctl(channel, TCSETA, (char *) &tios);

#if NDS_SPECIFIC
    /* Install interrupt handlers */
    switch (channel)
    {
    case 0:
        NAInstallIsr (BBUS_DMA03_INTERRUPT, rx_dma_int, (void*) port); 
        NAInstallIsr (BBUS_DMA04_INTERRUPT, tx_dma_int, (void*) port); 
        break;
    case 1 :
        NAInstallIsr (BBUS_DMA01_INTERRUPT, rx_dma_int, (void*) port); 
        NAInstallIsr (BBUS_DMA02_INTERRUPT, tx_dma_int, (void*) port); 
        break;
#if 1 /* TODO: Don't seem to be working */
    case 2 :
        NAInstallIsr (BBUS_DMA05_INTERRUPT, rx_dma_int, (void*) port); 
        NAInstallIsr (BBUS_DMA06_INTERRUPT, tx_dma_int, (void*) port); 
        break;
    case 3 :
        NAInstallIsr (BBUS_DMA07_INTERRUPT, rx_dma_int, (void*) port); 
        NAInstallIsr (BBUS_DMA08_INTERRUPT, tx_dma_int, (void*) port); 
        break;
#endif
    default:
        break;
    }
#else 
    /* Install interrupt handlers */
    switch (channel)
    {
    case 0:
        MCInstallIsr (BBUS_DMA03_INTERRUPT, rx_dma_int, (void*) port, 0); 
        MCInstallIsr (BBUS_DMA04_INTERRUPT, tx_dma_int, (void*) port, 0); 
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED 
        /* Enable fn 0, output on RxClk pin gpio 14 */
        NAconfigureGPIOpin(14, NA_GPIO_FUNC0_STATE, 0);
#endif
        break;
    case 1 :
        MCInstallIsr (BBUS_DMA01_INTERRUPT, rx_dma_int, (void*) port, 0); 
        MCInstallIsr (BBUS_DMA02_INTERRUPT, tx_dma_int, (void*) port, 0); 
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED 
        /* Enable fn 0, output on RxClk pin gpio 6 */
        NAconfigureGPIOpin(6, NA_GPIO_FUNC0_STATE, 0);        
#endif
        break;
#if 1 /* TODO: Don't seem to be working */
    case 2 :
        MCInstallIsr (BBUS_DMA05_INTERRUPT, rx_dma_int, (void*) port, 0); 
        MCInstallIsr (BBUS_DMA06_INTERRUPT, tx_dma_int, (void*) port, 0); 
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED 
        /* Enable fn 0, output on RxClk pin gpio 22 */
        NAconfigureGPIOpin(22, NA_GPIO_FUNC0_STATE, 0);
#endif
        break;
    case 3 :
        MCInstallIsr (BBUS_DMA07_INTERRUPT, rx_dma_int, (void*) port, 0 ); 
        MCInstallIsr (BBUS_DMA08_INTERRUPT, tx_dma_int, (void*) port, 0 ); 
#ifdef MERC_TIMER_ERRATA_WORKAROUND_ENABLED 
        /* Enable fn 0, output on RxClk pin gpio 26 */
        NAconfigureGPIOpin(26, NA_GPIO_FUNC0_STATE, 0);
#endif
        break;
#endif
    default:
        break;
    }
    
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
    naIsrInstall (serl_tx_intr[channel], tx_serl_int, (void *) port);
#endif

#endif

    /* Start monitoring thread */
    if (!thread_started) {

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
        tx_event_flags_create(&cntrl_flags, "Flags for Serial Control");
#endif
       
	tx_thread_create(
			 &serl_monitor_thread,
			 "Mercury Serial Monitor",
			 (VOID (*)(ULONG)) serl_monitor,
			 0,     //dummy
			 &serl_monitor_stack,
			 MONITOR_STACK_SIZE,    // stack size
			 14,    // arbitrary priority
			 14,    // arbitrary threshold
			 10,    // timeslice
			 TX_AUTO_START );
        
	thread_started = 1;
    }

#if ENABLE_TESTPAT_CHECKER
    port->rx_testpat_handle = TestpatCheckerInit();
    port->tx_testpat_handle = TestpatCheckerInit();
#endif    

#ifdef NDS_SPECIFIC
#if PLATFORM == amat4
    /* Enable the channels only after they are configured. */
    switch (channel)
    {
	case 0:
            GpioSetOutputWithValue(GPIO_RS232_ENABLE_0, 1);
	    break;
	case 1:
            GpioSetOutputWithValue(GPIO_RS232_ENABLE_1, 1);
	    break;
	case 2:
            GpioSetOutputWithValue(GPIO_RS232_ENABLE_2, 1);
	    break;
	default:
	    break;
    }
#endif
#endif
    /* Enable DMA channels and let's get going */
    DISABLE_INTS;
    *port->rx_dma_ctrl |= DMA_CE;
    RESTORE_INTS;
    /* Don't start until we have data */
    //*port->tx_dma_ctrl |= DMA_CE;

    //We get a spurious framing error at the start.  Clear it out.
    tx_thread_sleep(1);
    netos_serlioctl(port->channel, TCFLUSH, (char *)TCIOFLUSH);
    memset(port->counters, 0, sizeof(struct serial_cntr_t));

    return OK;
}

static dfrag *
dfalloc(merc_port_t *port)
{
    dfrag *df ;

    df = port->dfrag_in ;

    if(df->next == port->dfrag_out) {
	// we're supposed to know ahead of time that
	// we have enough to complete the operation.
	assert(df->next != port->dfrag_out) ;
	return 0 ;
    }

    port->avail_dfrag-- ;
    assert(port->avail_dfrag < N_DFRAG) ; // unsigned, covers < 0 case

    port->dfrag_in = df->next ;

    df->dmabuf = 0 ;
    // not setting data or len to sane values; assuming caller will

    return df ;
}

static void 
dffree(merc_port_t *port, dfrag *df)
{
    port->avail_dfrag++ ;

    assert(port->avail_dfrag < N_DFRAG) ; // unsigned, covers < 0 case
    assert(port->dfrag_out == df) ;

    port->dfrag_out = df->next ;
    return;
}

static rxmsg *
msgalloc(merc_port_t *port)
{
    rxmsg *msg ;

    msg = port->rxmsg_in ;

    if(msg->next == port->rxmsg_out) {
	// we're supposed to know ahead of time that
	// we have enough to complete the operation
	assert(msg->next != port->rxmsg_out) ;
	return 0 ;
    }

    port->avail_rxmsg-- ;
    assert(port->avail_rxmsg < N_RXMSG) ; // unsigned, covers < 0 case

    port->rxmsg_in = msg->next ;

    msg->flags = 0 ;
    
    return msg ;
}

static void 
msgfree(merc_port_t *port, rxmsg *msg)
{
    port->avail_rxmsg++ ;
    assert(port->avail_rxmsg < N_RXMSG) ;
    assert(port->rxmsg_out == msg) ;
    port->rxmsg_out = msg->next ;
}

static int
can_make_messages(merc_port_t *port)
{
    return ( (port->avail_dfrag > MIN_DFRAG) 
         && (port->avail_rxmsg > MIN_MSG) ) ;
}

static void 
recycle_bmd(merc_port_t *port, merc_bmd_t *bmd)
{
    assert((bmd->flags & DMA_FULL)) ;

    bmd->length = RX_BMD_SIZE - CH_INSERT_SIZE;

    bmd->flags &= ~DMA_FULL;

    NABeforeDMA(NA_DEVICE_TO_MEMORY_DMA, NULL, RXSRC_ADDR(bmd), RX_BMD_SIZE);

    port->rx_bmd_out = bmd + 1;
    if (port->rx_bmd_out == port->rx_bmd_end) {
	port->rx_bmd_out = port->rx_bmd_start;
    }
}

typedef struct dfbuilder_t {
    uint8   *base;
    uint32  count;
    uint32  datasent;
    dfrag   *df;
    merc_port_t *port;
} dfbuilder;

// add characters (at base + current count) to the dfrag
// being built
static void
dfbaddchars(dfbuilder *dfb, int n)
{
    dfb->count += n;
}

// complete this dfrag, add it to the message being built
// and prepare to build another dfrag
static void
dfbcomplete(dfbuilder *dfb)
{
    if(dfb->count != 0) {
	dfb->df = dfalloc(dfb->port) ;
	assert(dfb->df != 0) ;
	dfb->df->data = dfb->base ;
	dfb->df->len = dfb->count ;

	// add these bytes to the current data msg
	dfb->port->rxd_count += dfb->count ;

	// move up and start another dfrag
	dfb->base += dfb->count ;
	dfb->count = 0 ;
    }
}

// complete any dfrag in progress, and skip some charcters
// before starting the next dfrag
static void 
dfbskip(dfbuilder *dfb, int n) 
{
    dfbcomplete(dfb) ;
    dfb->base += n ;
}

// complete any dfrag in progress, and add another "special"
// dfrag containing a small number of characters to be inserted.
static void 
dfbinsert(dfbuilder *dfb, int n, char *cp) 
{
    dfbcomplete(dfb) ;
    assert(n <= 4) ;

    if(n > 0) {
	dfb->df = dfalloc(dfb->port) ;
	assert(dfb->df != 0) ;
	dfb->df->data = &dfb->df->local_data[0] ;
	dfb->df->len = n ;
	memcpy(dfb->df->data, cp, n) ;

	dfb->port->rxd_count += n ;
    }
}

// move dfrag on to next bmd
static void 
dfbsetbase(dfbuilder *dfb, void *b) 
{
    dfbcomplete(dfb) ;  // not sure this is necessary
    dfb->base = (uint8 *)b ;
    dfb->count = 0 ;
}

static void 
dfbsetbmd(dfbuilder *dfb, merc_bmd_t *bmd) 
{
    if(dfb->df == 0) {
	dfb->df = dfalloc(dfb->port) ;
	assert(dfb->df != 0) ;

	dfb->df->data = 0 ;
	dfb->df->len = 0 ;
    }
    dfb->df->dmabuf = bmd ;

    // make sure next call to setbmd allocates a new dfrag,
    // not messing up what we set here
    dfb->df = 0 ;
}

static void 
dfbmake_data_msg(dfbuilder *dfb) 
{
    rxmsg *msg ;

    dfb->datasent = 1 ;

    msg = msgalloc(dfb->port) ;
    if(msg == 0) {
	// shouldn't happen
	assert(msg != 0) ;
	return ;
    }

    msg->flags |= MSG_ISDATA ;
    msg->len = dfb->port->rxd_count ;
    dfb->port->rqbytes += dfb->port->rxd_count ;
    dfb->port->rxd_count = 0 ;
}

#if NDS_SPECIFIC
// Get serial port MEI state
static int
DipSwitchMode_int(int portno)
{
#if defined(SW_MEI_ENABLED)
    // Return whatever is set by SETDIPSWITCHES
    return ports[portno].dipswitches;
#else
    (void) portno;
    return DIPSWITCH_232;
#endif
}

#if defined(SW_MEI_ENABLED)
// Set serial port MEI state
static int
SetDipSwitches_int(int portno, int switches)
{
#if PLATFORM == cpx4

#define CPLD_ADDR       0x60000000      // CPLD register base

#define SER_RS232       0x80    // RS-232 enable
#define SER_RS485       0x40    // RS-485 enable
#define SER_2WIRE       0x02    // RS-485 2-wire mode
#define SER_TERM        0x01    // RS-485 terminated

    struct termios tios;
    uint8 reg;
    
    // Enable or restore original RTS toggle setting.
    // RTS toggle is needed for RS-485 2-wire operation.
    tios = current_termios[portno];
    if ((switches & (DIPSWITCH_232|DIPSWITCH_2W)) == DIPSWITCH_2W)
        tios.c_cflag |= CRTSTOGGLE;
    if (reconfig_hw (&ports[portno], &tios) < 0)
        return EIO;

    // Select MEI state in CPLD serial port control register
    if (switches & DIPSWITCH_232)
        reg = SER_RS232;
    else
    {
        reg = SER_RS485;
        if (switches & DIPSWITCH_2W)
            reg |= SER_2WIRE;
        if (switches & DIPSWITCH_TERM)
            reg |= SER_TERM;
    }
            
    WriteRegByte (CPLD_ADDR+portno, reg);
    
    ports[portno].dipswitches = switches;
    return OK;
    
#else // PLATFORM
    return ENOSYS;
#endif
}
#endif // SW_MEI_ENABLED
#endif // NDS_SPECIFIC

/*
 * 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 *arg)
{
    INTERRUPT_SAVE_AREA;
    merc_port_t * port = &ports[channel];
    int status;           /* status to return */
    int duration, queue_selector, action;
    struct termios *termios_p;
    int *done;
    struct serial_cntr_t *cntrs;
    struct serial_status_t *serl_status;
    struct sigchange_stats_t *sigchange;
    struct serial_buffer_t *bufs;
    unsigned char mout;
    unsigned char prev;
    int ret;
    uint32 opt;

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	assert(0);
	return EINVAL;
    }

    status = ENOSYS;

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

	termios_p = (struct termios*)arg;
	memcpy(termios_p, port->current_termios, sizeof(struct termios));
	break; 
    case TCSETA:
	termios_p = (struct termios*)arg;
	status = tcseta(termios_p, port->current_termios, &tios_support);

	ret = reconfig_hw(port, port->current_termios);
    
	if (ret >= 0 && (status == OK) ) {
	    tx_timer_change(&port->rtsup_timer, 
			    port->current_termios->c_cc[VPREDELAY],
			    0);
	    tx_timer_change(&port->rtsdown_timer, 
	     port->current_termios->c_cc[VPOSTDELAY] + rtsTCharTime(port),
			    0);
        
	    if (port->current_termios->c_oflag) {
		port->write_fn = write_opost_data;
	    } else {
		port->write_fn = write_raw_data;
	    }
	    status = OK;
	} else {
	    status = EIO;
	}

	break;
    case TCSENDBREAK:
	status = OK; 

	duration = (int)arg;

	serl_cb_event(port->serial_channel, TXB_EVENT);
	DISABLE_INTS; //protect estat
	port->status->estat |= EV_TXB;
	RESTORE_INTS;
	port->breaktime = duration;
	port->breakstatus = BRK_REQUESTED;
	break;
    case TCDRAIN:
	status = OK;
	port->tcdrain_req = 1;
	ret = tx_event_flags_get( &port->blocking_flags, DRAIN_BLOCKING, 
				  TX_OR_CLEAR, &port->actual_flags, 
				  TX_WAIT_FOREVER);
	assert(ret == TX_SUCCESS);
	break;
    case TCFLUSH:

	status = OK;
	queue_selector = (enum flush_opts)arg;

	if (queue_selector & TCIFLUSH) { /* Throw away input */
	    rxmsg *msg;
	    dfrag *df;
      
	    assemble_messages(port);

	    msg = port->rxmsg_out ; 
	    while (msg != port->rxmsg_in) {
		msgfree(port, msg);
		msg = port->rxmsg_out;
	    }
      
	    df = port->dfrag_out;
	    while (df != port->dfrag_in) {
		if (df->dmabuf != 0) {
		    recycle_bmd(port, df->dmabuf);
		}
		dffree(port, df);
		df = port->dfrag_out;
	    }
      
	    port->rxd_count = 0;
	    port->rx_bmd_offset = 0;
	    port->rx_bmd_lsr = 0;
      
	    port->rqbytes = port->rdbytes;
      
	    if (port->status->estat & EV_IPS) {
		DISABLE_INTS;
		port->status->estat &= ~EV_IPS;
		RESTORE_INTS;
		change_rx_flow(port);
		serl_cb_event(port->serial_channel, IPS_EVENT);
	    }
	} 

	if (queue_selector & TCOFLUSH) { /* Throw away output */
#if PROCESSOR == ns9360 
            uint32 ctrl;
            merc_bmd_t *currbmd;

            tx_mutex_get(&port->tx_mutex, TX_WAIT_FOREVER);

	        DISABLE_INTS;

            ctrl = *port->tx_dma_ctrl;
            ctrl &= ~DMA_INDEX;

            *port->tx_dma_ctrl = ctrl | (DMA_CE | RST);

            ctrl &= ~DMA_CE;
            *port->tx_dma_ctrl = ctrl;

       	    RESTORE_INTS;

	        for (currbmd = port->tx_bmd_start ;
	             currbmd != port->tx_bmd_end ;
	             currbmd++)
	        {
	            currbmd->src_addr = 0;
	            currbmd->length   = 0;
	            currbmd->dst_addr = 0;
	            currbmd->flags    = 0;
	            if (currbmd == (port->tx_bmd_end - 1)) {
		            currbmd->flags |= DMA_WRAP;
	            }
	        }

	        port->tx_bmd_in = port->tx_bmd_out = port->tx_bmd_start;
	        port->tx_buffer_scan = tx_buffer[port->channel].in 
	            = tx_buffer[port->channel].out;

            port->monitor_chains = 0;
            tx_mutex_put(&port->tx_mutex);
#else
	    DISABLE_INTS;
	    *port->tx_dma_ctrl |= (DMA_CE | CA | BDR); 
	    RESTORE_INTS;
#endif
	}

	assert(!(queue_selector & ~TCIOFLUSH ));
	if (queue_selector & ~TCIOFLUSH) {
	    status = EIO;
	}
	break;
    case TCFLOW: {
	unsigned short estat_pre = port->status->estat;
	status = OK; 

	action = (enum flow_opts)arg;
	switch (action) {
	case TCOOFF:
	    DISABLE_INTS;
	    port->status->estat &= ~EV_OPU;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPU )
                serl_cb_event(port->serial_channel, OPU_EVENT);
	    break;
	case TCOON:
	    DISABLE_INTS;
	    port->status->estat |= EV_OPU;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPU )
                serl_cb_event(port->serial_channel, OPU_EVENT);
	    break;
	case TCIOFF:
	    if (port->status->estat & EV_IPU) {
		DISABLE_INTS;
		port->status->estat &= ~EV_IPU;
		RESTORE_INTS;
		change_rx_flow(port);
	    }

	    if ( (estat_pre ^ port->status->estat) & EV_IPU )
                serl_cb_event(port->serial_channel, IPU_EVENT);
	    break;
	case TCION:
	    DISABLE_INTS;
	    port->status->estat |= EV_IPU;
	    RESTORE_INTS;
	    change_rx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_IPU )
                serl_cb_event(port->serial_channel, IPU_EVENT);
	    break;
        case TCIAOFF: // pattern alarm exit flow
	    if (port->status->estat & EV_IPA) {
		DISABLE_INTS;
		port->status->estat &= ~EV_IPA;
		RESTORE_INTS;
		change_rx_flow(port);
	    }
            break;
	case TCOSOFF:
	    DISABLE_INTS;
	    port->status->estat &= ~EV_OPS;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPS )
                serl_cb_event(port->serial_channel, OPS_EVENT);
	    break;
	case TCOSON:
	    DISABLE_INTS;
	    port->status->estat |= EV_OPS;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPS )
                serl_cb_event(port->serial_channel, OPS_EVENT);
	    break;
	case TCISOFF:
	    if (port->status->estat & EV_IPS) {
		DISABLE_INTS;
		port->status->estat &= ~EV_IPS;
		RESTORE_INTS;
		change_rx_flow(port);
	    }

	    if ( (estat_pre ^ port->status->estat) & EV_IPS )
                serl_cb_event(port->serial_channel, IPS_EVENT);
	    break;
	case TCISON:
	    DISABLE_INTS;
	    port->status->estat |= EV_IPS;
	    RESTORE_INTS;
	    change_rx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_IPS )
                serl_cb_event(port->serial_channel, IPS_EVENT);
	    break;
	case TCOXOFF:
	    DISABLE_INTS;
	    port->status->estat &= ~EV_OPX;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPX )
                serl_cb_event(port->serial_channel, OPX_EVENT);
	    break;
	case TCOXON:
	    DISABLE_INTS;
	    port->status->estat |= EV_OPX;
	    RESTORE_INTS;
	    change_tx_flow(port);

	    if ( (estat_pre ^ port->status->estat) & EV_OPX )
                serl_cb_event(port->serial_channel, OPX_EVENT);
	    break;
	case TCIXOFF:
	case TCIXON:
	    //TODO - all the extra flow states
	default:
	    status = EIO;
	    break;
	}

	break;
    }
    case TCSENDNOW:
	status = OK;
	/* This is SO ugly.  If there's a chance, we need to talk the hw
	 * guys into changing this for future hardware */

	//Verify we're not in the middle of sending something
	assert( !(port->serl_regs->force_reg & (TX_DIS | TX_IDLE)) );
      
	while (port->serl_regs->force_reg & FORCE_EN) {
	    /* If something else is forcing at the same time, we wait
	     * for it to finish */
	    tx_thread_sleep(1);
	}
	port->serl_regs->force_reg = TX_DIS;
	while (!(port->serl_regs->force_reg & TX_IDLE) ) {
	    /* Should this be a tx_thread_sleep(1) or something in order to 
	     * allow lower priority threads to run as well? */
	    tx_thread_sleep(1);
	}
	port->serl_regs->force_reg = FORCE_EN | (uint32)arg;
	break;
    case TCGETCOUNTERS:
	status = OK;

	cntrs = (struct serial_cntr_t*)arg;
	memcpy(cntrs, port->counters, 
	       sizeof(struct serial_cntr_t));
	break;
    case TCGETSTATS:
	status = OK;
    
	serl_status = (struct serial_status_t*)arg;
	serl_status->mstat = 0;

	opt = port->serl_regs->status_a;

	if (port->current_termios->c_xflag & XALTPIN) {
	    /* Swap DCD and DSR */
	    if (opt & DCD)
		serl_status->mstat |= MS_DSR;

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

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

	if (port->current_termios->c_xflag & XFORCEDCD) {
	    serl_status->mstat |= MS_DCD;
	}

#if defined(MERC_TIMER_ERRATA_WORKAROUND_ENABLED) && defined(NDS_SPECIFIC)
#if (PLATFORM == cpd)
    // CPD gets RI a different way due to the MERC_TIMER_ERRATA_WORKAROUND_ENABLED
    if (GetHardwareStrapping() == DIGI_STRAPPING_CPD_P1) {
        // Before Rev A, CPD got RI normally from the UART
    } else {
        // since then, CPD gets RI through GPIO 18. When GPIO 18 is low set RI ON
        if (ReadRegMask(NARM_BBUS_UTIL_GSTS01, NARM_BBUS_GSTS01_18_VAL_MASK)==NARM_BBUS_GSTS01_18_VAL_LOW)
            serl_status->mstat |= MS_RI;
    }
#else // (PLATFORM != cpd)
    // All new products use Gpio table to identify UARTA_RI pin.
    // When Gpio pin is low, set RI ON
    if (GpioGetValue(GPIO_UARTA_RI) == 0)
            serl_status->mstat |= MS_RI;
#endif // (PLATFORM == cpd)
#else /* old way, before workaround. */
    if (opt & RI)
        serl_status->mstat |= MS_RI;
#endif

	if (opt & CTS )
	    serl_status->mstat |= MS_CTS;

	opt = port->serl_regs->ctrl_a;

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

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

	serl_status->estat = port->status->estat;
	break;
    case TCGETSIGCHANGE:
	status = OK;

	sigchange = (struct sigchange_stats_t *)arg;
	memcpy(sigchange, port->sigchange, 
	       sizeof(struct sigchange_stats_t));
	break;
    case TCSETSIGNALS:

        status = OK;

        DISABLE_INTS;
	opt = port->serl_regs->ctrl_a;
	prev = 0;

	if (opt & DTR)
	    prev |= MS_DTR;
    
	if (opt & RTS)
	    prev |= MS_RTS;

	mout = (uint32)arg;  //Cast to uint32 to avoid warning.  Truncation ok

	if ( !(port->current_termios->c_cflag & CDTR) ) {
	    if (mout & MS_DTR) {
		opt |= DTR;
	    } else {
		opt &= ~DTR;
	    }
	}
	if ( !(port->current_termios->c_cflag & CRTS) ||
	     !(port->current_termios->c_cflag & CRTSTOGGLE) ) 
	{
	    if (mout & MS_RTS) {
		opt |= RTS;
	    } else {
		opt &= ~RTS;
	    }
	}

	port->serl_regs->ctrl_a = opt;
	RESTORE_INTS;

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

	    //Do we need this here if serl_monitor will catch it?
	    DISABLE_INTS;
	    port->status->mstat ^= MS_DTR;
	    RESTORE_INTS;
	}

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

	    //Do we need this here if serl_monitor will catch it?
	    DISABLE_INTS;
	    port->status->mstat ^= MS_RTS;
	    RESTORE_INTS;
	}

	break;
    case TCGETBUFFERS:

	status = OK;

	// Make sure stats are up to date for receive
	assemble_messages(port);
	bufs = (struct serial_buffer_t *)arg;
#if SHORT_CIRCUIT_DATA
	bufs->rxbuf = bufs->txbuf = (port->tx_buffer->in - port->tx_buffer->out) & TX_BUF_MASK;
#else
	bufs->rxbuf = port->rqbytes - port->rdbytes;
	bufs->txbuf = (port->tx_buffer->in - port->tx_buffer->out) 
	    & TX_BUF_MASK;
#endif
	break;
    case TCTXDONE:
	status = OK;

	done = (int *)arg;
    
	if ( (port->tx_bmd_in == port->tx_bmd_out) && 
	     (port->serl_regs->status_a & TEMPTY) )
	{
	    *done = 1;
	} 
	else 
	{
	    *done = 0;
	}
	break;
    case ISOPEN:
        *(int*)arg = serial_isopen_int(channel);
        status = OK;
        break;
#ifdef NDS_SPECIFIC
    case DIPSWITCHMODE:
        *(int*)arg = DipSwitchMode_int(channel);
        status = OK;
        break;
#ifdef SW_MEI_ENABLED
    case SETDIPSWITCHES:
        status = SetDipSwitches_int(channel, (int)arg);
        break;
#endif
#endif
    default:
	assert(0);
	/* Return ENOSYS: unsupported request */
	break;
    }
    return status;
}

#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;
    
    merc_port_t * port = &ports[channel];

    assert((int)port);

    if (!port->configured || (channel != port->channel))
	return EINVAL;

    // save checkMask and context.  We only support one select at a time
    // in this driver.
    if (insert_remove == NA_SELECT_INSERT)
    {
	port->select_context = context;
	port->checkMask = *checkMask;
    }
    else
    {
	port->select_context = 0;
	port->checkMask = 0;
    }
    if ((*checkMask & NA_SELECT_READ) 
	     && (port->rx_bmd_in != port->rx_bmd_out))
     	    ret |= NA_SELECT_READ; 
    if ((*checkMask & NA_SELECT_WRITE) 
	    && (((tx_buffer[channel].in - tx_buffer[channel].out) 
                 & TX_BUF_MASK )
                < (TX_BUF_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 */

/* ---------------------------------------------------------------------- 
 *          Port Ownership
 * ---------------------------------------------------------------------- */

/*
 *  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 moew
 *                   of O_RDONLY, O_WRONLY, O_RDWR, O_NONBLOCK
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */

int 
netos_serlopen(int channel, unsigned flags)
{
    merc_port_t * port = &ports[channel];

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	assert(0);
	return EINVAL;
    }

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

    /* Fail if unsupported flags are requested. */
    if (flags & ~(O_ACCMODE|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;
    }

    port->opened = 1;
    port->openflags = flags;

#ifndef NDS_SPECIFIC
    netos_serlioctl(port->channel, TCSETSIGNALS, (char *)(MS_DTR | MS_RTS));
    netos_serlioctl(port->channel, TCFLOW, (char *)TCOSOFF);
#endif

#ifdef NDS_SPECIFIC
    if ( !(flags & O_NOFLUSH) )
#endif /* NDS_SPECIFIC */
    {
	netos_serlioctl(port->channel, TCFLUSH, (char *)TCIOFLUSH);
    }

    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)
{
    merc_port_t * port = &ports[channel];

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EBADF;
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	assert(0);
	return EBADF;
    }

#ifdef NET_OS_SPECIFIC
    /* netos is okay with just the drain forever behavior */
    netos_serlioctl(channel, TCDRAIN, 0);
#else
    /* Block for transmit data to drain */
    close_logic(MAJOR_NETOS_SERL, channel);      
#endif

    port->opened = 0;

    serl_cb_event(port->serial_channel, CLOSE_EVENT);

    return OK;
}

/* ---------------------------------------------------------------------- 
 *          Read Path
 * ---------------------------------------------------------------------- */

/* assemble_messages - Turn raw BMDs into processed rxmsgs 
 *
 * This function takes the BMDs which have been received by the read
 * completion interrupt and scans them to perform the iflag actions,
 * flow control stripping, etc.  
 *
 * NOTE: To protect the data structures that are used by this
 * function, the calling routine should get the rx_mutex for the call
 * to this function */
static void
assemble_messages(merc_port_t *port) 
{
    merc_bmd_t      *next_rx ;
    uint8       *base ;
    uint8       c ;
    uint8       *p ;
    uint32      len ;   // length of data
    uint32      first ; // first character to scan
    uint32      dropchar, noaction, must_send ;
    uint16      error_compare ;
    int stripped;
    struct termios *tios = port->current_termios;
    INTERRUPT_SAVE_AREA;

    // My little "helper" class
    dfbuilder dfb;
    memset(&dfb, 0, sizeof(dfb));
    dfb.port = port;

    must_send = 0 ;

    // Protect from other assembles, known callers TCGETBUFFERS & read()
    tx_mutex_get(&port->assemble_mutex, TX_WAIT_FOREVER);

    while ( port->rx_bmd_out == port->rx_bmd_scan && 
	    ( (port->rx_bmd_out->status & (DMA_MATCH1 | DMA_MATCH2 | DMA_MATCH3 | DMA_MATCH4)))
#if PROCESSOR == ns9360 
	     && (port->rx_bmd_out->length == 1))
#else
	    && ((RX_BUF_SIZE - CH_INSERT_SIZE - port->rx_bmd_out->length) == 1)) 
#endif
    {
	stripped = 0;
	/* Throw away buffers that are only flow control characters */
	/* we've already acted on them */
	if (tios->c_iflag & IXON) 
	{
	    if ( *(char*)RXSRC_ADDR(port->rx_bmd_out) == tios->c_cc[VSTART] || 
		 *(char*)RXSRC_ADDR(port->rx_bmd_out) == tios->c_cc[VSTOP] )
	    {
		recycle_bmd(port, port->rx_bmd_out);
		port->rx_bmd_scan = port->rx_bmd_out;
		stripped = 1;
	    }
	}
    
	if (tios->c_xflag & XIXON)
	{
	    if ( *(char*)RXSRC_ADDR(port->rx_bmd_out) == tios->c_cc[VXSTART] ||
		 *(char*)RXSRC_ADDR(port->rx_bmd_out) == tios->c_cc[VXSTOP] )
	    {
		recycle_bmd(port, port->rx_bmd_out);
		port->rx_bmd_scan = port->rx_bmd_out;
		stripped = 1;
	    } 
	}
    
	if (!stripped)
	    break; //Avoid an infinite loop
    }

    next_rx = port->rx_bmd_scan;

    if(tios->c_iflag & INPCK) {
	error_compare = DMA_RFE | DMA_ROVER | DMA_RPE ;
    } else {
	error_compare = DMA_RFE | DMA_ROVER;
    }

    while( (next_rx != port->rx_bmd_in) && can_make_messages(port) )
    {

	assert((next_rx->flags & DMA_FULL)) ;

	base = (uint8 *)RXSRC_ADDR(next_rx) + port->rx_bmd_offset ;

	dfbsetbase(&dfb, base) ;
#if PROCESSOR==ns9360
	len = next_rx->length;
#else
	len = RX_BMD_SIZE - CH_INSERT_SIZE - next_rx->length;
#endif
	len -= port->rx_bmd_offset ;

	first = 0 ; // first char to scan

#if 0 /* Not currently supporting CREAD */
	if( (tios->c_cflag & CREAD) == 0) {

	    dfbsetbmd(&dfb, next_rx) ;
	    next_rx = next_rx->next_bmd ;
	    port->rx_bmd_offset = 0 ;

	    continue ;
	}
#endif

	// Error handling.  At this point, set ch->port->rx_bmd_lsr
	// to value for last character.

	if(port->rx_bmd_offset == 0) {
	    if (next_rx->status & error_compare) {
		int lsr = 0 ;   // lsr value for DOSMODE & MSPARITY

		// the rx_count register is supposed to leave us room.
		assert(len < (RX_BUF_SIZE - CH_INSERT_SIZE)) ;

		// error character. Update counters.
		if( next_rx->status & DMA_RPE & error_compare ) {
		    port->counters->nparity++ ;
		    //flags |= FL_STAT_ERR ;
		    lsr |= 0x04 ;
		}

		if(next_rx->status & DMA_RFE) {

		    if(next_rx->status & DMA_RBRK) {
			port->counters->nbreak++ ;
			//flags |= FL_STAT_ERR ;
			lsr |= 0x10 ;
		    } else {
			port->counters->nframe++ ;
			//flags |= FL_STAT_ERR ;
			lsr |= 0x08 ;
		    }
		}

		if (next_rx->status & DMA_ROVER) {
		    port->counters->norun++;
		    lsr |= 0x02;
		}
        
		port->rx_bmd_lsr = lsr ;

	    } else {
		// no error
		port->rx_bmd_lsr = 0 ;
	    }
	}

	switch(port->thread_scan_type) {
	case SCAN_NONE:
	    // can deliver this whole BMD without scanning
	    if(port->rx_bmd_lsr == 0) {
		first = len ;
		dfbaddchars(&dfb, len) ;
		break ;
	    }
	    // else FALL THRU

	case SCAN_LASTONLY:
	    // if match happened, need to scan last
	    first = len - 1 ;
	    dfbaddchars(&dfb, first) ;

	    // FALL THRU

	case SCAN_ONMATCH:
	    // only need to scan if match happened.
	    // else, deliver the whole thing
	    if( ( (next_rx->status & port->thread_match_mask) == 0)
		&& (port->rx_bmd_lsr == 0) )
	    {
		// lnext will always put us in ONMATCH or ALWAYS mode.
		// If this BMD has data but we're not going to scan it,
		// reset rxlnext like it would have been reset had
		// we scanned the data.
		if(len)
		    port->rxlnext = 0 ;
        
		dfbaddchars(&dfb, len - first) ;
		first = len ;
		break ;
	    }

	    // FALL THRU

	case SCAN_ALWAYS:
	    // scan through charcters, starting at base + first,
	    // and look for anything interesting.


	    // Solution to checking most of those damn flags
	    // every time through the loop:  make the character
	    // comparisons 16 bit, and load the comparison locations
	    // with things that will never match if the flags
	    // aren't turned on.

	    p = base + first ;
	    while( (first++ < len)
		   && (port->avail_dfrag > MIN_DFRAG)
		   && (port->avail_rxmsg > MIN_MSG) )
	    {
		int lsr = (first == len) ? port->rx_bmd_lsr : 0 ;
		c = *p++ ;

		port->rxlnext >>= 1 ;

		// at the end, if noaction is set,
		// nothing is done; else if dropchar
		// is set, this character is dropped;
		// else this character is added.
		// Then, if must_send is set, a DXB
		// message build is forced.

		dropchar = 0 ;
		noaction = 0 ;
		must_send = 0 ;

		// break reception

		if (port->current_termios->c_iflag & ISTRIP) {
		    p-- ;
		    c &= 0x7f;
		    *p++ = c;
		}

		if(lsr & 0x10) {
            
		    if(tios->c_iflag & IGNBRK) {
			dropchar = 1 ;
		    } else if(tios->c_iflag & BRKINT) {
			if(! (port->status->estat & EV_RXB)) {
			    DISABLE_INTS;
			    port->status->estat |= EV_RXB;
			    RESTORE_INTS;
			    serl_cb_event(port->serial_channel, RXB_EVENT);
			}
			dropchar = 1 ;
		    } else if(!(tios->c_iflag & PARMRK)) {

			// PARMRK not set, break becomes 00
			p-- ;
			*p++ = 0 ;
		    } else {

			// encode as FF 10 00 (DOSMODE) or FF 00 00
			p-- ;
			*p++ = 0xff ;
			*p++ = (tios->c_iflag & DOSMODE) ? 0x10 : 0 ;
			*p++ = 0x00 ;
			dfbaddchars(&dfb, 2) ;
		    }
		} 

		// Parity and/or framing errors
		// if some character processing should be skipped,
		// lsr will still be non-zero at the end; else
		// lsr gets zeroed

		else if((lsr)) {
            
		    // if XEDATA, char goes straight into data stream
		    if(tios->c_xflag & XEDATA) {
			lsr = 0 ;
		    }

		    // if !XEDATA && IGNPAR, drop character
		    else if(tios->c_iflag & IGNPAR) {
			dropchar = 1 ;
		    }

		    // if XMSER, replace with mser char
		    // if !XEDATA && !IGNPAR && !PARMRK, replace with 00

		    else if(! (tios->c_iflag & PARMRK)) {
			p-- ;
			*p++ = 0 ;
		    }

		    // if !XEDATA && !IGNPAR && PARMRK
		    // encode as FF 00 xx (!DOSMODE) or FF zz xx (DOSMODE)

		    else {
			p-- ;
			*p++ = 0xff ;
			*p++ = (tios->c_iflag & DOSMODE) ? lsr : 0 ;
			*p++ = c ;
			dfbaddchars(&dfb, 2) ;
		    }
		}

		// All these tests are compared with 16 bit values
		// in the channel structure.  

		if(port->rxlnext != 1) {

		    // check for lnext

		    if((c == tios->c_cc[VLNEXT]) && !(lsr)) {
			port->rxlnext = 2 ;
		    }

		    // Check for flow control chars not masked by lnext

		    //The if should not be necessary but I'll put it here
		    //to guard against improper scanning in the future.
		    if (tios->c_iflag & IXON) {
			if( (c == tios->c_cc[VSTART])
			    || (c == tios->c_cc[VSTOP]) )
			{
			    dropchar = 1 ;
			}
		    }

		    if (tios->c_xflag & XIXON) {
			if( (c == tios->c_cc[VXSTART])
			    || (c == tios->c_cc[VXSTOP]) )
			{
			    dropchar = 1 ;
			}
		    }
            
		} // end flow char checks

                
		if( (tios->c_xflag & XEDATA) == 0
		    && (tios->c_iflag & (IGNPAR|PARMRK)) == PARMRK
		    && (!(tios->c_iflag & ISTRIP)) ) 
		{
		    if ( (c == 0xff) &&!(lsr) ) {
			dfbaddchars(&dfb, 1) ;
			dfbcomplete(&dfb) ;
            
			noaction = 1 ;

			dfbinsert(&dfb, 1,"\377") ;
		    }
		}

		//------------------------------
		// OK, all tests are finished.
		//
        
		if(!noaction) {
		    if(dropchar) {
			dfbskip(&dfb, 1) ;
		    } else {
			dfbaddchars(&dfb, 1) ;
		    }
		}
		if(must_send) {
		    dfbcomplete(&dfb) ;
		    if(port->rxd_count)
			dfbmake_data_msg(&dfb) ;
		}
	    } // end of while loop for characters
	} // end of switch for scan type

	dfbcomplete(&dfb) ;

	if(first >= len) {
	    // completed processing of this bmd.
        
	    // mark this dfrag as the last one to reference this bmd.
	    dfbsetbmd(&dfb, next_rx) ;

	    next_rx = next_rx + 1;
	    if (next_rx == port->rx_bmd_end) {
		next_rx = port->rx_bmd_start;
	    }
	    port->rx_bmd_offset = 0 ;
        
	} else {
	    // ran out of resources to process this BMD.  save
	    // info for next time.
	    port->rx_bmd_offset = first ;

	    // FIXME:  If we are out of dfrags, but there are
	    // no outstanding messages to send, we are in a deadlock
	    // condition; nothing is going to free up dfrags.  Two
	    // possible things to do: force delivery of data in this
	    // condition, or re-write/pack the buffers so that
	    // the data is contiguous (one dfrag per BMD) and
	    // free the earlier bmds.

	    if(port->avail_dfrag <= MIN_DFRAG) {
		port->rxflags |= NEEDFRAGS ;
	    }
	    if(port->avail_rxmsg <= MIN_MSG) {
		port->rxflags |= NEEDMSG ;
	    }
	}
    }

    port->rx_bmd_scan = next_rx ;
    if (port->rxd_count)
	dfbmake_data_msg(&dfb);
    
    tx_mutex_put(&port->assemble_mutex);
}

/*
 * 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, char * buffer, int size, int *bytesRead)
{
    INTERRUPT_SAVE_AREA;
    merc_port_t * port = &ports[channel];
    ULONG wait_option;
    int ret = 0;
    int freed_rxmsgs = 0, freed_frags = 0;
    int length = size;
    rxmsg *msg;
    dfrag *df;
    merc_bmd_t *currbmd;
#ifdef PORTBUFFERING_ENABLED
    char *origbuffer = buffer;
#endif

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	assert(0);
	return EINVAL;
    }

    //Obtain rx_mutex to protect read synchronization
    wait_option = 
	(port->openflags & O_NONBLOCK) 
	? TX_NO_WAIT
	: TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->rx_mutex, wait_option);

    if (ret == TX_NOT_AVAILABLE) {
	return EAGAIN;
    } else if (ret != TX_SUCCESS) {
	return EINVAL;
    }

    ret = 0;
    *bytesRead = 0;

#if SHORT_CIRCUIT_DATA
    while (length) {int to_end, to_in, to_read;
	
	to_end = TX_BUF_SIZE - port->tx_buffer->out;
	to_in = (port->tx_buffer->in - port->tx_buffer->out) & TX_BUF_MASK;
	to_read = (to_end > to_in) ? to_in : to_end;
	to_read = (length > to_read) ? to_read : length;

	memcpy(buffer, &port->tx_buffer->buf[port->tx_buffer->out], to_read);
	port->tx_buffer->out = (port->tx_buffer->out + to_read) & TX_BUF_MASK;
	buffer += to_read;
	*bytesRead += to_read;
	length -= to_read;

	if (port->tx_buffer->in == port->tx_buffer->out) {
	    // No more data to read right now
	    if (port->openflags & O_NONBLOCK) {
		ret = (*bytesRead) ? 0 : EAGAIN;
		break;
	    } else {
		// Block
		if (*bytesRead) {
		    break; /* We've read data, no need to block */
		} else {
		    tx_event_flags_get( &port->blocking_flags, READ_BLOCKING, 
					TX_OR_CLEAR, &port->actual_flags, 
					TX_WAIT_FOREVER);
		}
	    }
	}
    }

    port->counters->tbytes += *bytesRead;
#else // !SHORT_CIRCUIT_DATA
    assemble_messages(port);

    msg = port->rxmsg_out;
    while(length) {
        int dobreak = 0;
	/* Read blocking */
	while (msg == port->rxmsg_in) {
	    // No more data to read right now
	    if (port->openflags & 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_event_flags_get( &port->blocking_flags, READ_BLOCKING, 
					TX_OR_CLEAR, &port->actual_flags, 
					TX_WAIT_FOREVER);
		    assemble_messages(port);
		    msg = port->rxmsg_out;
		}
	    }
	}
	if (dobreak == 1) {
	    break;
	}

	if(msg->flags & MSG_ISDATA) {
	    // split into maxlen length messages
	    while(msg->len > 0 && length) {

		uint32 len = msg->len;
		if(len > (uint32) length)
		    len = length ;
            
		// at this point, we're committed to sending len bytes
		// so we can subtract it of of msg->len
		msg->len -= len ;

		while(len > 0) {
		    df = port->dfrag_out ;
		    assert(df != port->dfrag_in) ;
		    if(df->len <= len) {

			// Uses all of data in this dfrag

			if(df->len) {
			    memcpy(buffer, df->data, df->len);
			    *bytesRead += df->len;
			    length -= df->len;
			    buffer += df->len ;
			    len -= df->len ;
			}

			if((currbmd = df->dmabuf) != 0) {
			    /*
			     * Recycle this BMD now that we've got
			     * what we needed from it.  
			     */

			    recycle_bmd(port, currbmd) ;
			}
            
			dffree(port, df) ;
			freed_frags++ ;
		    } else {
			memcpy(buffer, df->data, len);
			*bytesRead += len;
			length -= len;
			buffer += len ;
			df->data += len ;
			df->len -= len ;
			len = 0 ;
		    }
		}
	    }
	} else {
	    assert(0); // Should be all data messages for now.
	    // That might change if we add in-band breaks or something
	}

	if(msg->len == 0) {
	    msgfree(port, msg) ;
	    msg = port->rxmsg_out ;
	    freed_rxmsgs++ ;
	} else {
	    /* Only leave a msg unemptied if we've read out everything */
	    assert(*bytesRead == size);
	    break ;
	}
    }

    
    // Now, free any dfrags that are zero length from the begining
    // of the list (resulting from buffers whose contents are completely
    // discarded).

    df = port->dfrag_out ;
    while(df != port->dfrag_in) {
	if(df->len != 0)
	    break ;
	currbmd = df->dmabuf ;
	if(currbmd != 0) {
	    /*
	     * Recycle this BMD now that we've got
	     * what we needed from it.  
	     */
	    recycle_bmd(port, currbmd) ;
	}

	dffree(port, df) ;
	freed_frags++ ;

	df = port->dfrag_out ;
    }

    /* Release flow control if necessary */
    if(port->status->estat & EV_IPS) {
	int in  = port->rx_bmd_in  - port->rx_bmd_start ;
	int out = port->rx_bmd_out - port->rx_bmd_start ;
	uint32 count ;
	if(in >= out)
	    count = in - out ;
	else
	    count = in + NUM_BMDS_PER_CHANNEL - out ;

	if(count < RX_BMD_LOWAT) {
	    DISABLE_INTS;
	    port->status->estat &= ~EV_IPS;
	    RESTORE_INTS;
	    serl_cb_event(port->serial_channel, IPS_EVENT);
	    // if not also stopped unconditionally
	    if(!(port->status->estat & EV_IPU)) {
		change_rx_flow(port) ;
	    }
	}
    }
#endif // SHORT_CIRCUIT_DATA not defined 
    
    port->rdbytes += *bytesRead;
    tx_mutex_put(&port->rx_mutex);

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

    return ret;
}


static void 
int_fc_scan(merc_port_t *port, merc_bmd_t *currbmd)
{
    uint32 first = 0 ;
#if PROCESSOR == ns9360
    uint32 len = currbmd->length ;
#else
    uint32 len = RX_BMD_SIZE - CH_INSERT_SIZE - currbmd->length ;
#endif
    char *p = (char *)RXSRC_ADDR(currbmd);
    char c ;

    port->counters->rbytes += len;

#if ENABLE_TESTPAT_CHECKER
    TestpatChecker(port->rx_testpat_handle, p, len);
#endif

#ifdef ALARMS_ENABLED
    if((patternAlarmEnabled != NULL) && 
       (patternAlarmEnabled[port->serial_channel]==PATTERN_ALARM_ENABLED)) 
    {
	int alarmret = PM_FLOW_CONTROL_OFF;
	// Look for a pattern match in the data
        if (port->serial_channel >= 0 
            && port->serial_channel < max_serial_ports())
        {
            alarmret = patternAlarmNewData(port->serial_channel, p, len);
        }
	if (alarmret > PM_FLOW_CONTROL_OFF) {
	    //TODO: Protect estat - only called from interrupt
	    port->status->estat |= EV_IPA;
	    change_rx_flow(port);
	}

    }
#endif

    switch(port->int_scan_type) {
    case SCAN_NONE:
	// need not look at any characters
	break ;

    case SCAN_LASTONLY:
	// look only at last character
	first = len - 1 ;
	p += first ;

	// FALL THRU

    case SCAN_ONMATCH:
	if(!(currbmd->status & port->int_match_mask)) {
	    // lnext will always put is in ONMATCH or ALWAYS mode.
	    // If this BMD has data but we're not going to scan it,
	    // reset rxlnext like it would have been reset had
	    // we scanned the data.
	    if(len) {
		port->int_rxlnext = 0 ;
		if(port->int_flags & IFL_IXANY) {
		    int tranbits = port->status->estat & (EV_OPS | EV_OPX) ;
		    port->status->estat ^= tranbits ;
		    if (tranbits & EV_OPS) {
			serl_cb_event(port->serial_channel, OPS_EVENT);
		    }
		    if (tranbits & EV_OPX) {
			serl_cb_event(port->serial_channel, OPX_EVENT);
		    }

		    change_tx_flow(port) ;
		    port->int_flags &= ~IFL_IXANY ;

		    // if XTOSS, re-write the character as a STARTC,
		    // so the upper level routine can find it to remove
		    // it from the stream.
		    if(port->current_termios->c_xflag & XTOSS) {
			*p = port->current_termios->c_cc[VSTART] ;
			// and mark as if we matched
			currbmd->status |= port->int_match_mask ;
		    }
		}
	    }
	    break ;
	}
	// FALL THRU

    case SCAN_ALWAYS:

	while(first++ < len) {
	    int tranbits;
	
	    c = *p++ ;

	    port->int_rxlnext >>= 1 ;

	    if(port->int_flags & IFL_IXANY) {
		port->int_flags &= ~IFL_IXANY ;

		// release flow control
		tranbits = port->status->estat & (EV_OPS | EV_OPX) ;
		port->status->estat ^= tranbits ;
		if (tranbits & EV_OPS) {
		    serl_cb_event(port->serial_channel, OPS_EVENT);
		}
		if (tranbits & EV_OPX) {
		    serl_cb_event(port->serial_channel, OPX_EVENT);
		}
        
		change_tx_flow(port) ;

		// if XTOSS, re-write the character as a STARTC,
		// so the upper level routine can find it to remove
		// it from the stream
		if(port->current_termios->c_xflag & XTOSS) {
		    *(p - 1) = port->current_termios->c_cc[VSTART];
		}
	    } else if(port->int_rxlnext != 1) {


		if(c == port->current_termios->c_cc[VLNEXT])
		    port->int_rxlnext = 2 ;

		//The if should not be necessary but I'll put it here
		//to guard against improper matching in the future.
		if (port->current_termios->c_iflag & IXON) {
		    if(!(port->status->estat & EV_OPS)) {
			if(c == port->current_termios->c_cc[VSTOP]) {
			    port->status->estat |= EV_OPS ;
			    serl_cb_event(port->serial_channel, OPS_EVENT);
			    change_tx_flow(port) ;
			    if(port->current_termios->c_iflag & IXANY)
				port->int_flags |= IFL_IXANY ;
			}
		    } else {
			if(c == port->current_termios->c_cc[VSTART]) {
			    port->status->estat &= ~EV_OPS ;
			    serl_cb_event(port->serial_channel, OPS_EVENT);
			}
		    }
		}

		if (port->current_termios->c_xflag & XIXON) {
		    if(!(port->status->estat & EV_OPX)) {
			if(c == port->current_termios->c_cc[VXSTOP]) {
			    port->status->estat |= EV_OPX ;
			    serl_cb_event(port->serial_channel, OPX_EVENT);
			    change_tx_flow(port) ;
			    if(port->current_termios->c_iflag & IXANY)
				port->int_flags |= IFL_IXANY ;
			}
		    } else {
			if(c == port->current_termios->c_cc[VXSTART]) {
			    port->status->estat &= ~EV_OPX ;
			    serl_cb_event(port->serial_channel, OPX_EVENT);
			}
		    }
		}
	    }
	}
    }
}


static int
rx_dma_int(void *handle)
{
    merc_port_t *port = (merc_port_t *)handle;
    merc_bmd_t *currbmd = 0;
    uint32 status = *port->rx_dma_status;

    /* Acknowledge interrupt */
    *port->rx_dma_status |= status;

    if (status & CAIP) {
	/* Implementation notes
	 *
	 * User requests flush w/ CA bit
	 * CA interrupt comes in
	 *   handler clears CA bit 
	 *   Notifies serl_monitor of CAIP received
	 *   Skips counter processing
	 * serl_monitor takes over
	 *   Set rx_bmd_in, _out back to _start
	 *   For all rx bmds:
	 *     Clear the F bit and reinit buffers
	 *   End for
	 *   Set the BMD pointer register to beginning of the list
	 *   Re-enabled channel with CE
	 */

        merc_bmd_t *errbmd = (merc_bmd_t *)(((uint32)port->rx_bmd_start) + 
					    ((uint32)(*port->rx_dma_ctrl) 
					     & DMA_INDEX));

	errbmd->flags      |= DMA_WRAP;

	*port->rx_dma_ctrl |=  (DMA_CE | CA | BDR); 

	*port->rx_dma_ctrl &= ~(CA | BDR | DMA_MODE | DMA_CE);
	*port->rx_dma_ctrl |= FLY_BY_WR;

	status = *port->rx_dma_status;

	/* Acknowledge interrupt again*/
	*port->rx_dma_status |= status;

	for (currbmd = port->rx_bmd_start ;
	     currbmd != port->rx_bmd_end ;
	     currbmd++)
	{
	    currbmd->length = RX_BMD_SIZE - CH_INSERT_SIZE;
	    currbmd->flags  = 0;
	    if (currbmd == (port->rx_bmd_end - 1)) {
		currbmd->flags |= DMA_WRAP;
	    }
	    NABeforeDMA(NA_DEVICE_TO_MEMORY_DMA,
			NULL, 
			RXSRC_ADDR(currbmd), 
			currbmd->length);

	}

	*port->rx_dma_ctrl |= DMA_CE;
    
	return 0;
    }

    if (status & DMA_ERRORS & ~PCIP) {
	if (status & NRIP) {
	    /* This poses a major problem.  I cannot move the pointer
	     * backwards, and thus I can't give back BMDs. Will just
	     * have to turn off and wait */
	    port->rx_dma_stalled = 1;
            if (port->rx_dma_oflowon == 0) {
                port->counters->noflow++;
                port->rx_dma_oflowon = 1;
            }
	} else if (status & ECIP) {
	    assert(0);
	} 
    } else if (port->rx_dma_stalled) {
        port->rx_dma_stalled = 0;
        port->rx_dma_oflowon = 0;
    }

    currbmd = port->rx_bmd_in;

    while (currbmd->flags & DMA_FULL) {
        merc_bmd_t *next_bmd;

	NAAfterDMA(NA_DEVICE_TO_MEMORY_DMA, 
		   NULL, 
		   RXSRC_ADDR(currbmd), 
		   RX_BMD_SIZE);

	next_bmd = currbmd + 1;
	if (next_bmd == port->rx_bmd_end) {
	    next_bmd = port->rx_bmd_start;
	}
    
	if ((next_bmd == port->rx_bmd_out) && (port->rx_dma_stalled)) {
            // We're overflowing and this is the next to last buffer,
            // we'll be seeing it again.
	    break;
	}

	int_fc_scan(port, currbmd);
	rx_note[port->channel]++;

        if (next_bmd == port->rx_bmd_out) {
            // We weren't stalled, but still just processed the last
            // BMD.  Probably going to stall soon, but give it a
            // chance and just exit.
            break;
        }

	currbmd = next_bmd;
    } 
    port->rx_bmd_in = currbmd;

    /* Resume transmit if a LNEXT XOFF stopped us in hw processing and
       shouldn't have */
    change_tx_flow(port);

    if (!(port->status->estat & EV_IPS) ) {
	int in = port->rx_bmd_in - port->rx_bmd_start;
	int out = port->rx_bmd_out - port->rx_bmd_start;
	uint32 count;
	if (in >= out)
	    count = in - out;
	else
	    count = in + NUM_BMDS_PER_CHANNEL - out;

	if (count > RX_BMD_HIWAT) {
	    port->status->estat |= EV_IPS;
	    serl_cb_event(port->serial_channel, IPS_EVENT);
        
	    if (!(port->status->estat & EV_IPU)) {
		change_rx_flow(port);
	    }
	}
    }

    return 0;
}

/* ---------------------------------------------------------------------- 
 *          Write Path
 * ---------------------------------------------------------------------- */

/* chain_tx_buffer
 *
 * Called by serl_monitor and netos_serlwrite, this function takes
 * data un-chained in the tx_buffer and fills BMDs.
 *
 * Returns - 0 if all data was fully chained, -1 if we ran out of BMDs
 * too soon
 */
static int
chain_tx_buffer(merc_port_t *port)
{
    INTERRUPT_SAVE_AREA;
    merc_bmd_t *next_tx;
    merc_bmd_t *curr_tx;
    int ret = 0;
    struct circ_queue *cq = port->tx_buffer;
    int bmdidx;

    // If by chance we happen to be waiting for an abort interrupt to be taken,
    // don't chain.
    if (*port->tx_dma_ctrl & CA) {
	return -1;
    }

    curr_tx = port->tx_bmd_in;
    while (port->tx_buffer_scan != cq->in) {
        int to_end, to_in, to_write;

	/* Advance through list */
	next_tx = curr_tx + 1;
        if (next_tx == port->tx_bmd_end) {
	    next_tx = port->tx_bmd_start;
	}

        /* Scan is where we last left off queueing */
        if (next_tx == port->tx_bmd_out) {
	    /* No more BMDs */
	    ret = -1;
	    break;
	}
	bmdidx = curr_tx - port->tx_bmd_start;
    
	to_end = TX_BUF_SIZE - port->tx_buffer_scan;
	to_in = (cq->in - port->tx_buffer_scan) & TX_BUF_MASK;
	to_write = (to_end > to_in) ? to_in : to_end;

#if ((defined NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA) && (defined NS9750))
        if (port->current_termios->c_cflag & CCTS)
        {
            if (to_write > TX_BMD_SIZE)
                to_write = TX_BMD_SIZE;     
        }
#endif

	curr_tx->src_addr = MyVaToPhys(&cq->buf[port->tx_buffer_scan]);

	curr_tx->length = to_write;
	txlengths[port->channel][bmdidx] = to_write;

#if ENABLE_TESTPAT_CHECKER
	TestpatChecker(port->tx_testpat_handle, &cq->buf[port->tx_buffer_scan], to_write);
#endif

	/* Flush the cache for transmit */
	NABeforeDMA(NA_MEMORY_TO_DEVICE_DMA, 
		    TXSRC_ADDR(curr_tx), 
		    NULL, 
		    to_write);

	curr_tx->status = 0;
	curr_tx->flags |= DMA_FULL;
	port->tx_buffer_scan = (port->tx_buffer_scan + to_write) & TX_BUF_MASK;

	curr_tx = next_tx;
    }
    port->tx_bmd_in = curr_tx;

    /* The DMA CE bit for TX should always be written 1 regardless if it was a "1"*/
   /* if ( !(*port->tx_dma_ctrl & DMA_CE) ) { */
        DISABLE_INTS;
        *port->tx_dma_ctrl |= DMA_CE;
	    RESTORE_INTS;
   /*  } */
    return ret;    
}

#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
static int tx_serl_int (void *handle)
{
    merc_port_t *port = (merc_port_t *)handle;

    if (port->serl_regs->status_a & CTSI)
    {
        // clear the bit
        port->serl_regs->status_a |= CTSI;

        tx_event_flags_set( &cntrl_flags, NA_CTS_CHANGE, TX_OR);
    }
    
    return 0;
}
#endif


/*
 * 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
 *
 * Notes: Even though we're using DMA, in order to maintain the
 * semantics of the write call, we have to do a buffer copy of the
 * written data.  We can take advantage of this to perform slightly
 * more efficient BMD usage by consolidating all buffer data when
 * creating DMA buffers but that will not buy us near enough to offset
 * the copy unfortunately.
 *     
 */
int 
netos_serlwrite(int channel, char * buffer, int length, int *bytesWritten)
{
    merc_port_t * port = &ports[channel];
    ULONG wait_option;
    UINT ret;
    int written = 0;
    int notfirsttime = 0;
    INTERRUPT_SAVE_AREA;

    if ( (channel >= max_merc_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	assert(0);
	return EINVAL;
    }

    //Obtain tx_semaphore to protect write synchronization
    wait_option = 
	(port->openflags & O_NONBLOCK) 
	? TX_NO_WAIT
	: TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->tx_mutex, wait_option);

    if (ret == TX_NOT_AVAILABLE) {
	return EAGAIN;
    } else if (ret != TX_SUCCESS) {
	return EINVAL;
    }

    if (port->breakstatus != BRK_IDLE) {
	if (port->openflags & O_NONBLOCK) {
            tx_mutex_put(&port->tx_mutex);
	    return EAGAIN;
	} else {
	    //Block for break to finish.
	    tx_event_flags_get( &port->blocking_flags, BRK_BLOCKING, 
				TX_OR_CLEAR, &port->actual_flags, 
				TX_WAIT_FOREVER);
	}
    }

    // RTS Toggle support
    if ((port->current_termios->c_cflag & CRTSTOGGLE) &&
     ((port->current_termios->c_cc[VPREDELAY] != 0) || 
     (port->current_termios->c_cc[VPOSTDELAY] != 0))) {
	// De-activate any RTS down timer
	tx_timer_deactivate(&port->rtsdown_timer);
	// Raise RTS
	DISABLE_INTS;
	port->serl_regs->ctrl_a |= RTS;
	RESTORE_INTS;

	// Wait (optional) pre-delay
	if ((port->current_termios->c_cc[VPREDELAY])
	    && (tx_buffer[channel].in == tx_buffer[channel].out)
	    && (port->serl_regs->status_a & TEMPTY)
	    ) 
	{
	    DISABLE_INTS;
	    port->status->estat |= EV_OPT;
	    RESTORE_INTS;
	    change_tx_flow(port);
	    tx_timer_change(&port->rtsup_timer, 
			    port->current_termios->c_cc[VPREDELAY],
			    0);
	    tx_timer_activate(&port->rtsup_timer);
	}
    }

    ret = 0;
    do {
      
	if ( notfirsttime ) {
	    if (port->openflags & O_NONBLOCK) {
		ret = (written == length) ? 0 : EAGAIN;
		break;
	    } else {
		// Block
		tx_event_flags_get( &port->blocking_flags, WRITE_BLOCKING, 
				    TX_OR_CLEAR, &port->actual_flags, 
				    TX_WAIT_FOREVER);
	    }
	} else {
	    notfirsttime = 1;
	}
	written += port->write_fn(buffer + written, length - written,
				  &tx_buffer[channel], TX_BUF_SIZE, 
				  TX_BUF_MASK, 
				  &port->ocol, port->current_termios);

#if SHORT_CIRCUIT_DATA
	// Temporarily short circuit any actual data transfer and read
	// back directly from the transmit buffer
	//port->rx_note++;
        rx_note[port->channel]++;
#else

	/* Queue to DMA controller */
	if (port->monitor_chains == 0) {
	    /* DMA chaining can occur here or in serl_monitor.
	     * monitor_chains is a variable the two entities use to
	     * communicate responsibility */
	    if (chain_tx_buffer(port) < 0) {
		port->monitor_chains = 1;
	    }
	}
#endif
    } while (written != length);

    if (bytesWritten)
	*bytesWritten = written;

    tx_mutex_put(&port->tx_mutex);
    if ( (written == 0) && (ret == EAGAIN) ) {
        return EAGAIN;
    } else {
        return 0;
    }
}

static int
tx_dma_int(void *handle)
{
    merc_port_t *port = (merc_port_t *)handle;
    merc_bmd_t *currbmd = 0;
    uint32 status = *port->tx_dma_status;

    /* Acknowledge interrupt */
    *port->tx_dma_status |= status;

    /* Determine if there's an error */
    if (status & ECIP) {
        /* Bad BMD or buffer pointer: Implies queuing code queued wrong */
        assert(0);
    }
    if (status & NRIP) {
	/* Queue is finished because F was not set on the next BMD */

        *port->tx_dma_ctrl &= ~DMA_CE;
    }
    
#if ((defined NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA) && (defined NS9750))
    if ((port->current_termios->c_cflag & CCTS) && 
                    (!(port->serl_regs->status_a & CTSI)))
    {
        tx_event_flags_set( &cntrl_flags, NA_CTS_CHANGE, TX_OR);
    }
#endif

    if (status & CAIP) {
	/* Implementation notes
	 *
	 * User requests flush w/ CA bit
	 * CA interrupt comes in
	 *   handler clears CA bit 
	 *   Notifies serl_monitor of CAIP received
	 *   Skips counter processing
	 * serl_monitor takes over
	 *   Set tx_bmd_in, _out back to _start
	 *   For all tx bmds:
	 *     Clear the F bit and reinit buffers
	 *   End for
	 *   Set the BMD pointer register to beginning of the list
	 *   Re-enabled channel with CE
	 */
        merc_bmd_t *errbmd = (merc_bmd_t *)(((uint32)port->tx_bmd_start) + 
					    ((uint32)(*port->tx_dma_ctrl) 
					     & DMA_INDEX));

	errbmd->flags      |= DMA_WRAP;

	*port->tx_dma_ctrl |=  (DMA_CE | CA | BDR); 
	while (*port->tx_dma_ctrl & DMA_INDEX) 
	    ;
	*port->tx_dma_ctrl &= ~(DMA_CE | CA | BDR);

	assert( (*port->tx_dma_ctrl & DMA_INDEX) == 0);

	status = *port->tx_dma_status;
    
	/* Acknowledge interrupt again*/
	*port->tx_dma_status |= status;

	for (currbmd = port->tx_bmd_start ;
	     currbmd != port->tx_bmd_end ;
	     currbmd++)
	{
	    currbmd->src_addr = 0;
	    currbmd->length   = 0;
	    currbmd->dst_addr = 0;
	    currbmd->flags    = 0;
	    if (currbmd == (port->tx_bmd_end - 1)) {
		currbmd->flags |= DMA_WRAP;
	    }
	}

	port->tx_bmd_in = port->tx_bmd_out = port->tx_bmd_start;
	port->tx_buffer_scan = tx_buffer[port->channel].in 
	    = tx_buffer[port->channel].out;
	return 0;
    }
    if (status & PCIP) {
	/* Should not happen for Fly-by-Read mode, also sets NCIP
	 * so would trigger assert in default anyway, don't really
	 * need to enable this one either probably */
	assert(0);

    }
#if 0
    if (status & NCIP) 
#endif
    {
	/* If no error, adjust counters and return buffer to be used again */
	currbmd = port->tx_bmd_out;
	while (currbmd != port->tx_bmd_in )
	{
	    int bmdidx = currbmd - port->tx_bmd_start;

	    if (currbmd->flags & DMA_FULL) {
		break;
	    }

	    NAAfterDMA(NA_MEMORY_TO_DEVICE_DMA,
		       TXSRC_ADDR(currbmd), 
		       NULL,
		       txlengths[port->channel][bmdidx]
		       );

	    /* adjust tx_buffer.out */
#if PROCESSOR == ns9360 
            assert(currbmd->length == txlengths[port->channel][bmdidx]); // No errors to get here
#else
	    assert(currbmd->length == 0); // No errors to get here
#endif
	    port->tx_buffer->out = (uint32)currbmd->src_addr 
		+ txlengths[port->channel][bmdidx]
		- (uint32)port->tx_buffer->buf;

	    /* Handle a write at the very end of tx_buf */
	    if (port->tx_buffer->out == TX_BUF_SIZE) {
		port->tx_buffer->out = 0;
	    }

	    port->counters->tbytes += txlengths[port->channel][bmdidx];
	    assert(port->tx_buffer->out < TX_BUF_SIZE);

	    currbmd++;
	    if (currbmd == port->tx_bmd_end) {
		currbmd = port->tx_bmd_start;
	    }
	}
	port->tx_bmd_out = currbmd;
    }

    return 0;
}

/*----------------------------------------------------------------------
 *          Miscellaneous Helpers
 *----------------------------------------------------------------------*/

/* 
 * Function: serl_monitor()
 *
 * This function provides most, or all, of the periodic processing for the 
 * system.  Its responsibilities usually include signal monitoring, event
 * generation, blocking task wakes, flow control, etc.
 */
static void
serl_monitor(void)
{
    INTERRUPT_SAVE_AREA;
    struct serial_status_t status;
    int i;

    while (1) {
	for (i = 0 ; i < NUM_MERC_SERL_PORTS ; i++) {
	    merc_port_t *port = &ports[i];
	    unsigned char mprev;
	    unsigned char mdiff;
	    unsigned char mflow;

	    if (!port->write_fn)
		continue;

	    mprev = port->status->mstat;

	    netos_serlioctl(i, TCGETSTATS, (char *)&status);

	    mdiff = mprev ^ status.mstat;

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

	    //Flow control for incoming sigs other than CTS
	    mflow = (port->current_termios->c_cflag & CMDMIN)  >> MFLOW_SHIFT;
	    if (status.estat & EV_OPH) {              // We are flow-controlled
		if ( (mflow & status.mstat) == mflow) {// and don't need to be
        
		    DISABLE_INTS; //Protect estat from other tasks and int
		    port->status->estat &= ~EV_OPH;
		    RESTORE_INTS;
		    change_tx_flow(port);
		    serl_cb_event(port->serial_channel, OPH_EVENT);
		}
	    } else {                                //We're not flow controlled
		if ( (mflow & status.mstat) != mflow) {//and should be
		    DISABLE_INTS;
		    port->status->estat |= EV_OPH;
		    RESTORE_INTS;
		    change_tx_flow(port);
		    serl_cb_event(port->serial_channel, OPH_EVENT);
		}
	    }

	    if (rx_note[i] != rx_noted[i]) {
		rx_noted[i] = rx_note[i];
		serl_cb_event(port->serial_channel, RX_DATA);
#if NETWORK_STACK == TRECK
		if (port->checkMask & NA_SELECT_READ)
		    tfDeviceSelectNotify(NA_SELECT_READ, port->select_context);
#endif
		// Wake any blocked reads
		tx_event_flags_set( &port->blocking_flags, READ_BLOCKING, 
				    TX_OR);
#ifdef SERIAL_4_RX_LEDS_ENABLED
		/* NOTE: This is kind of amat4-specific.  Amat4 has 3 mercury
		 * serial ports, and the fourth is an external uart (sp_serl).
		 * I have defined the 4th port for some future device with
		 * 4 serial ports that needs RX LED control.
		 */
		switch (port->serial_channel)
		{
		    case 0:
			led_mark_activity( SERIAL_ACTIVITY_LED0 );
			break;
		    case 1:
			led_mark_activity( SERIAL_ACTIVITY_LED1 );
			break;
		    case 2:
			led_mark_activity( SERIAL_ACTIVITY_LED2 );
			break;
		    case 3: /* Not used by amat4. */
			led_mark_activity( SERIAL_ACTIVITY_LED3 );
			break;
		    default:
			break;
		}
#endif
	    }

	    //QUESTION: is this sufficient, or do I need a size limit on
	    //tx chaining to make sure we are always continuing to
	    //process while more data is added
	    if ( ((tx_buffer[i].in - tx_buffer[i].out) & TX_BUF_MASK )
		 < (TX_BUF_SIZE / 2) ) 
	    {
#if NETWORK_STACK == TRECK
		if (port->checkMask & NA_SELECT_WRITE)
		    tfDeviceSelectNotify(NA_SELECT_WRITE, 
                                         port->select_context);
#endif
		//Wake any blocked writes
		tx_event_flags_set( &port->blocking_flags, WRITE_BLOCKING, 
                                    TX_OR);
	    }

	    if (port->monitor_chains) {
		/* Channel ran out of BMDs previously.  Try again */
		if (chain_tx_buffer(port) == 0) {
		    port->monitor_chains = 0;
		}
	    }

	    if (port->rx_dma_stalled) {
		DISABLE_INTS;
		*port->rx_dma_ctrl |= DMA_CE;
		RESTORE_INTS;
	    }

	    if (port->breakstatus == BRK_ACTIVE) {
		port->breaktime -= SERL_POLL_RATE;
		if (port->breaktime <= 0) {
		    DISABLE_INTS;
		    port->serl_regs->ctrl_a &= ~BRK;
		    port->status->estat &= ~EV_TXB;
		    RESTORE_INTS;
                    serl_cb_event(port->serial_channel, TXB_EVENT);

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

	    if ( (port->tx_bmd_in == port->tx_bmd_out) && 
		 (port->serl_regs->status_a & TEMPTY) )
	    {
		if (port->breakstatus == BRK_REQUESTED) {
		    // Start a break
		    DISABLE_INTS;
		    port->serl_regs->ctrl_a |= BRK;
		    RESTORE_INTS;

		    port->breakstatus = BRK_ACTIVE;
		}
		if (port->tcdrain_req) {
		    tx_event_flags_set( &port->blocking_flags, DRAIN_BLOCKING, 
					TX_OR);
		    port->tcdrain_req = 0;
		}

		// Start RTS down thread if necessary
		if ( (port->current_termios->c_cflag & CRTSTOGGLE) &&
		 ((port->current_termios->c_cc[VPREDELAY] != 0) || 
		 (port->current_termios->c_cc[VPOSTDELAY] != 0)) && 
		 (port->serl_regs->ctrl_a & RTS))
		{
		    char dummy_buf[256];
		    UINT active;
		    ULONG remaining;
		    ULONG reschedule;
		    TX_TIMER *next_timer;
			
		    tx_timer_info_get(&port->rtsdown_timer, 
			    (char **)&dummy_buf,
				      &active,
				      &remaining,
				      &reschedule,
				      &next_timer);
		    if (active != TX_TRUE) {
			tx_timer_change(&port->rtsdown_timer, 
			 port->current_termios->c_cc[VPOSTDELAY] + 
			 rtsTCharTime(port),
			    0);
			tx_timer_activate(&port->rtsdown_timer);
		    }
		}
	    }

	    /* TODO: poll_dips(); when we're ready to do MEI*/

	    //Note: only serl_monitor should change mstat right?
	    port->status->mstat = status.mstat;
	}
#ifdef NA_SERIAL_ENABLE_CTS_DUPLICATE_CHAR_ERRATA
        tx_event_flags_get( &cntrl_flags, NA_CTS_CHANGE, 
                    TX_OR_CLEAR, &actual_flags, SERL_POLL_RATE);
#else
	    tx_thread_sleep(SERL_POLL_RATE); 
#endif
    }
}

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

    for (i = 0 ; i < max_merc_ports() ; i++) {
	if (desc == NAserialComPortFds[i]) {
	    /* yes -- it's /com/<i> */
	    if (ports[i].opened) {
		return 1;
	    } else {
		break;
	    }
	}
    }
    return 0;
}
#endif

/*
 * Function: max_merc_ports()
 *
 * Return: Returns the number of serial ports available on the sytem
 *
 */
static int 
max_merc_ports(void)
{
    return 4; /* For now we always have four ports */
}

/*
 * 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 channel)
{
    merc_port_t * port = &ports[channel];

    if ( (channel >= max_serial_ports() ) || ( channel < 0) ) {
	return 1; // A non-existant port is always open.
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	return 1; // A non-existant port is always open.
    }

    return port->opened;
}

#if 0 //def NDS_SPECIFIC // No longer used, see TCFLOW TCIAOFF
/* A function provided to allow the pattern alarm functionality to tell us
 * that is now able to continue receiving data
 */
void
patternAlarmExitFlow(int channel)
{
    merc_port_t *port = &ports[channel];
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS;
    port->status->estat &= ~EV_IPA;
    RESTORE_INTS;
    change_rx_flow(port);
}
#endif /* NDS_SPECIFIC */

static VOID 
rtsup_fn(ULONG arg __attribute__((unused)) )
{
    merc_port_t *port = (merc_port_t *)arg;
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS; // Protect estat
    port->status->estat &= ~EV_OPT;
    RESTORE_INTS;
    change_tx_flow(port);
}

static VOID
rtsdown_fn(ULONG arg __attribute__((unused)) )
{
    merc_port_t *port = (merc_port_t *)arg;
    INTERRUPT_SAVE_AREA;

    if (port->current_termios->c_cflag & CRTSTOGGLE) { // May have been turned off
	DISABLE_INTS;  //Protect MCR
	port->serl_regs->ctrl_a &= ~RTS;
	RESTORE_INTS;
    }
}

#if 0 // Unused
int
serl_port_index(int channel)
{
    return channel;
}
#endif

/*
 * int GetResource(int *resource, void *owner)
 *
 *  Description:
 *		This routine locks a resource
 *
 *  Parameters:
 *		resource - pointer to the resource lock
 *		owner	 - pointer to the resource owner
 *
 *  Return Values:
 *		TRUE	- resource was available to a user & locked
 *		FALSE   - resource already in use
 */	
int GetResource(int *resource, void *owner)
{
	TX_INTERRUPT_SAVE_AREA

	if (resource == NULL)
		return FALSE;

	TX_DISABLE
	if (*resource)
	{
		TX_RESTORE
		return FALSE;
	}

	*resource = (int) owner;
	TX_RESTORE
	return TRUE;
}

/*
 * int ReleaseResource(int *resource, void *owner)
 *
 *  Description:
 *		This routine unlocks a resource
 *
 *  Parameters:
 *		resource - pointer to the resource lock
 *		owner	 - pointer to the resource owner
 *
 *  Return Values:
 *		TRUE	- resource is released
 *		FALSE   - bad owner
 */	
int ReleaseResource(int *resource, void *owner)
{
	if (resource && (*resource == (int)owner))
	{
		*resource = 0;
		return TRUE;
	}
	return FALSE;
}
