#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"
#include "nagpio.h"
#include "gpio_def.h"
#define GpioGetValue naGpioGetValue
#define GpioSetOutputValue naGpioSetOutputValue
#define GpioSetOutputWithValue naGpioSetOutputWithValue
#define GpioGetAddress naGpioGetAddress
#define GpioSetFunction naGpioSetFunction

#else
#define NDS_SPECIFIC 1
#define NONCACHED_ALLOC 1 /* FIXME: Correct caching. Really, not uncached */
#define BSP_ARM9
#define SHORT_CIRCUIT_DATA 0
#define NA_GPIO_SELECT_MUX_FUNC0            (0x00)
#define NA_GPIO_SELECT_MUX_FUNC1            (0x08)
#endif
 
#include "sysClock.h" /* for NAgetXtalFreq */
#include "camry_serl.h"
#include "errno.h"
#include "string.h"
#include "tx_api.h"
#include "NA_ISR.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"

#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

#include "iohub_reg.h"
#include "uart_16750.h"

#endif /* NDS_SPECIFIC */

#include "netosIo.h"

#if !NONCACHED_ALLOC
#include "noncache.h"
#endif
 
#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

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

static int serial_isopen_int(int chan);

#if NDS_SPECIFIC
/* TODO: Replace when MEI is written */
static int
DipSwitchMode_int(int portnum)
{
    /* unused parameter */
    (void) portnum;

    return DIPSWITCH_232;
}
#endif


/* 
 * On NS9210 and NS9215, RTS in modem_ctrl register can't be controlled by 
 * software while CTS is used for HW flow control. 
 * To be able to use CTS to halt the transmitter, the HW requires the AFE bit in 
 * modem_ctrl register to be 1, but this requirement prohibits us writing to RTS directly. 
 * The work around of this problem is to use RTS pin as GPIO output 0/1 to control RTS. 
 * Refer to NS9215/NS9210 ASIC Errata on RTS.
 */
/* return the logic level of RTS. Return of 0 indicates RTS is deasserted, return of non-zero indicates RTS is asserted */
static int read_rts(camry_port_t *port)
{
 	if (GpioGetValue(port->rts_gpio_pin))
 		return 0;
 	else
 		return 1;
} 

/* Assert RTS if the logica_level is 1, deassert RTS if the logica_level is 0. */
static void write_rts(camry_port_t *port, int logic_level)
{
	if (logic_level)
		GpioSetOutputValue(port->rts_gpio_pin, 0);
	else
		GpioSetOutputValue(port->rts_gpio_pin, 1);	

}   

/*
 * If HW flow control is disabled we must read the value of CTS
 * from the GPIO status register because the UART wrapper does
 * not pass the value of CTS through in this case.
 *
 * 09/05/2008: 
 * 1)	You can read CTS from modem status register if RTS/CTS is the only 
 *      thing you use for flow control. If you want to use also DTR/DSR then , 
 *      you can't read CTS from that register. This is current limitation.
 * 2)	You can read CTS status by reading the input status of appropriate 
 *      GPIO register. And CTS does not have to be reconfigured as GPIO for that. 
 *      This provides a way for software workaround.
 */
static int read_cts(camry_port_t *port)
{
    /*if (port->serl_regs->wrap_conf & TXFLOW_CTS)
    {
        uint32 opt;
        opt = port->serl_regs->modem_status;

        return (opt & S_CTS);
    }
    else
    {*/
        if (GpioGetValue(port->cts_gpio_pin))
            return 0;
        else
            return 1;
    //}
} 
	   
/*
 * 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_camry_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 camry_serl_t * const serl_regs[NUM_CAMRY_SERL_PORTS] = {
    CAMRY_REGS_0,
    CAMRY_REGS_1,
    CAMRY_REGS_2,
    CAMRY_REGS_3,
};

/* BMD layout:
 *  Chan 0 BMDs
 *    - RX BMDs
 *    - TX BMDs
 *  Chan 1 BMDS.
 *  ...
 */
#if !NONCACHED_ALLOC
static camry_bmd_t *bmds;
static uint8 *rx_bufs;
static uint8 *tx_data_buf;
#else
static camry_bmd_t bmds[NUM_CAMRY_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_CAMRY_SERL_PORTS * RX_BUF_SIZE] __attribute__ ((aligned(32)));
#else
#pragma alignvar (32)
static uint8      rx_bufs[NUM_CAMRY_SERL_PORTS * RX_BUF_SIZE];
#endif /* #if defined(NDS_SPECIFIC) || defined(NETOS_GNU_TOOLS) */

static uint8 tx_data_buf[NUM_CAMRY_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_CAMRY_SERL_PORTS][NUM_BMDS_PER_CHANNEL];

static struct circ_queue tx_buffer[NUM_CAMRY_SERL_PORTS];

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

static camry_dma_t * const dma_regs [NUM_CAMRY_SERL_PORTS] = {
    DMA_CONTROL_REG0,
    DMA_CONTROL_REG1,
    DMA_CONTROL_REG2,
    DMA_CONTROL_REG3,
};

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

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

static camry_port_t ports[NUM_CAMRY_SERL_PORTS];

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(camry_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_camry_ports(); ++i)
    {
        //ports[i].serl_regs->ctrl_a &= ~CE;
        ports[i].serl_regs->wrap_conf &= ~RXEN;
        ports[i].serl_regs->wrap_conf &= ~TXEN;
        ports[i].dma_regs->rx_control &= ~DMA_CE;
        ports[i].dma_regs->tx_control &= ~DMA_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(camry_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->wrap_conf &= ~TXEN;
    //port->serl_regs->wrap_conf &= ~TXFLOW_SOFT;
    } 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!. */
        port->serl_regs->wrap_conf |= TXEN;
        //port->serl_regs->wrap_conf |= TXFLOW_SOFT;
    }
    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(camry_port_t *port)
{
  INTERRUPT_SAVE_AREA;
    
  /* Wait for FORCE_EN to be zero, so any previous characters are sent */
  while (port->serl_regs->force_reg & (uint32)FORCE_EN)
    ;

  DISABLE_INTS; //guarantee we wont' get any ints.
  /* Tell the hardware we want to force a char */
  port->serl_regs->force_reg = port->current_termios->c_cc[VSTOP] | (uint32)FORCE_EN;
  /* what if transmitter is disabled? */
  if (!(port->serl_regs->wrap_conf & TXEN))
  {
    port->serl_regs->wrap_conf |= TXEN;  
    while (port->serl_regs->force_reg & (uint32)FORCE_EN)
        ;
    port->serl_regs->wrap_conf &= ~TXEN;
  }
  
  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(camry_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(camry_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;
        MCR = port->serl_regs->modem_ctrl;
	if (port->status->estat & EV_IPALL) {
	    if (port->current_termios->c_cflag & CDTR) {
		MCR &= ~DTR;
	    }
	    if (port->current_termios->c_cflag & CRTS) {
		write_rts(port, 0);
		
		
	    }
	} else {
	    if (port->current_termios->c_cflag & CDTR) {
		MCR |= DTR;
	    }
	    if (port->current_termios->c_cflag & CRTS) {
		write_rts(port, 1);
	    }
	}
	//port->serl_regs->ctrl_a = MCR;
	port->serl_regs->modem_ctrl = 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 5

static void
config_match_flow_reg(camry_port_t * port,
		      struct termios *tios,
		      uint32 *match_reg, 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_MATCH0, 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] );
	    match_reg[mccount] = tios->c_cc[VSTOP] | (uint32)MATCH_ENABLE;
	    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);
	    match_reg[mccount] = 0xff | (uint32)MATCH_ENABLE;
	    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] );
	    match_reg[mccount] = tios->c_cc[VXSTOP] | (uint32)MATCH_ENABLE;
	    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] );
	    match_reg[mccount] = tios->c_cc[VSTART] | (uint32)MATCH_ENABLE;
	    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] );
	    match_reg[mccount] = tios->c_cc[VXSTART] | (uint32)MATCH_ENABLE;
	    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] );
	    match_reg[mccount] = tios->c_cc[VLNEXT] | (uint32)MATCH_ENABLE;
	    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;

   //Li  *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) 
{
    unsigned int Clk = NAgetCpuClkFreq(); //Li NAgetSysOscFreq();
    unsigned int Btime = BUFFER_GAP_TIME * Clk /1000 - 1;      /* 1000 ms/sec */
    assert((Btime & ~GAP_MASK) == 0);

    *buffer_gap_timer_p = (uint32)GAP_EN | 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)
{
    unsigned int Clk = NAgetCpuClkFreq(); //Li 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 /1000 - 1; /* 1000 ms/sec */

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

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

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

static int
rtsTCharTime(camry_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(camry_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;
    uint32 bit_rate_reg = EBIT | TMODE | TICS_BRG | RICS_DPLL;*/
    uint32 line_control = 0, modem_control = (port->serl_regs->modem_ctrl & (DTR | RTS));
    uint32 wrap_conf= RXEN | TXEN | UART_MODE | DTREN | TXFLOW_SOFT | TXFLOW_RXCBFC;
    
    unsigned divisor;
    uint32 match_reg[5];


    uint32 char_gap_timer = 0;
    uint32 buffer_gap_timer = 0;
    uint32 flow_ctrl = 0; 
    int word_size = 5;
    int frame_size;
    
    switch(tios->c_cflag & CSIZE) {
    case CS8:
	word_size++;
    case CS7:
	word_size++;
    case CS6:
	word_size++;
    case CS5:
	line_control |= WORD_LEN(word_size);
    };

    if (tios->c_cflag & CSTOPB) {
	line_control |= 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) { 
		    line_control |= MARK_PARITY; 
	    } else /* !PARODD */ { 
		    line_control |= SPACE_PARITY; 
	    }
	} else /* !XPAR */ {
	    if (tios->c_cflag & PARODD) { 
		    line_control |= ODD_PARITY;
	    } else /* !PARODD */ { 
		    line_control |= EVEN_PARITY;
	    }
	}
    }

    /* Hardware flow control */
    if (tios->c_cflag & CCTS) {
//	ctrl_a |= CTSTX; 
    wrap_conf |= TXFLOW_CTS;
    modem_control |=AFE;
    }

    memset(match_reg, 0, sizeof(match_reg));
    
    config_match_flow_reg(port, tios, &match_reg[0], &flow_ctrl);

    divisor = NAgetSysOscFreq() / (16 * tios->baud);

    config_char_gap(tios, &char_gap_timer, frame_size);
    config_buf_gap(&buffer_gap_timer);

    /* 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) ) {
	/*
	 * Use HW RTS toggle to support CRTSTOGGLE when predelay and post delay are zero,
	 * in this case, the RTS pin has to use the default GPIO function for RTS.
	 */
	    GpioSetFunction(port->rts_gpio_pin, port->rts_gpio_pin_default_func);
	    wrap_conf |= RTSEN;
	}
	else
	{
	    GpioSetOutputWithValue(port->rts_gpio_pin, 1); /* Deassert RTS */
	}
    }

    /* Update signals to new flow control */
    if (!(port->status->estat & EV_IPALL)) {
	if (tios->c_cflag & CDTR) {
	    //ctrl_a |= DTR; /* Turn ON DTR */
	    modem_control |=DTR;
	}
	if (tios->c_cflag & CRTS) {
	  write_rts(port, 1);
	}
    }
    
    /* 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->line_ctrl = line_control;
    port->serl_regs->modem_ctrl = modem_control /*| AFE */;
    port->serl_regs->line_ctrl |= DLAB;
    port->serl_regs->baud_div_lsb = divisor & 0xff;
    port->serl_regs->baud_div_msb = divisor >> 8;
    port->serl_regs->line_ctrl &= ~DLAB;
    port->serl_regs->buffer_gap_timer = buffer_gap_timer;
    port->serl_regs->char_gap_timer = char_gap_timer;
    port->serl_regs->match_reg[0] = match_reg[0];
    port->serl_regs->match_reg[1] = match_reg[1];
    port->serl_regs->match_reg[2] = match_reg[2];
    port->serl_regs->match_reg[3] = match_reg[3];
    port->serl_regs->match_reg[4] = match_reg[4];
    port->serl_regs->flow_ctrl = flow_ctrl;
    port->serl_regs->tx_count |= (uint32)TX_COUNT_EN;
    port->serl_regs->wrap_conf = RXFLUSH | TXFLUSH; //reset FIFO
    port->serl_regs->wrap_conf = wrap_conf;
    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
    camry_port_t * port = &ports[channel];
    camry_serl_t * sregs;
    camry_bmd_t * bmd;
    int bufidx = 0, i = 0;
    unsigned divisor;
    unsigned long address, bitNum, gpioNum;
#ifdef NDS_SPECIFIC
    unsigned long addr, bit;
    
    switch (channel) {
    case 0:
    case 1:
    case 2:
    case 3:
	for (i = channel*8; i < channel*8+8; ++i)
	{
	    /* On Connect EM, some signals aren't serial. */
	    if (i >= 16 && i <= 18 || i >= 20 && i <= 22)   /* TODO: Fix this so it is not product-specific! */
		continue;
	    ProcessorGpioPin(i, &addr, &bit);
	    ProcessorGpioSetFunction(addr, bit, 0);
	}
	break;
    default:
	break;
    };
#endif

#if NET_OS_SPECIFIC
        *NA_SCM_CCR_ADDRESS |= 1 << (channel+1);        /* turn clock on */
#endif
        /* reset module */
        *CAMRY_MODULE_RESET_REGISTER &= ~(1 << (channel+1));
        /* enable module */
        *CAMRY_MODULE_RESET_REGISTER |= 1 << (channel+1);
        
    /*  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_camry_ports() ) || ( channel < 0) ) {
	/* Out of range */
	assert(0);
	return EINVAL;
    }

    if (port->configured) {
	/* enter called twice for some reason */
	assert(0);
	return OK;
    }
    
    switch (channel) {
    case 0:
    	port->rts_gpio_pin = GPIO_UARTA_RTS;
    	port->cts_gpio_pin = GPIO_UARTA_CTS;
    	break;
    case 1:
    	port->rts_gpio_pin = GPIO_UARTB_RTS; 
    	port->cts_gpio_pin = GPIO_UARTB_CTS; 
    	break;
    case 2:
    	port->rts_gpio_pin = GPIO_UARTC_RTS;	
    	port->cts_gpio_pin = GPIO_UARTC_CTS;	
    	break;
    case 3:
    	port->rts_gpio_pin = GPIO_UARTD_RTS;	
    	port->cts_gpio_pin = GPIO_UARTD_CTS;	
    	break;
    default:
	break;
    };
    GpioSetOutputWithValue(port->rts_gpio_pin, 1); /* Deassert RTS */
    
    if(GpioGetAddress(port->rts_gpio_pin, &address, &bitNum))
    {
    		gpioNum = address*32 + bitNum;
    		if (gpioNum == 21 || gpioNum == 29)
    			port->rts_gpio_pin_default_func = NA_GPIO_SELECT_MUX_FUNC1;
    		else
    			port->rts_gpio_pin_default_func = NA_GPIO_SELECT_MUX_FUNC0;
    }

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

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

	bmds = nonCachedMalloc(i);
	memset(bmds, 0, i);
    
	i = NUM_CAMRY_SERL_PORTS * RX_BUF_SIZE;
	rx_bufs = nonCachedMalloc(i);
	memset(rx_bufs, 0, i);
    
	i = NUM_CAMRY_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 camry_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 camry_port_t members */

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

    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[channel * 2 + 1].un.reg;
    //port->tx_dma_status     = &dma_status_regs[channel * 2 + 1].un.reg;
    //port->tx_dma_bmd        = &dma_bmd_regs[channel*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;

    /* Configure initial serial registers */
    sregs = port->serl_regs;
    
    /*
     * Disable interrupts from the port
     */
    port->dma_regs->rx_int_conf = 0;  
    port->dma_regs->tx_int_conf = 0;  
    port->serl_regs->int_enable =0;  
    port->serl_regs->baud_div_msb =0; /*uart interrupt enable reg*/
    
    /*
     * Reset the port and its fifos
     */
    sregs->wrap_conf = RXFLUSH | TXFLUSH; //reset FIFO
    sregs->wrap_conf =0; //reset FIFO
    

    /* Basic 9600 8N1 configuration, DMA, sw flow */
    sregs->wrap_conf = RXEN | TXEN | UART_MODE | DTREN;
    sregs->line_ctrl = ONE_STOP_BIT | EIGHT_BIT_WORD;
    sregs->modem_ctrl = 0;

    /* A basic config.  With default camry dev board and Digi products
     * should be 9600 baud. Overwritten by real values once known */
    divisor = NAgetSysOscFreq() / (16 * 9600);
    sregs->line_ctrl |= DLAB;
    sregs->baud_div_lsb = divisor & 0xff;
    sregs->baud_div_msb = divisor >> 8;
    sregs->line_ctrl &= ~DLAB;
    sregs->buffer_gap_timer = 0;
    sregs->char_gap_timer   = 0; 
    sregs->match_reg[0] = XON | MATCH_ENABLE;
    sregs->match_reg[1] = XOFF | MATCH_ENABLE;  
	sregs->flow_ctrl        = FLOW(1, FLOW_XON) | FLOW(2, FLOW_XOFF);  
	sregs->wrap_conf |= TXFLOW_SOFT | TXFLOW_RXCBFC;  //enable receive char-based flow control 
	
	/* Enable internal UART FIFO.  This is different than the wrapper FIFOs */
    sregs->fifo_ctrl |= FIFO_EN;
    
    /* Enable the transmitter holding register from the UART.  This interrupt will not be
     * seen by software.  It is handled by the hardware wrapper, and needed by the wrapper
     * in order to function. */
    sregs->uart_int_enable |= ETBEI;

    /* 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->dma_regs->rx_ctrl = 0;
    port->dma_regs->tx_ctrl = 0;
    
    port->dma_regs->status = port->dma_regs->status; //clean out all the pending int.
    
    port->dma_regs->rx_ctrl = DMA_MODE; // Set CE later
    port->dma_regs->rx_int_conf = RXNCIE | RXECIE | RXNRIE | RXCAIE | RXPCIE;
    port->dma_regs->rx_bdp = (uint32)MyVaToPhys(port->rx_bmd_start);

    port->dma_regs->tx_ctrl = DMA_MODE; // Set CE later
    port->dma_regs->tx_int_conf = TXNCIE | TXECIE | TXNRIE | TXCAIE;
    port->dma_regs->tx_bdp = (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;
    camry_port_t * port = &ports[channel];
    struct termios tios;

    if ( (channel >= max_camry_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));
    tios.c_iflag = IXON | IXOFF ;
    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);

    /* Install interrupt handlers */
    switch (channel)
    {
    case 0:
        NAInstallIsr (UARTA_INTERRUPT, tx_dma_int, (void*) port); 
        break;
    case 1 :
        NAInstallIsr (UARTB_INTERRUPT, tx_dma_int, (void*) port); 
        break;
    case 2 :
        NAInstallIsr (UARTC_INTERRUPT, tx_dma_int, (void*) port);
        break;
    case 3 :
        NAInstallIsr (UARTD_INTERRUPT, tx_dma_int, (void*) port); 
        break;
    default:
        break;
    }


    /* Start monitoring thread */
    if (!thread_started) {
        
	tx_thread_create(
			 &serl_monitor_thread,
			 "Camry 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    


    /* Enable DMA channels and let's get going */
    DISABLE_INTS;
    port->dma_regs->rx_ctrl &= ~DMA_CE;
    port->dma_regs->rx_ctrl |= (uint32)DMA_CE;
    RESTORE_INTS;
    /* Don't start until we have data */
    //port->dma_regs->tx_ctrl |= DMA_CE;

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

    return OK;
}

static dfrag *
dfalloc(camry_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(camry_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(camry_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(camry_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(camry_port_t *port)
{
    return ( (port->avail_dfrag > MIN_DFRAG) 
         && (port->avail_rxmsg > MIN_MSG) ) ;
}

static void 
recycle_bmd(camry_port_t *port, camry_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;
    camry_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, camry_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 ;
}

/*
 * 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;
    camry_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_camry_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 ( (port->dma_regs->tx_ctrl & STATE_MASK) != 0){
	        DISABLE_INTS;
	        port->dma_regs->tx_ctrl &= ~DMA_CE;
	        port->dma_regs->tx_ctrl |= (DMA_CE | DMA_CA); 
	        /* reset FIFO in case some thing is already in FIFO */
	        port->serl_regs->wrap_conf |= TXFLUSH;
	        port->serl_regs->wrap_conf &= ~TXFLUSH; 
	        RESTORE_INTS;
	    }
	    else
	    {
	        /* if the TX DMA is idle, reset FIFO in case some thing is already in FIFO */
	        DISABLE_INTS;
	        port->serl_regs->wrap_conf |= TXFLUSH; 
	        port->serl_regs->wrap_conf &= ~TXFLUSH; 
	        RESTORE_INTS;
	    }
	}

	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;
	while (port->serl_regs->force_reg & (uint32)FORCE_EN) {
	    /* If something else is forcing at the same time, we wait
	     * for it to finish */
	    tx_thread_sleep(1);
	}
	DISABLE_INTS;
	port->serl_regs->force_reg = (uint32)FORCE_EN | ((uint32)arg & 0xff);
    /* what if transmitter is disabled? */
    if (port->current_termios->c_iflag & IXOFF)
    {
        if (port->current_termios->c_cc[VSTART] == ((uint32)arg & 0xff))
        {
            if (!(port->serl_regs->wrap_conf & TXEN))
            {
                port->serl_regs->wrap_conf |= TXEN;  
                while (port->serl_regs->force_reg & (uint32)FORCE_EN)
                    ;
                port->serl_regs->wrap_conf &= ~TXEN;
            }
        }
    }
	RESTORE_INTS;
	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->modem_status;

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

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

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

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

    if (opt & S_RI)
        serl_status->mstat |= MS_RI;


	if (read_cts(port))
	    serl_status->mstat |= MS_CTS;

	opt = port->serl_regs->modem_ctrl;

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

	if (read_rts(port))
	    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->modem_ctrl;
	prev = 0;

	if (opt & DTR)
	    prev |= MS_DTR;
    
	if (read_rts(port))
	    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) {
		write_rts(port, 1);
	    } else {
		write_rts(port, 0);
	    }
	}

	port->serl_regs->modem_ctrl = 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->dma_regs->status & TX_FE) /*tx fifo is empty too*/ )
	{
	    *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;
#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;
    
    camry_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)
{
    camry_port_t * port = &ports[channel];
   
    if ( (channel >= max_camry_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));
#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)
{
    camry_port_t * port = &ports[channel];

    if ( (channel >= max_camry_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(camry_port_t *port) 
{
    camry_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_MASK) == DMA_MATCH) && 
	     ( (port->rx_bmd_out->status & (DMA_MATCH0 | DMA_MATCH1 | DMA_MATCH2 | DMA_MATCH3 | DMA_MATCH4)))
	     && (port->rx_bmd_out->length == 1))
    {
	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) ;
	len = next_rx->length;

	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 & DMA_MASK) == DMA_ERR) && (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 & DMA_MASK) == DMA_MATCH) && ((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;
    camry_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;
    camry_bmd_t *currbmd;
#ifdef PORTBUFFERING_ENABLED
    char *origbuffer = buffer;
#endif

    if ( (channel >= max_camry_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(camry_port_t *port, camry_bmd_t *currbmd)
{
    uint32 first = 0 ;
    uint32 len = currbmd->length ;
    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);
			}
		    }
		}
	    }
	}
    }
}


/* ---------------------------------------------------------------------- 
 *          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(camry_port_t *port)
{
    INTERRUPT_SAVE_AREA;
    camry_bmd_t *next_tx;
    camry_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->dma_regs->tx_ctrl & DMA_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;

	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;

    //if ( !(port->dma_regs->tx_ctrl & DMA_CE) ) {
    DISABLE_INTS;
    port->dma_regs->tx_ctrl &= ~DMA_CE;
    port->dma_regs->tx_ctrl |= (uint32)DMA_CE;
	RESTORE_INTS;
    //}
    return ret;    
}

/*
 * 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)
{
    camry_port_t * port = &ports[channel];
    ULONG wait_option;
    unsigned int ret;
    int written = 0;
    int notfirsttime = 0;
    INTERRUPT_SAVE_AREA;

    if ( (channel >= max_camry_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;
	write_rts(port, 1);
	RESTORE_INTS;

	// Wait (optional) pre-delay
	if ((port->current_termios->c_cc[VPREDELAY])
	    && (tx_buffer[channel].in == tx_buffer[channel].out) && 
		 (port->dma_regs->status & TX_FE) /*tx fifo is empty too*/ 
	    ) 
	{
	    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)
{
    camry_port_t *port = (camry_port_t *)handle;
    camry_bmd_t *currbmd = 0;
    uint32 tx_status, status = port->dma_regs->status;
     

    tx_status = status;
    /* Acknowledge interrupt */
    port->dma_regs->status |= status;
    
    if(status & RX_INT_MASK){
    if (status & RXCAIP) {
	/* 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
	 */

	port->dma_regs->rx_ctrl = INDEXEN;
	port->dma_regs->rx_ctrl = 0;

	status = port->dma_regs->status;

	/* Acknowledge interrupt again*/
	port->dma_regs->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->dma_regs->rx_ctrl &= ~DMA_CE;
	port->dma_regs->rx_ctrl |= (uint32)DMA_CE;
    
	return 0;
    }

    if (status & RXDMA_ERRORS & ~RXPCIP) {
	if (status & RXNRIP) {
	    /* 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 & RXECIP) {
	    assert(0);
	} 
    } else if (port->rx_dma_stalled) {
        port->rx_dma_stalled = 0;
        port->rx_dma_oflowon = 0;
    }

#ifdef DOTNETMF
    switch ( port->channel )
	{
	case 0:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM1);
		break;
	case 1:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM2);
		break;
	case 2:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM3);
		break;
	case 3:
        DIGI_EventSets(DIGI_EVENT_FLAG_RXISR_COM4);
		break;
    }
#endif

    currbmd = port->rx_bmd_in;

    while (currbmd->flags & DMA_FULL) {
        camry_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);
	    }
	}
    }
    } //end RX interrupt process RX_INT_MASK
    
    
    /* Determine if there's an error */
    if (tx_status & TXECIP) {
        /* Bad BMD or buffer pointer: Implies queuing code queued wrong */
        assert(0);
    }
    if (tx_status & TXNRIP) {
	/* Queue is finished because F was not set on the next BMD */

        port->dma_regs->tx_ctrl &= ~DMA_CE;
    }
    
    if (tx_status & TXCAIP) {
	/* 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
	 */
 
    port->dma_regs->tx_ctrl = INDEXEN; 
    port->dma_regs->tx_ctrl = 0; 
    
	assert( (port->dma_regs->tx_ctrl & DMA_INDEX) == 0);

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

    /* reset FIFO in case some thing is already in FIFO */
    port->serl_regs->wrap_conf |= TXFLUSH; 
    port->serl_regs->wrap_conf &= ~TXFLUSH; 
	        
	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 0
    if (tx_status & TXNCIP) 
#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 */
            assert(currbmd->length == txlengths[port->channel][bmdidx]); // No errors to get here

	    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_CAMRY_SERL_PORTS ; i++) {
	    camry_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 camryury
		 * 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->dma_regs->rx_ctrl &= ~DMA_CE;
		port->dma_regs->rx_ctrl |= (uint32)DMA_CE;
		RESTORE_INTS;
	    }

	    if (port->breakstatus == BRK_ACTIVE) {
		port->breaktime -= SERL_POLL_RATE;
		if (port->breaktime <= 0) {
		    DISABLE_INTS;
		    port->serl_regs->line_ctrl &= ~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->dma_regs->status & TX_FE) /*tx fifo is empty too*/ )
	    {
		if (port->breakstatus == BRK_REQUESTED) {
		    // Start a break
		    DISABLE_INTS;
		    port->serl_regs->line_ctrl |= 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)) && 
		 (read_rts(port)/*port->serl_regs->modem_ctrl & RTS*/))
		{
		    char dummy_buf[256];
		    unsigned int 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;
	}
	tx_thread_sleep(SERL_POLL_RATE); 
    }
}

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

/*
 * Function: max_camry_ports()
 *
 * Return: Returns the number of serial ports available on the sytem
 *
 */
static int 
max_camry_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)
{
    camry_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)
{
    camry_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)) )
{
    camry_port_t *port = (camry_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)) )
{
    camry_port_t *port = (camry_port_t *)arg;
    INTERRUPT_SAVE_AREA;

    if (port->current_termios->c_cflag & CRTSTOGGLE) { // May have been turned off
	DISABLE_INTS;  //Protect MCR
	write_rts(port, 0);
	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;
}
