/*
 *  Copyright (c) 1996-2007 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 * =======================================================================
 * FIM UART driver
 *
 * Edit Date/Ver   Edit Description
 * ==============  =======================================================
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

#include "netosIo.h"
#include "iop_serial.h"
#include "termios_int.h"
#include "fim_uart.h"
#include "sysClock.h"
#include "NA_ISR.h"
#include "bsp_iop.h"
#include "bsp_api.h"
#include "event_handler.h"
#include "ddi.h"

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

static iop_serl_port_t *iop_ports[IOP_MAX_UART_PORTS] = {NULL, NULL};

#ifdef NETOS_GNU_TOOLS
static unsigned char iop_rx_buf[NA_IOP_MAX_PICS][IOP_RX_BUF_SZ]  __attribute__ ((aligned(32)));
#else
#pragma alignvar (32)
static unsigned char iop_rx_buf[NA_IOP_MAX_PICS][IOP_RX_BUF_SZ];
#endif

static unsigned char iop_tx_buf[NA_IOP_MAX_PICS][IOP_TX_BUF_SZ];

#define MONITOR_STACK_SIZE 0x800
static TX_THREAD serl_monitor_thread;
static char *serl_monitor_stack = NULL;

static TX_EVENT_FLAGS_GROUP iop_serlevents;

#define IOP_SERL_READ_ISR     0x01
#define IOP_SERL_WRITE_ISR    0x02
#define IOP_SERL_RTS_CHANGE   0x04
#define IOP_SERL_CTS_CHANGE   0x08

#define IOP_SERL_ALL_EVENTS   (IOP_SERL_READ_ISR | IOP_SERL_WRITE_ISR | \
                               IOP_SERL_RTS_CHANGE | IOP_SERL_CTS_CHANGE)

#define IOP_DMA_BUF_SIZE   (IOP_RX_BUF_SZ / IOP_MAX_DMA_DESCRIPTORS)

#define SERL_POLL_RATE   NS_MILLISECONDS_TO_TICKS(40)

#define CT_CTRL 	0
#define CT_BS   	1
#define CT_TAB  	2
#define CT_NL   	3
#define CT_CR		4
#define CT_PRINT	5

extern char ct_ojump[];

/* function returns port structure pointer for valid serial channel */
static iop_serl_port_t * getPortInfo(int ch)
{
    if ((ch >= NA_IOP_PROCESSOR_PIC0) && (ch <= NA_IOP_PROCESSOR_PIC1))
    {
        if (ch < IOP_MAX_UART_PORTS)
        {
            return iop_ports[ch];
        }
    }
    
    return NULL;
}

/* function returns the calculated parity bit */
static unsigned char getParity(unsigned char data, unsigned char mask, int odd)
{
    unsigned char ones = 0; 

    mask = (mask >> 1) + 1;
    do
    {
        ones += (data & mask) ? 1 : 0;
        mask >>= 1;
    } while (mask);

    if (odd)
        return ((ones & 1)^1);
    else
        return (ones & 1);
}

/* function inserts the character in transmit data stream */
static int insert_char(iop_serl_port_t *port, unsigned char ch)
{
    short data = 1;

    /* wait for PIC to process any pending inserted character */
    while (*NA_IOHUB_EXP_getAddress(port->processor, 0) & IOP_TX_INSERT_CHAR_BUSY);
     
    if (port->tios.c_cflag & CSTOPB)
        data = (data << 1) | 0x1;

    if (port->tios.c_cflag & PARENB)
        data = (data << 1) | getParity(ch, port->mask, port->tios.c_cflag & PARODD);

    data = (data << port->numBits) | (ch & port->mask);

    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = data & 0xFF;
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = (data >> 8) & 0xFF;
    if (naIopInterruptProcessor(port->processor, IOP_INSERT_CHAR) != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;    
}

/* function updates software flow control on transmit side */
static void change_tx_flow(iop_serl_port_t *port)
{
    assert(port != NULL);

    if (port->tios.c_iflag & IXON)
    {
        if (port->status.estat & EV_OPALL)
        {
            /* disable transmit */
            *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~IOP_TX_ENABLE;
        }
        else
        {
            /* enable transmit */
            *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_TX_ENABLE;
        }
    }
}

/* function sends VSTART or VSTOP to control rx flow */
static int change_rx_flow(iop_serl_port_t *port)
{
    int ret = EXIT_SUCCESS;
    assert(port != NULL);

    if (port->tios.c_iflag & IXOFF)
    {
        if (port->status.estat & EV_IPALL)
        {
            /* send VSTOP */
            ret = insert_char(port, port->tios.c_cc[VSTOP]);
        }
        else
        {
            /* send VSTART */
            ret = insert_char(port, port->tios.c_cc[VSTART]);
        }
    }

    return ret;
}

/* transmit DMA callback, called when IOP driver completes DMA transfer */
static void fimDmaTxCb(NaIopProcessor_t processor, void * buffer, unsigned length, unsigned result, void * context)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    (void)processor;
    (void)buffer;

    assert(port != NULL);

    if (result != NA_IOP_DMA_SUCCESS)
    {
        if (result != NA_IOP_DMA_FLUSHED)
            port->info.tx_dma_error++;
        return;
    }
    else
    {
        if (port->totalBits > 8)
            port->counters.tbytes += length/2;
        else
            port->counters.tbytes += length;

        port->info.tx_out = (port->info.tx_out + length) & IOP_TX_BUF_MASK;

        if ((port->info.tx_out + 2) > IOP_TX_BUF_SZ)
        {
            /* we skipped the tx_in at this stage, so skip here too */
            port->info.tx_out = 0;
        }        
        
        tx_event_flags_set(&iop_serlevents, IOP_SERL_WRITE_ISR, TX_OR);                
    }
}

/* receive DMA callback, called when IOP driver receives data from FIM */
static void fimDmaRxCb(NaIopProcessor_t processor, void * buffer, unsigned length, unsigned result, void * context)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    (void)processor;

    assert(port != NULL);

    if (result != NA_IOP_DMA_SUCCESS)
    {
        if (result != NA_IOP_DMA_FLUSHED)
            port->info.rx_dma_error++;
        return;
    }
    else
    {
        if (buffer != NULL)
        {
            port->info.rx_len[port->info.rx_in_idx]    = length;
            port->info.rx_offset[port->info.rx_in_idx] = 0;
        }

        
        port->counters.rbytes += length/2;
    
        port->info.rx_in_idx = (port->info.rx_in_idx + 1) & IOP_RX_DESCR_MASK;

        if (port->tios.c_iflag & IXOFF)
        {
            if (((port->info.rx_in_idx + 2) & IOP_RX_DESCR_MASK) == port->info.rx_out_idx)
            {
                /* one or two more DMA descriptors are left to fill */
                port->status.estat |= EV_IPS;
                change_rx_flow(port); 
                serl_cb_event(port->channelIndx, IPS_EVENT);                 
            }
        }
        
        tx_event_flags_set(&iop_serlevents, IOP_SERL_READ_ISR, TX_OR);        
    }
}

/* this callback function is called when IOP driver receives interrupt from FIM */
static void fimIsrCb(NaIopProcessor_t processor, void * context, unsigned interruptCode)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    assert(port != NULL);

    /* handle all the interrupt here */
    switch (interruptCode)
    {
        case IOP_MATCH_CHAR1:
            /* handle character VSTART match here */
            if (port->tios.c_iflag & IXON)
                port->status.estat &= ~EV_OPS;
            change_tx_flow(port);
            break;

        case IOP_MATCH_CHAR2:
            /* handle character VSTOP match here */
            if (port->tios.c_iflag & IXON)
                port->status.estat |= EV_OPS;
            change_tx_flow(port);
            break;

        case IOP_RX_OVERFLOW:
            port->counters.noflow++;
            break;

        default:
            /* unknown error code */
            break;
    }

    /* clear the interrupt */
    *NA_IOHUB_CONTROL_getAddress(processor, 7) = interruptCode;    
}

/* function fills up DMA with provided data */
static int sendData(iop_serl_port_t *port, void *buffer, unsigned length)
{
    NaStatus ccode;
    int ret = EXIT_SUCCESS;

    assert(port != NULL);

    /* send the current buffer we have so far */
    ccode = naIopDmaTransmit(port->processor, buffer, length, fimDmaTxCb, 
                             (void *)port);
    if (ccode != NASTATUS_SUCCESS)
    {
        if (ccode == NASTATUS_IOP_QUEUE_IS_FULL)
            ret = EAGAIN;
        else
        {
            port->info.tx_dma_error++;    
            ret = EINVAL;
        } 
    }
    else
    {
        ret = EXIT_SUCCESS;
    }

    return ret;
} 

/* function adds parity, stop bits to the given buffer and initates the transmision */
static int frameTxData(iop_serl_port_t *port, char *buffer, int length, int *bytesWritten)
{
    struct termios *tp;
    int ret = EXIT_SUCCESS;
    unsigned char *startPtr, *curPtr, extraCharSent = 0, skip = 0;
    unsigned short data;
    char ch = 0;

    assert(port != NULL);
    
    tp = &port->tios;

    startPtr = &port->txBuf[port->info.tx_in];
    curPtr   = startPtr;
    *bytesWritten = 0;

    while (length > 0)
    {
        if ((((port->info.tx_in + 1) & IOP_TX_BUF_MASK) == port->info.tx_out) ||
            (((port->info.tx_in + 2) & IOP_TX_BUF_MASK) == port->info.tx_out))
        {
            break;
        }

        if ((port->info.tx_in + 2) >= IOP_TX_BUF_SZ)
        {
            /* don't have enough place to send 2 bytes */
            port->info.tx_in = 0;
            break;        
        }

        ch = *buffer;
        skip = 0;
        
        switch (ct_ojump[(int)ch])
        {
            /* Following special characters yet to be handled */
            case CT_BS:
            case CT_TAB:
            case CT_CR:
                break;
                
            case CT_NL:
                if ((port->tios.c_oflag & ONLCR) && (extraCharSent == 0))
                {
                    ch = '\r';
                    extraCharSent = 1;
                }
                else
                    extraCharSent = 0; /* forces to print NL */                   
                break;

            case CT_PRINT:
                break;
                
            case CT_CTRL:
                /* can we skip the unprintable character? */
                skip = 0;
                break;
                
            default:
            // all cases should be handled...
                assert(0);
        }
        
        if (!extraCharSent)
        {
            length--;
            buffer++;
            *bytesWritten += 1;
        }
        
        if (skip)
            continue;
            
        data = 1;
        if (tp->c_cflag & CSTOPB)
        {
            data = (data << 1) | 0x1;
        } 

        if (tp->c_cflag & PARENB)
        {
            data = (data << 1) | getParity(ch, port->mask, tp->c_cflag & PARODD);
        }

        data = (data << port->numBits) | (ch & port->mask);
        
        *curPtr++ = data & 0xFF;
        port->info.tx_in = (port->info.tx_in + 1) & IOP_TX_BUF_MASK;

        if (data > 0xFF)
        {
            *curPtr++ = (data >> 8) & 0xFF;
            port->info.tx_in = (port->info.tx_in + 1) & IOP_TX_BUF_MASK;
        }        
    }

    /* send rest of the data */
    if ((curPtr-startPtr) > 0)
        ret = sendData(port, (void *)startPtr, (unsigned)(curPtr - startPtr));
    else
        ret = EAGAIN;
    
    return ret;
}

/* function checks for the parity and frame errors and extract the data from 
   the received data stream */
static int parseData(iop_serl_port_t *port, char *dest, unsigned char *src, int length, int *bytesCopied)
{
    struct termios *tp;
    unsigned short data;
    int ret = EXIT_SUCCESS;

    assert(port != NULL);
    
    tp = &port->tios;

    *bytesCopied = 0;

    while (length > 1) /* 2 bytes per character */
    {
        if (port->totalBits < 8)
        {
            data = *src >> (8 - port->totalBits);
            src++;
        }
        else
        {
            data = *src++;
            data |= ((unsigned short)*src >> (16 - port->totalBits)) << 8;
        }

        /* ignore VSTART and VSTOP characters if s/w handshake is enabled */
        if (port->tios.c_iflag & IXOFF)
        {
            /* ignore, if it is matching character */
            if (((data & port->mask) == port->tios.c_cc[VSTART]) || 
                ((data & port->mask) == port->tios.c_cc[VSTOP])) 
            {
                src++;
                length -= 2;
                continue;
            }
        }

        *dest = data & port->mask;

        if (tp->c_cflag & PARENB)
        {
            if (((data >> port->numBits) & 0x1) != getParity(*dest, port->mask, tp->c_cflag & PARODD))
            {
                port->counters.nparity++;
                ret = EINVAL;
            }
        }

        if (tp->c_cflag & CSTOPB)
        {
            if (((data >> (port->totalBits - 2)) & 3) != 3)
            {
                port->counters.nframe++;
                ret = EINVAL;
            }
        } 
        else
        {
            if (!((data >> (port->totalBits - 1)) & 1))
            {
                port->counters.nframe++;
                ret = EINVAL;
            }
        }

        src++;
        dest++;
        *bytesCopied += 1;
        length -= 2;
    }

    return ret;
}

/* function extracts the data from DMA descriptors and puts the completed 
   descriptors back in the receive queue */
static int assembleRxData(iop_serl_port_t *port, char *buffer, int length, int *bytesRead)
{
    int ret = EXIT_SUCCESS;
    unsigned char *curPtr;
    int bytesCopied;
    NaStatus ccode;
    int l_len, l_offset;
    INTERRUPT_SAVE_AREA;

    assert(port != NULL);
    
    while ((length > 0) && (ret == EXIT_SUCCESS))
    {
        curPtr = &port->rxBuf[port->info.rx_out_idx*IOP_DMA_BUF_SIZE];
        l_len = (int)port->info.rx_len[port->info.rx_out_idx];
        l_offset = (int)port->info.rx_offset[port->info.rx_out_idx];

        if (l_len == 0)
        {
            if (*bytesRead == 0)
                ret = EAGAIN;
            break; // no more data to read
        }
                
        if (length >= l_len/2)
        {
            /* returns error only for the data error */
            ret = parseData(port, buffer, &curPtr[l_offset], l_len, &bytesCopied); 

            DISABLE_INTS;                        
            port->info.rx_len[port->info.rx_out_idx]    = 0;
            port->info.rx_offset[port->info.rx_out_idx] = 0;                

            // put the buffer back in DMA
            ccode = naIopDmaReceive(port->processor, curPtr, 
                                    IOP_DMA_BUF_SIZE, fimDmaRxCb,
                                    (void *)port);
            if (ccode != NASTATUS_SUCCESS)
            {
                port->info.rx_dma_error++;    
                ret = EINVAL;
            }
            else
            {
                port->info.rx_out_idx = (port->info.rx_out_idx + 1) & IOP_RX_DESCR_MASK;

                /* flow controlled? */
                if ((port->tios.c_iflag & IXOFF) && (port->status.estat & EV_IPS))
                {
                    port->status.estat &= ~EV_IPS;
                    change_rx_flow(port); 
                    serl_cb_event(port->channelIndx, IPS_EVENT);                     
                }
            }
            RESTORE_INTS;
        }
        else
        {
            l_len = length*2;
            ret = parseData(port, buffer, &curPtr[l_offset], l_len, &bytesCopied); 
            
            DISABLE_INTS;                        
            port->info.rx_len[port->info.rx_out_idx]    -= (unsigned)l_len;
            port->info.rx_offset[port->info.rx_out_idx] += (unsigned)l_len;                
            RESTORE_INTS;
        }

        length      -= bytesCopied;
        buffer      += bytesCopied;
        *bytesRead  += bytesCopied;        
        port->info.rdbytes += bytesCopied;                    
    }

    return ret;
}

/* function calculates the clock divisor  and interrupts the FIM to implement
   the change. function also sets the character gap */ 
static int setBaudrate(iop_serl_port_t *port, int baud)
{
    NaStatus ccode;
    unsigned i, timerDiv, preScale, clocksPerBit, charGap;
    
    /* Calculate timer divider */
    clocksPerBit = NAgetPllOutputClkFreq()/baud;
    timerDiv     = clocksPerBit/256 + 1;

    /* Must round up to next power of 2 */
    for (i = 1; i <= 8; i++)
    {
        if (timerDiv < (unsigned)(1 << i))
        {
            timerDiv = 1 << i;
            preScale = i - 1;
            break;
        }
    }
    if (i > 8)
        return EINVAL;
        
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = (clocksPerBit / timerDiv) + 1;
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_TIME);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    /* Setup Character gap timer */
    charGap = (2 * clocksPerBit * port->totalBits) / 30;   /* Char gap to 2 character times */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = (charGap & 0xff) + 1;         /* charGap[7:0]  + 1 */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = ((charGap>>8) & 0xff) + 1;   /* charGap[15:8] + 1 */
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_CHAR_GAP);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = preScale;
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_TIMER_PRESCALE);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;
}

/* function sets the matching characters for VSTART (XON) and VSTOP (XOFF) */
static int setupSwFlowCtrl(iop_serl_port_t *port, int enable, struct termios *tios)
{
    unsigned short match = 1;
    unsigned short mask;


    if (enable)
    {
        if (tios->c_cflag & CSTOPB)
            match = (match << 1) | 1;
        
        if (tios->c_cflag & PARENB)
            match = (match << 1) | getParity(tios->c_cc[VSTART], port->mask, tios->c_cflag & PARODD);
        
        match = (match << port->numBits) | (tios->c_cc[VSTART] & port->mask);            
        mask  =  match | port->mask;

        if (port->totalBits < 8)
        {
            match = match << (8 - port->totalBits);
            mask  = mask << (8 - port->totalBits);
        }
                    
        /* set match character */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = match & 0xFF; /* Match char 1 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0; /* Match char 1 MSB */

        match = 1;
        if (tios->c_cflag & CSTOPB)
            match = (match << 1) | 1;
        
        if (tios->c_cflag & PARENB)
            match = (match << 1) | getParity(tios->c_cc[VSTOP], port->mask, tios->c_cflag & PARODD);
        
        match = (match << port->numBits) | (tios->c_cc[VSTOP] & port->mask);            

        if (port->totalBits < 8)
            match = match << (8 - port->totalBits);
                    
        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = match & 0xFF; /* Match char 2 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0; /* Match char 2 MSB */

        *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = mask & 0xFF; /* Match Mask LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0; /* Match Mask MSB */
    }
    else
    {
        /* disable match character */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 0xFF; /* Match char 1 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0xFF; /* Match char 1 MSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0xFF; /* Match char 2 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0xFF; /* Match char 2 MSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = 0x00; /* Match Mask LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0x00; /* Match Mask MSB */
    }
    
    if (naIopInterruptProcessor(port->processor, IOP_SETUP_MATCH_CHAR) != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;
}

/*
 * Function: iop_serlioctl()
 *
 * Description  This function sets the serial parameters like baudrate, flow 
 *              control, stop bits, character gap
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              EXIT_SUCCESS for success
 */
int iop_serlioctl(int channel, int request, char *arg)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS;
    struct termios *tiosp;
    int force_change = 0;
    INTERRUPT_SAVE_AREA;

    if ((port = getPortInfo(channel)) == NULL)
    {
        ret = EBADF;
        goto done_ioctl;
    }

    switch (request)
    {
        case TCSETA:
            tiosp = (struct termios *)arg;
            assert(tiosp != NULL);
            
            /* need to verify the input here with supported functionality */

            /* set number of bits */
            if ((port->tios.c_cflag & (CSIZE | PARENB | CSTOPB)) != 
                        (tiosp->c_cflag & (CSIZE | PARENB | CSTOPB)))
            {
                port->numBits = 5;

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

                port->totalBits = port->numBits + 1; /* stop bit */
                port->mask      = (1 << port->numBits) - 1;
                if (tiosp->c_cflag & PARENB)
                    port->totalBits++;
                if (tiosp->c_cflag & CSTOPB)
                    port->totalBits++;

                /* copy this value to control0 and interrupt the FIM */
                *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = port->totalBits;
                ccode = naIopInterruptProcessor(port->processor, IOP_SET_BITS_IN_CHAR);
                if (ccode != NASTATUS_SUCCESS)
                {
                    ret = EINVAL;
                    goto done_ioctl;
                }
                force_change = 1;                
            }

            /* set hardware handshaking or RS485 RTS toggle mode */            
            if (((port->tios.c_cflag & CRTSCTS) != (tiosp->c_cflag & CRTSCTS)) ||
        ((port->tios.c_cflag & CRTSTOGGLE) != (tiosp->c_cflag & CRTSTOGGLE)))
            {
                /*  First setup the bit positions for GPIO based on processor selected */
                *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0;   
                *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0;

                if (port->processor == NA_IOP_PROCESSOR_PIC1)
                {
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM1_UART_TXD; 
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM1_UART_RXD; 
                    if ((tiosp->c_cflag & CRTS) || (tiosp->c_cflag & CRTSTOGGLE)) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 1 << BSP_IOP_FIM1_UART_RTS;
                    if (tiosp->c_cflag & CCTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 1 << BSP_IOP_FIM1_UART_CTS;
                }
                else
                {
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM0_UART_TXD; 
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM0_UART_RXD; 
                    if ((tiosp->c_cflag & CRTS) || (tiosp->c_cflag & CRTSTOGGLE)) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 1 << BSP_IOP_FIM0_UART_RTS;
                    if (tiosp->c_cflag & CCTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 1 << BSP_IOP_FIM0_UART_CTS;
                }

                ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_POSITION);
                if (ccode != NASTATUS_SUCCESS)
                {
                    ret = EINVAL;
                    goto done_ioctl;
                }

                if (tiosp->c_cflag & CRTSCTS)
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_EN_HW_FLOW_CNTRL;
                else
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~IOP_EN_HW_FLOW_CNTRL;
                
                if (tiosp->c_cflag & CRTSTOGGLE)
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_EN_RTS_TOGGLE;
                else
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~IOP_EN_RTS_TOGGLE;		
            }

            /* setup software handshake */
            if (!(tiosp->c_cflag & CRTSCTS) && !(tiosp->c_cflag & CRTSTOGGLE))
            {
                if (((port->tios.c_iflag & IXON) != (tiosp->c_iflag & IXON)) 
                    || ((tiosp->c_iflag & IXON) && force_change))
                {
                    if ((ret = setupSwFlowCtrl(port, (tiosp->c_iflag & IXON), tiosp)) != EXIT_SUCCESS)
                    {
                        ret = EINVAL;
                        goto done_ioctl;
                    }

                    if (tiosp->c_iflag & IXON)
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= (IOP_EN_MATCH_CHAR1 | IOP_EN_MATCH_CHAR2);
                    else
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~(IOP_EN_MATCH_CHAR1 | IOP_EN_MATCH_CHAR2);
                }
            }

            if (port->tios.baud != tiosp->baud)
            {
                if ((ret = setBaudrate(port, tiosp->baud)) != EXIT_SUCCESS)
                    goto done_ioctl;
            }

            /* set the rest */
            memcpy(&port->tios, tiosp, sizeof(port->tios));
            break;

        case TCGETA:
            tiosp = (struct termios *)arg;
            assert(tiosp != NULL);
            memcpy(tiosp, &port->tios, sizeof(port->tios));
            break;

        case TCDRAIN:
            /* wait for all the sent data to transmit */
            while (port->info.tx_dma_error == 0)
            {
                if (port->info.tx_out != port->info.tx_in)
                    tx_thread_sleep(1);
                else
                    break;
            }
            
            DISABLE_INTS;
            port->info.tx_out = port->info.tx_in = 0;
            RESTORE_INTS;                
            
            break;
        
        case TCFLUSH:
        {
            int i, queue_selector = (enum flush_opts)arg;

            if (queue_selector & ~TCIOFLUSH) 
            {
                ret = EIO;
                goto done_ioctl;
            }
            
            if (queue_selector & TCIFLUSH) 
            {   /* Throw away input */
                /* flush the receive DMA queue */
                ccode = naIopDmaFlushQueue(port->processor, FALSE);
                if (ccode != NASTATUS_SUCCESS)
                {
                    ret = EINVAL;
                    goto done_ioctl;
                }

                /* setup initial receive DMA buffer descriptors */
                for (i = 0; i < IOP_MAX_DMA_DESCRIPTORS; i++)
                {
                    ccode = naIopDmaReceive(port->processor, 
                                            (void *)&port->rxBuf[i*IOP_DMA_BUF_SIZE], 
                                            IOP_DMA_BUF_SIZE, fimDmaRxCb, (void *)port);
                    if (ccode != NASTATUS_SUCCESS)
                    {
                        ret = EINVAL;
                        goto done_ioctl;
                    }
                }
        
                /* reset all local rx house keeping data */
                DISABLE_INTS;
                port->info.rx_in_idx = port->info.rx_out_idx = 0;
                memset(port->info.rx_len, 0, sizeof port->info.rx_len);
                memset(port->info.rx_offset, 0, sizeof port->info.rx_offset);
                port->counters.rbytes = port->info.rdbytes;
                RESTORE_INTS;                

                /* flow controlled? */
                if ((port->tios.c_iflag & IXOFF) && (port->status.estat & EV_IPS))
                {
                    DISABLE_INTS;
                    port->status.estat &= ~EV_IPS;
                    RESTORE_INTS;                
                    change_rx_flow(port);
                    serl_cb_event(port->channelIndx, IPS_EVENT);                     
                }
            }

            if (queue_selector & TCOFLUSH) 
            { /* Throw away output */
                ccode = naIopDmaFlushQueue(port->processor, TRUE);
                if (ccode != NASTATUS_SUCCESS)
                {
                    ret = EINVAL;
                    goto done_ioctl;
                }
        
                DISABLE_INTS;
                port->info.tx_in = port->info.tx_out = 0;
                RESTORE_INTS;                
            }
        }            
        break;

        case TCFLOW: 
        {
            unsigned short estat_pre = port->status.estat;
            enum flow_opts action = (enum flow_opts)arg;

            DISABLE_INTS;
            switch (action) 
            {
                case TCOOFF:
                    if (port->status.estat & EV_OPU)
                    { 
                        port->status.estat &= ~EV_OPU;
                        change_tx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_OPU )
                        serl_cb_event(port->channelIndx, OPU_EVENT);
                    
                    break;
    
                case TCOON:
                    if (!(port->status.estat & EV_OPU))
                    {
                        port->status.estat |= EV_OPU;
                        change_tx_flow(port);
                    }
                
                    if ( (estat_pre ^ port->status.estat) & EV_OPU )
                        serl_cb_event(port->channelIndx, OPU_EVENT);
                    
                    break;
    
                case TCIOFF:
                    if (port->status.estat & EV_IPU) 
                    {
                        port->status.estat &= ~EV_IPU;
                        change_rx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_IPU )
                        serl_cb_event(port->channelIndx, IPU_EVENT);
                    
                    break;

                case TCION:
                    if (!(port->status.estat & EV_IPU))
                    {
                        port->status.estat |= EV_IPU;
                        change_rx_flow(port);
                    }

                    if ( (estat_pre ^ port->status.estat) & EV_IPU )
                        serl_cb_event(port->channelIndx, IPU_EVENT);

                    break;

                case TCIAOFF: // pattern alarm exit flow
                    if (port->status.estat & EV_IPA) 
                    {
                        port->status.estat &= ~EV_IPA;
                        change_rx_flow(port);
                    }
                    break;

                case TCOSOFF:
                    if (port->status.estat & EV_OPS)
                    {
                        port->status.estat &= ~EV_OPS;
                        change_tx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_OPS )
                        serl_cb_event(port->channelIndx, OPS_EVENT);
                    
                    break;

                case TCOSON:
                    if (!(port->status.estat & EV_OPS))
                    {
                        port->status.estat |= EV_OPS;
                        change_tx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_OPS )
                        serl_cb_event(port->channelIndx, OPS_EVENT);
                    
                    break;
    
                case TCISOFF:
                    if (port->status.estat & EV_IPS) 
                    {
                        port->status.estat &= ~EV_IPS;
                        change_rx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_IPS )
                        serl_cb_event(port->channelIndx, IPS_EVENT);
                    
                    break;
    
                case TCISON:
                    if (!(port->status.estat & EV_IPS))
                    {
                        port->status.estat |= EV_IPS;
                        change_rx_flow(port);
                    }
                    
                    if ( (estat_pre ^ port->status.estat) & EV_IPS )
                        serl_cb_event(port->channelIndx, IPS_EVENT);
                    
                    break;

                case TCOXOFF:
                    if (port->status.estat & EV_OPX)
                    {
                        port->status.estat &= ~EV_OPX;
                        change_tx_flow(port);
                    }

                    if ( (estat_pre ^ port->status.estat) & EV_OPX )
                        serl_cb_event(port->channelIndx, OPX_EVENT);

                    break;
    
                case TCOXON:
                    if (!(port->status.estat & EV_OPX))
                    {
                        port->status.estat |= EV_OPX;
                        change_tx_flow(port);
                    }


                    if ( (estat_pre ^ port->status.estat) & EV_OPX )
                        serl_cb_event(port->channelIndx, OPX_EVENT);

                    break;

                case TCIXOFF:
                case TCIXON:
                    //TODO - all the extra flow states
    
                default:
                    ret = EINVAL;
                    break;
            }
            RESTORE_INTS;                
        }
        break;

        case TCSENDNOW:
            ret = insert_char(port, (unsigned char)((int)arg));
            break;

        case TCGETSTATS:
        {
            struct serial_status_t *serl_status = (struct serial_status_t *)arg;

            serl_status->mstat = 0;

            /* FIM supports max 4-wire, so we can get only CTS and RTS line */
            if (*NA_IOHUB_EXP_getAddress(port->processor, 0) & IOP_CTS_STATE)
                serl_status->mstat |= MS_CTS;

            if (*NA_IOHUB_EXP_getAddress(port->processor, 0) & IOP_RTS_STATE)
                serl_status->mstat |= MS_RTS;

            serl_status->estat = port->status.estat;
        }   
        break;
                                        
        case TCGETBUFFERS:
        {
            struct serial_buffer_t *bufs = (struct serial_buffer_t *)arg;
        
            bufs->rxbuf = (port->counters.rbytes - port->info.rdbytes)
                             & IOP_RX_BUF_MASK;
            bufs->txbuf = (port->info.tx_in - port->info.tx_out) & IOP_TX_BUF_MASK;
        }
        break;
        
        case TCGETCOUNTERS:
        {
            struct serial_cntr_t *cntrs = (struct serial_cntr_t *)arg;
            
            memcpy(cntrs, &port->counters, sizeof(struct serial_cntr_t));
        }
        break;
        
        case TCGETSIGCHANGE:
        {
            struct sigchange_stats_t *sigchange = (struct sigchange_stats_t *)arg;
            
            memcpy(sigchange, &port->sigchange, sizeof(struct sigchange_stats_t));
        }
        break;

        case TCTXDONE:
            if ( (port->info.tx_in != port->info.tx_out) || 
                 (*NA_IOHUB_IFS_getAddress(port->processor) & NA_IOHUB_IFS_TX_FIFO_FULL) 
                || (*NA_IOHUB_EXP_getAddress(port->processor, 0) & IOP_TX_ACTIVE))
            {
                *(int *)arg = 0;
            } 
            else 
                *(int *)arg = 1;

            break;

        case ISOPEN:
            *(int *)arg = port->opened;
            break;
        
        default:
            break;
    }

    done_ioctl:
        return ret;
}

/* This function should be called form iop_serlopen and not from iop_serlinit.
 * function does the following 
 * 1. Loads the uart program to FIM
 * 2. Registers ISR callback function which is called when ARM receives
 *    interrupt from FIM
 * 3. Sets up GPIO for TX, RX line
 * 4. Configures the baudrate, flow control, bits per char, stop bits
 */
static int setupFim(iop_serl_port_t *port)
{
    NaStatus ccode;
    int ret = EXIT_SUCCESS;
    struct termios tios;

    /*
     * Stop the processor so we can download its program.
     */
    ccode = naIopProcessorStopAndReset(port->processor);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /*
     * Download the code to the FIM.
     */
    ccode = naIopProcessorDownloadPicProgram(port->processor, &naUartProgram);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /*
     * Register ISR callback routine.
     */
    ccode = naIopRegisterIsrCallback(port->processor, fimIsrCb, (void *)port);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* Disable everything until get going */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) = 0;

    /*
     * Start the FIM
     */
    ccode = naIopProcessorStartAtZero(port->processor);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

     tx_thread_sleep(2);  /* Let it get going */

    /*---------------------*/
    /* Setup PIC parameter */
    /*---------------------*/
    /*  First setup the bit positions for GPIO */
    if (port->processor == NA_IOP_PROCESSOR_PIC1)
    {
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM1_UART_TXD; 
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM1_UART_RXD; 
        if (port->tios.c_cflag & CRTSCTS)
        {
            *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = BSP_IOP_FIM1_UART_RTS;
            *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = BSP_IOP_FIM1_UART_CTS;
        } 
    }
    else
    {
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM0_UART_TXD; 
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM0_UART_RXD; 
        if (port->tios.c_cflag & CRTSCTS)
        {
            *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = BSP_IOP_FIM0_UART_RTS;
            *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = BSP_IOP_FIM0_UART_CTS;
        }
    }

    /* clear RTS and CTS bit position if not required */
    if (!(port->tios.c_cflag & CRTSCTS))
    {    
        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0;
        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0;
    }

    ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_POSITION);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* disable match character */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 0xFF; /* Match char 1 LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0xFF; /* Match char 1 MSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0xFF; /* Match char 2 LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0xFF; /* Match char 2 MSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = 0x00; /* Match Mask LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0x00; /* Match Mask MSB */
    ccode = naIopInterruptProcessor(port->processor, IOP_SETUP_MATCH_CHAR);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* set termios config values */
    memcpy(&tios, &port->tios, sizeof(tios));
    /* we need this for all changes to take effect */
    memset(&port->tios, 0, sizeof(tios)); 

    ret = iop_serlioctl((int)port->processor, TCSETA, (char *)&tios);

    done_setup:
    return ret;
}

/* 
 * 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;
    ULONG actual_flags = 0;        
    int i;

    while (1) 
    {
        for (i = 0 ; i < NA_IOP_MAX_PICS ; i++) 
        {
            iop_serl_port_t *port = getPortInfo(i);
            unsigned char mprev;
            unsigned char mdiff;
            unsigned char mflow;

            if ((port == NULL) || !port->opened)
                continue;

            mprev = port->status.mstat;

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

            mdiff = mprev ^ status.mstat;

            if (mdiff & MS_CTS) 
            {
                port->sigchange.cts_counter++;
                serl_cb_event(port->channelIndx, CTS_DELTA);
            }
        
            if (mdiff & MS_RTS) 
            {
                port->sigchange.rts_counter++;
                serl_cb_event(port->channelIndx, RTS_DELTA);
            }

            //Flow control for incoming sigs other than CTS
            mflow = (port->tios.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->channelIndx, 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->channelIndx, OPH_EVENT);
                }
            }

            if (actual_flags & IOP_SERL_READ_ISR) 
            {
                if (port->counters.rbytes != port->info.rdbytes)
                {
                    serl_cb_event(port->channelIndx, RX_DATA);

#if NETWORK_STACK == TRECK
                    /* notify read select if there is data to read */
                    if (port->checkMask & NA_SELECT_READ)
                        tfDeviceSelectNotify(NA_SELECT_READ, port->select_context);
#endif
                }
            }
                        
#if NETWORK_STACK == TRECK
            if (port->checkMask & NA_SELECT_WRITE)
            {
                /* notify write select if there is a enough space to write */
                if (((port->info.tx_in - port->info.tx_out) & IOP_TX_BUF_MASK)
                    < (IOP_TX_BUF_SZ / 2) ) 
                {
                    tfDeviceSelectNotify(NA_SELECT_WRITE, port->select_context);
                }
            }
#endif

            //Note: only serl_monitor should change mstat right?
            port->status.mstat = status.mstat;
        }
        
        actual_flags = 0;
        // Note: used one event for both the FIMs for now... 
        tx_event_flags_get(&iop_serlevents, IOP_SERL_ALL_EVENTS, 
                    TX_OR_CLEAR, &actual_flags, SERL_POLL_RATE);
    }
}

/*
 *  Function: iop_serlinit
 *
 *  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, based on which FIM
 *                   to use
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int iop_serlinit(int channel)
{
    iop_serl_port_t *port = NULL;
    int ret = EXIT_SUCCESS;    
    
    if ((channel < NA_IOP_PROCESSOR_PIC0) || (channel > NA_IOP_PROCESSOR_PIC1))
    {
        ret = EINVAL;
        goto done_init;
    }

    port = (iop_serl_port_t *) malloc(sizeof(iop_serl_port_t));
    if (port == NULL)
    {
        ret = EINVAL;
        goto done_init;
    }

    memset(port, 0, sizeof(iop_serl_port_t));

    iop_ports[channel] = port;
    port->processor = (NaIopProcessor_t)channel;
    port->channelIndx = IOP_MAX_REGULAR_PORTS + channel;
    
    port->tios.c_iflag = IXON | IXOFF ;
    port->tios.c_cflag = CS8 | CREAD ;
    port->tios.c_cc[VSTART] = XON;
    port->tios.c_cc[VSTOP] = XOFF;
    port->tios.baud = IOP_DEFAULT_BAUDRATE ;

    port->txBuf = iop_tx_buf[channel];
    port->rxBuf = iop_rx_buf[channel];

    memset(port->txBuf, 0, IOP_TX_BUF_SZ);
    memset(port->rxBuf, 0, IOP_RX_BUF_SZ);

    tx_mutex_create(&port->txMutex, "IOP Serial Tx Mutex", TX_INHERIT);
    tx_mutex_create(&port->rxMutex, "IOP Serial Rx Mutex", TX_INHERIT);
    
    if (serl_monitor_stack == NULL)
    {
        tx_event_flags_create(&iop_serlevents, "IOP Serial Events");

        serl_monitor_stack = (char *)malloc(MONITOR_STACK_SIZE);

        tx_thread_create(&serl_monitor_thread,
                         "IOP Serial Monitor",
                         (VOID (*)(ULONG)) serl_monitor,
                         0,     //dummy
                         serl_monitor_stack,
                         MONITOR_STACK_SIZE,    // stack size
                         15,    // arbitrary priority
                         15,    // arbitrary threshold
                         2,     // timeslice
                         TX_AUTO_START );        
    }
    
    done_init:
    if ((ret != EXIT_SUCCESS) && (port != NULL))
    {
        free(port);
    }

    return ret;            
}


/*
 *  Function: iop_serlopen()
 *
 *  Description: open a new session on a channel
 *
 *  called by: open (), creat () in DDI
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *      flags        a bit field formed by orring one or more
 *                   of O_RDONLY, O_WRONLY, O_RDWR, O_NONBLOCK, O_DMA
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int iop_serlopen(int channel, unsigned flags)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int i, ret = EXIT_SUCCESS; 
     
    if ((port = getPortInfo(channel)) == NULL)
    {
        ret = EINVAL;
        goto done_open;
    }

    if (port->opened)  /* no need to open if its already open */
    {
        ret = EBUSY;
        goto done_open;
    }

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

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

    /* clear all the info */
    memset(&port->info, 0, sizeof(port->info));
    port->info.rdbytes = port->counters.rbytes;

    if ((ret = setupFim(port)) != EXIT_SUCCESS)
        goto done_open;

    /* flush the transmit DMA queue */
    ccode = naIopDmaFlushQueue(port->processor, TRUE);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_open;
    }

    if (!(flags & O_NOFLUSH))
    {
        /* flush the receive DMA queue */
        ccode = naIopDmaFlushQueue(port->processor, FALSE);
        if (ccode != NASTATUS_SUCCESS)
        {
            ret = EINVAL;
            goto done_open;
        }
    }

    /* setup initial receive DMA buffer descriptors */
    for (i = 0; i < IOP_MAX_DMA_DESCRIPTORS; i++)
    {
        ccode = naIopDmaReceive(port->processor, 
                                (void *)&port->rxBuf[i*IOP_DMA_BUF_SIZE], 
                                IOP_DMA_BUF_SIZE, fimDmaRxCb, (void *)port);
        if (ccode != NASTATUS_SUCCESS)
        {
            ret = EINVAL;
            goto done_open;
        }
    }

    /* all setup, tell PIC init is complete and we are ready to roll */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_INIT_COMPLETE | IOP_TX_ENABLE;

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

    done_open:
        return ret;
}


/*
 *  Function: iop_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
 */
int iop_serlclose(int channel)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
     
    if ((port = getPortInfo(channel)) == NULL)
        return EINVAL;

    /* wait for all the sent data to transmit */
    while (port->info.tx_dma_error == 0)
    {
        if (port->info.tx_out != port->info.tx_in)
            tx_thread_sleep(1);
        else
            break;
    }

    ccode = naIopUninstallIsr(port->processor);
    if (ccode != NASTATUS_SUCCESS)
        ret = EINVAL;

    /*
     * Stop the processor and reset
     */
    ccode = naIopProcessorStopAndReset(port->processor);
    if (ccode != NASTATUS_SUCCESS)
        ret = EINVAL;

    port->opened  = 0;

    serl_cb_event(port->channelIndx, CLOSE_EVENT);

    return ret;   
}


/*
 * Function: iop_serlread()
 *
 * Description: read data from FIM
 *
 * Called by: DDI read
 *
 * Input : channel - 0 for FIM0, 1 for FIM1
 *         buffer - data to be stored
 *         length - size of data buffer
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int iop_serlread(int channel, char * buffer, int size, int *bytesRead)
{
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
    unsigned wait_option;
     
    if ((port = getPortInfo(channel)) == NULL)
        return EBADF;

    //Obtain tx_semaphore to protect read synchronization
    wait_option = (port->mode & O_NONBLOCK) ? TX_NO_WAIT : TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->rxMutex, wait_option);
    
    if (ret == TX_NOT_AVAILABLE)
        return EAGAIN;
    else if (ret != TX_SUCCESS)
        return EINVAL;

    *bytesRead = 0;
    do
    {
        ret = assembleRxData(port, buffer, size, bytesRead);
        if (ret == EAGAIN)
        {
            if (wait_option == TX_WAIT_FOREVER)
                tx_thread_sleep(1);
            else
                break;
        }
    }while (ret == EAGAIN);

    tx_mutex_put(&port->rxMutex);
    return ret;
}

/*
 * Function: iop_serlwrite()
 *
 * Description: write data to FIM
 *
 * Called by: DDI write
 *
 * Input : channel - 0 for FIM0, 1 for FIM1
 *         buffer - data to be sent
 *         length - size of data
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         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 iop_serlwrite(int channel, char * buffer, int length, int *bytesWritten)
{
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
    unsigned wait_option;
    int bytes_sent = 0;
     
    if ((port = getPortInfo(channel)) == NULL)
        return EBADF;

    /* obtain tx_semaphore to protect write synchronization */
    wait_option = (port->mode & O_NONBLOCK) ? TX_NO_WAIT : TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->txMutex, wait_option);

    *bytesWritten = 0;

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

    /* software flow controlled? */
    while ((port->tios.c_iflag & IXON) && (port->status.estat & EV_OPS))
    {
        if (wait_option == TX_WAIT_FOREVER)
            tx_thread_sleep(1);
        else
        {
            tx_mutex_put(&port->txMutex);
            return EAGAIN;
        }
    }

    while (length > 0)
    {
        ret = frameTxData(port, buffer, length, &bytes_sent);
        if (wait_option == TX_WAIT_FOREVER)
        {
            if ((ret == EXIT_SUCCESS) || (ret == EAGAIN))
            {
                if (ret == EXIT_SUCCESS)
                {
                    length -= bytes_sent;
                    *bytesWritten = (*bytesWritten) + bytes_sent;
                    buffer += bytes_sent;
                }

                if (length > 0)
                {
                    tx_thread_sleep(1);
                    continue;
                }
            }
        }
        else
        {
            if (ret == EXIT_SUCCESS)
                *bytesWritten += bytes_sent;
        }
        break;
    }

    tx_mutex_put(&port->txMutex);
    return ret;
}

#if NETWORK_STACK == TRECK
/*
 * Function: iop_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 iop_serlpoll(int channel, int insert_remove, int *checkMask, 
                   ulong context)
{
    int ret = 0;
    iop_serl_port_t *port; 

    if ((port = getPortInfo(channel)) == NULL)
        return EINVAL;

    if (!port->opened)  /* port not opened yet */
        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->counters.rbytes - port->info.rdbytes) & IOP_RX_BUF_MASK))
            ret |= NA_SELECT_READ; 
    if ((*checkMask & NA_SELECT_WRITE) 
        && (((port->info.tx_in - port->info.tx_out) & IOP_TX_BUF_MASK)
                < (IOP_TX_BUF_SZ / 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 */




