/*****************************************************************************
* Copyright (c) 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
*
* shared_serial.c - This file contains the routines necessary to
* implement sharing of serial ports.  This code was derived from
* shared_serial.cc which was is the ps3 CVS module.  The ps3 code used
* a C++ class to interface to the streams architecture, in NDS this 
* code has been changed to use a DDI interface and interface to the
* serial driver using the termios/DDI serial API.
*****************************************************************************/


#ifdef SHARING_ENABLED

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "Npttypes.h"
#include "tx_api.h"
#include "ddi.h"
#include "trace.h"
#include "termios.h"
#include "termios_int.h"
#include "netosIo.h"
#include "assert.h"
#include "sockapi.h"
#include "ind_io.h"
#include "shared_serial.h"
#include "devices.h"
#include "serlDev.h"
#include "event_handler.h"

#ifdef NET_OS_SPECIFIC
#include "minmax.h"
#endif


static unsigned int num_open_children=0;
SharedSerialParent *shared_serial_parent_array[SHARED_PARENT_CNT];
static SharedSerialChild  *shared_serial_child_array[NUM_SHARED_SERIAL_PORTS];

/* Used to protect the list of children waiting on select */
static int open_child_port(int port);
static int open_parent_port(SharedSerialParent *parent);
static int open_for_config(int channel);
static SharedSerialParent *get_parent_ptr(int channel);
static SharedSerialChild *get_child_ptr(int child_index);
static void port_listener_thread(unsigned long arg);
static int buf_data_avail(SharedSerialParent *parent, SharedSerialChild *child);
static int buf_space_avail(SharedSerialParent *parent, 
		           SharedSerialChild *child);
static void update_timeout(SharedSerialParent *parent);
static int update_parent_buffer(int channel);
static int is_port_writeable(int channel);


extern deviceInfo majorTable[];

#define SERIAL_READ_POLL_TIME 10
#define SERIAL_WRITE_POLL_TIME 10

extern void shared_serial_register_settings_listener(void);

int shared_serial_initialized=0;

int total_serial_read=0;

/*
 * shared_serial_epoch:
 *      Initialize shared serial shim driver, this must be called after 
 *      DDISecondLevelInitialize in master_epoch().
 */
void shared_serial_epoch(void)
{
    int channel, tbl_index;

    /* Register a listener callback for settings changes */
    shared_serial_register_settings_listener();

    for (channel=0; channel < max_serial_ports(); channel++)
    {
        /*
         * Initialize the shared serial settings for each port 
         */
        shared_serial_parent_array[channel] = 
                                   malloc(sizeof(SharedSerialParent));

        if (shared_serial_parent_array[channel] == (SharedSerialParent *)NULL)
        {
            return;
        }

        memset(shared_serial_parent_array[channel], 0,
               sizeof(SharedSerialParent));

        memset(shared_serial_child_array, 0,
               sizeof shared_serial_child_array);
    

        port_sharing_settings_get(channel, 
			&shared_serial_parent_array[channel]->config);

        /*
         * For ports which are shared replace the major number in the
         * device_names table with the shared serial major.
         */
        if (shared_serial_parent_array[channel]->config.max_clients > 1)
        {
            /* Find entry in table */
            if ((tbl_index = portno_to_dtable(channel)) < 0)
            {
                assert(0);
                return;
            }

            /*
             * Save the old major so we know which serial driver to call later
             * and also to revert back if the port is no longer shared. 
             */
            shared_serial_parent_array[channel]->major = 
                                               device_names[tbl_index].major; 
            shared_serial_parent_array[channel]->minor = 
                                               device_names[tbl_index].minor; 

            /* Switch to shared serial major, keep minor the channel */
            device_names[tbl_index].major = MAJOR_SHARED_SERIAL; 
        }
    }

    shared_serial_initialized = 1;

}

/*
 * shared_serial_open
 *    This routine is called once per shared serial port, it does the 
 *    following:
 *      - malloc a buffer used to store serial data for reading/writing.
 *      - create a linked list of childres listening to this port.
 *      - create a thread to listen for serial data
 *
 * Params:
 * channel - Indicates which serial port in the devices table.
 *
 * Return Value:
 *   errno if an error occured
 *   otherwise the devid which is passed into future calls to this driver
 *
 * EINVAL Channel is not valid
 * EAGAIN Resources unavailable - try again later
 */
int shared_serial_open(int channel, unsigned flags)
{
    SharedSerialParent *parent;
    SharedSerialChild  *child;
    unsigned int        ret;
    unsigned int        i, status;
    int                 child_index;

    if (flags & O_CONFIG)
    {
        /* Only allow configuration accesses */
        return open_for_config(channel);
    }
    /* 
     * One parent per serial port, the channel is the index into the parent 
     * array
     */
    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
        return EINVAL;
    }

    if (parent->open_count >= parent->config.max_clients)
    {
        trace(SHARE_TRACE, TRACE_DEBUG,
        "shared_serial_open: exceeded max open clients\n");
        return EAGAIN;
    }

    if (!parent->open)
    {
        trace(SHARE_TRACE, TRACE_INFO,
        "shared_serial_open: opening parent %d \n", channel);
        if ((ret = open_parent_port(parent)) != EXIT_SUCCESS)
        {
            return ret; /* Could not open serial port */
        }
    }
    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
         != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_CRITICAL, 
        "shared_serial_open: tx_mutex_get failed %d\n", status); 
        return EINVAL;
    } 

    if ((child_index = open_child_port(channel)) < 0)
    {
        trace(SHARE_TRACE, TRACE_CRITICAL, 
        "shared_serial: could not open child port"); 
        assert(0);
        tx_mutex_put(&parent->mutex);
        return EAGAIN;
    }

    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        assert(0);
        tx_mutex_put(&parent->mutex);
        return EAGAIN;
    }

    if ((flags & O_CONFIG) == 0)
        parent->open_count++;

    /*
     * All children are assigned a bit in the parent event_flag_mask,
     * this is used to wake up child threads on blocking reads, we only
     * allow a max of 32 clients, so there is at least one bit
     * available per child.
     */
#if (SHARING_MAX_CLIENTS > 32)
/* We only have 32 bits in our event flag */
#error Shared serial max clients exceeds 32
#endif

    for (i=0; i < SHARING_MAX_CLIENTS; i++)
    {
        if ((parent->event_flag_mask & (1 << i)) == 0)
            break;
    }

    if (i == SHARING_MAX_CLIENTS)
    {
        tx_mutex_put(&parent->mutex);
        return EAGAIN;
    }

    parent->event_flag_mask |= (1 << i);
    child->event_flag        = (1 << i);

    /*
     * Initialize the child pointer to point the beginning of the
     * parent buffer, this moves/wraps as the child reads data
     * from the parent.
     */

    child->shared_buf     = parent->shared_buf;
    child->shared_buf_out = parent->shared_buf_in;

    child->parent = parent;

    if (flags & O_NONBLOCK)
        child->blocking = 0;
    else
        child->blocking = 1;

    if (flags & O_CONFIG)
        child->open_for_config = 1;
    else
        child->open_for_config = 0;

    trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_open: child %d \n", child_index);

    if ((status = tx_mutex_put(&parent->mutex)) 
         != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_CRITICAL, 
        "shared_serial_open: tx_mutex_put failed %d\n", status); 
        return EINVAL;
    } 

    return (child_index | DDI_DEV_ID_BIT);
}

/*
 * Close down a serial port
 */
int shared_serial_close(int child_index)
{
    SharedSerialParent *parent;
    SharedSerialChild  *child;
    struct child_queue_t *tmp;
    int ret=EXIT_SUCCESS, channel;
    unsigned long status;

    trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_close: closing %d \n", child_index);
    
    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        return EBADF;
    }

    if (child->open_for_config)
    {
        /* This child was opened for config only, just free */
        shared_serial_child_array[child_index] = NULL;
        free(child);
	return ret;
    }

    parent = child->parent;



    /* 
     * Unlink from list of children 
     */
    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
         != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_CRITICAL, 
        "shared_serial_close: tx_mutex_get failed %d\n", status); 
        return EINVAL;
    } 

    parent = child->parent;

    channel = child->channel;

    for (tmp = parent->child_head; tmp != NULL; tmp = tmp->next)
    {
        if (child == tmp->child) /* found it */
        {
            if (tmp->prev == NULL)
            {
                parent->child_head = tmp->next; 
            }
            else
            {
                tmp->prev->next = tmp->next;
            }

            if (tmp->next == NULL)
            {
                parent->child_tail = tmp->prev;
            }
            else
            {
                tmp->next->prev = tmp->prev;
            }
            free(tmp);
            break;
        }
    }

    parent->open_count--;

    /* Clear bit from parent event mask */
    parent->event_flag_mask &= ~child->event_flag;

    /* free the child */
    trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_close: freeing child %p\n", child);
    free(child);

    shared_serial_child_array[child_index] = NULL;

    num_open_children--;

    trace(SHARE_TRACE, TRACE_INFO,
          "num_open_children %d\n", num_open_children);

    if ((status = tx_mutex_put(&parent->mutex)) != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_CRITICAL, 
        "shared_serial_close: tx_mutex_get failed %d\n", status); 
        return EINVAL;
    }

    /*
     * If the port is no longer shared and we have no open ports
     * free any used memory and revert back to the previous
     * serial driver.
     */
    if ((parent->open_count == 0) && (parent->no_longer_shared == 1))
    {
        ret = close_parent_port(parent);
    }

    /* The close event has to be called by the serial driver */
    serl_cb_event(channel, CLOSE_EVENT);

    return ret;
}


/* 
 * Read from the shared serial buffer for this port.
 * If there is data in the child buffer, copy into
 * the callers buffer and return.  If there is no data
 * available and in blocking mode, wait for an event
 * from the port listener thread, if non-blocking don't
 * wait on the event.
 */
int shared_serial_read(int child_index, void *buffer, int size, int *bytesRead)
{
    SharedSerialChild  *child;
    SharedSerialParent *parent;
    unsigned long flags;
    int  contig, status;
    int  bytes_available, bytes_to_read;

    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_read: BADF\n");
        return EBADF;
    }

    parent = child->parent;

    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
         != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial_read: tx_mutex_get failed %d\n", status); 
        return EINVAL;
    } 

    *bytesRead = 0;

    if (size == 0)
        return 0;
    
    if (child->timed_out) 
    {
        /* Now that the child has read some data, remove the timeout */
        cancel_timeout(child);
    }

    /* Restart the timeout counter */
    child->timeout_start = 0;

    update_parent_buffer(child->channel);
    bytes_available =  buf_data_avail(parent, child);

    if (!bytes_available && !child->blocking)
    {
        *bytesRead = 0;
        status = tx_mutex_put(&parent->mutex);
        return EAGAIN;
    }
    else
    {
        /*
         * If data is available and the event flag is not set, return
         * what data we have in the shared buffer
         */
        if (!bytes_available && child->blocking)
        {
            do
            {
                if ((status = tx_mutex_put(&parent->mutex)) 
                    != TX_SUCCESS) 
                { 
                    trace(SHARE_TRACE, TRACE_DEBUG, 
                        "shared_serial_read: tx_mutex_get failed %d\n", 
                        status); 
                    return EINVAL;
                } 
                /* Wait for a read event from the parent */
                status = tx_event_flags_get(&parent->event, child->event_flag, 
                                            TX_OR_CLEAR, 
                                            &flags,
                                            TX_WAIT_FOREVER);

                if (status != TX_SUCCESS)  
                {
                    printf("Could not get flags\n");
                    assert(0);
                    return EAGAIN;
                }

                /* Our event flag has triggered there data is available */
                if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
                    != TX_SUCCESS) 
                { 
                    trace(SHARE_TRACE, TRACE_DEBUG, 
                        "shared_serial_read: tx_mutex_get failed %d\n", status); 
                    return EINVAL;
                } 

                update_parent_buffer(child->channel);
                bytes_available =  buf_data_avail(parent, child);

            } while (bytes_available == 0); /* No data available */
        }
    }


    if (size > bytes_available)
        bytes_to_read = bytes_available;
    else
        bytes_to_read = size;

    contig = child->shared_buf + SHARED_BUF_SIZE - child->shared_buf_out;

    if (bytes_to_read >= contig) 
    {
        memcpy(buffer, child->shared_buf_out, contig);
        memcpy((char *)buffer+contig, child->shared_buf, bytes_to_read - 
               contig);
        child->shared_buf_out = child->shared_buf + bytes_to_read - contig;
    } 
    else 
    {
        memcpy(buffer, child->shared_buf_out, bytes_to_read);

        if (bytes_to_read < contig)
            child->shared_buf_out += bytes_to_read;
        else /* bytes_to_read == contig, wrap pointer */
            child->shared_buf_out = child->shared_buf;
    }

    *bytesRead += bytes_to_read;

    status = tx_mutex_put(&parent->mutex);

    return EXIT_SUCCESS;
}


/*
 * Write to a shared serial port, this can be called from multiple different
 * threads/applications which write to the same serial port.
 */
int shared_serial_write(int child_index, void *buffer, int length, int *bytesWritten)
{
    SharedSerialParent *parent;
    SharedSerialChild  *child;
    int ret;

    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_write: BADF\n");
        return EBADF;
    }

    parent = child->parent;

    trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial_write: write %d child %p\n", length, child); 

    
    /* Check to see if this child has write access to this port */
    if (child->can_write)
    {
        int is_writeable=is_port_writeable(child->channel);

         /* The parent port is always opened in non-blocking mode */
        if (!child->blocking && !is_writeable)
        {
            /* Nonblocking socket and the port is not writeable */
            return EAGAIN;
        }

        /* Port is blocking, but not writeable, in this case we need to wait */
        if (child->blocking && !is_writeable)
        {
            do
            {
                tx_thread_sleep(SERIAL_WRITE_POLL_TIME);
            } while (is_port_writeable(child->channel) == 0);
        }

        ret = majorTable[parent->major].write(child->channel, 
                                              buffer, length, 
                                              bytesWritten);
        return ret;
    }

    /* 
     * The child does not have permission to this port 
     * Fake the write.
     */
    *bytesWritten = length;

    return EXIT_SUCCESS;
}

/* Flush a shared serial port */
int shared_serial_flush(SharedSerialChild *child, char *arg)
{
    int ret=0;
    int queue_selector;
    SharedSerialParent *parent;
    unsigned int status;

   trace(SHARE_TRACE, TRACE_INFO,
         "shared_serial_flush\n");
    parent = child->parent;

    queue_selector = (enum flush_opts)arg;

    if (queue_selector & TCIFLUSH)  /* Flush receive buffer */
    {
        /* Reset the child's shared buffer pointer */
        if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
         != TX_SUCCESS) 
        { 
            trace(SHARE_TRACE, TRACE_DEBUG, 
            "shared_serial_flush: tx_mutex_get failed %d\n", status); 
            return EINVAL;
        } 
        update_parent_buffer(child->channel);

        child->shared_buf_out = parent->shared_buf_in;

        if ((status = tx_mutex_put(&parent->mutex)) 
            != TX_SUCCESS) 
        { 
            trace(SHARE_TRACE, TRACE_DEBUG, 
            "shared_serial_flush: tx_mutex_get failed %d\n", status); 
            return EINVAL;
        } 

    }

    /* 
     * Only flush the transmit buffer when there is only
     * one child.
     */
    if ((queue_selector & TCOFLUSH) && (parent->open_count  <= 1))
    {
        ret = majorTable[parent->major].ioctl(child->channel, 
                                             TCFLUSH, (char *)TCOFLUSH);
    }


    return ret;
}

/* Check the permissions and then call the ioctl for the port */
int shared_serial_ioctl(int child_index, int request, char *arg)
{
    SharedSerialChild  *child;
    SharedSerialParent *parent;
    int ret=0, channel, major;

   trace(SHARE_TRACE, TRACE_INFO,
         "shared_serial_ioctl [%d]\n", request);
    child = get_child_ptr(child_index);

    if (child == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_INFO,
               "shared_serial_ioctl: child not opened\n");
        parent  = shared_serial_parent_array[child_index];
        channel = child_index;
        major   = parent->major; 
    }
    else if (child->open_for_config)
    {
        /* opened for config access only, does not have a parent */

        /* See if the parent serial port for this opened */
        parent  = shared_serial_parent_array[child->channel];
        channel = child->channel;
        major   = parent->major; 
    }
    else
    {
        /* Child is opened */
        if ((parent = get_parent_ptr(child->channel)) == 
	        (SharedSerialParent *)NULL)
        {
           trace(SHARE_TRACE, TRACE_INFO,
                 "shared_serial_ioctl: EINVAL2\n");
            return EINVAL;
        }

        channel = child->channel;
        major   = parent->major; 
    }

    switch (request)
    {
        case TCGETBUFFERS: /* We need to check the shared buffer */
        {
            int    bytes_available;
            struct serial_buffer_t tmp_bufs;
            struct serial_buffer_t *bufs;
            unsigned int status;


            /*
             * We need to handle the case where open was called with 
             * O_CONFIG, this means that the serial driver 'open'
             * call was never actually called, so we just check
             * how much data the pararent has in it's shared buffer
             * since the SharedSerialStructure has not been setup yet.
             *
             * In the case where the child has not been opened the
             * arg (child_index) is the channel number.
             */
            if ((child == (SharedSerialChild *)NULL) || (child->open_for_config))
            {
                ret = majorTable[major].ioctl(child_index, 
				                              TCGETBUFFERS, 
                                              arg);
            }
            else
            {
                if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
                    != TX_SUCCESS) 
                { 
                    trace(SHARE_TRACE, TRACE_DEBUG, 
                    "shared_serial: tx_mutex_get failed %d\n", status); 
                    return EINVAL;
                } 

                /* Update parent buffer */
                update_parent_buffer(child->channel);
                bytes_available =  buf_data_avail(parent, child);
                tx_mutex_put(&parent->mutex);

                bufs = (struct serial_buffer_t *)arg;

                /* Only return # of bytes in the shared buffer */
                bufs->rxbuf = bytes_available;
                ret = majorTable[parent->major].ioctl(child->channel, 
                                         TCGETBUFFERS, 
                                         (char *)&tmp_bufs);
                bufs->txbuf  = tmp_bufs.txbuf;
            }
            break;
        }
        case TCFLUSH:
        {
            ret = shared_serial_flush(child, arg);
            break;
        }

        /* Look for ioctls which require permision */
        case TCSENDBREAK:
        case TCSENDNOW:
            if (child == (SharedSerialChild *)NULL)
            {
                return EINVAL;
            }
            if (child->can_write)
            {
                ret = majorTable[major].ioctl(channel, 
                                              request, arg);
            }

            break;

        case ISOPEN:
            *(int *)arg = 
                    (parent->open_count >= parent->config.max_clients)?1:0;
            ret = EXIT_SUCCESS;
            break;

          case TCSETA:
            /*
             * In exclusive mode we only allow the UI to modify
             * parameters, they always open the port with O_CONFIG
             * set, everything else gets rejected.
             */ 
            if ((parent->config.control == SHARING_CONTROL_EXCLUSIVE) &&
                (child->open_for_config == 0))
            {
               trace(SHARE_TRACE, TRACE_INFO,
               "shared_serial_ioctl: permission denied\n");
                return 0;  /* Silently ignore permission errors */
            }
        case TCGETCOUNTERS: 
        case TCFLOW:
        case TCSETSIGNALS:
        case TCGETA:
        case TCDRAIN:
        case TCGETSTATS:
        case TCGETSIGCHANGE:
        case TCTXDONE:
        case DIPSWITCHMODE:
            ret = majorTable[major].ioctl(channel, request, arg);
            break;

        default:
           trace(SHARE_TRACE, TRACE_CRITICAL,
           "shared_serial_ioctl: unsupported ioctl [%d]\n", request);
	    assert(0);
            break;
    }

    return ret;
}


static int is_port_writeable(int channel)
{
    int ret, checkMask;
    SharedSerialParent *parent;

    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
       trace(SHARE_TRACE, TRACE_INFO,
            "is_port_writeable: EINVAL\n");
        return EINVAL;
    }

    checkMask = NA_SELECT_WRITE; 

    ret = majorTable[parent->major].poll(channel, 0, &checkMask, 0);

    if (checkMask & NA_SELECT_WRITE)
    {
        return 1;
    }

    return 0;
}

/*
 * Check to see if the port has data available.
 */
static int is_port_readable(int channel)
{
    int ret, checkMask;
    SharedSerialParent *parent;

    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
       trace(SHARE_TRACE, TRACE_INFO,
            "is_port_readable: EINVAL\n");
        return EINVAL;
    }

    checkMask = NA_SELECT_READ; 

    ret = majorTable[parent->major].poll(channel, 0, &checkMask, 0);

    if (checkMask & NA_SELECT_READ)
    {
        return 1;
    }

    return 0;
}


/*
 * Function: shared_serial_poll()
 *
 * 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 shared_serial_poll(int child_index, int insert_remove, int *checkMask, 
                       ulong context)
{
    int ret = 0;
    SharedSerialChild  *child;
    SharedSerialParent *parent;

    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        return EBADF;
    }
    parent = child->parent;

    /* 
     * Save checkMask and context. 
     */
    if (insert_remove == NA_SELECT_INSERT)
    {
        child->select_context = context;
        child->checkMask      = *checkMask;
    }
    else
    {
        child->select_context = 0;
        child->checkMask      = 0;
    }

    if (*checkMask & NA_SELECT_READ) 
    {
        if (buf_data_avail(parent, child) > 0)
        {
            ret |= NA_SELECT_READ; /* There is data in the shared buffer */
        }
    }
    if (*checkMask & NA_SELECT_WRITE) 
    {
        /* See if we have room to write */
        if (is_port_writeable(child->channel))
        {
            ret |= NA_SELECT_WRITE; 
        }
    }

    *checkMask = ret;

    return 0;
}

static void check_for_select(SharedSerialParent *parent)
{
    SharedSerialChild    *child;
    struct child_queue_t *tmp;
    unsigned long status;

    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
        != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_CRITICAL, 
            "shared_serial: tx_mutex_get failed %d\n", status); 
        assert(0);
        return;
    } 

    for (tmp = parent->child_head; tmp != NULL; tmp = tmp->next)
    {
        child   = tmp->child;

        if (child->checkMask & NA_SELECT_READ)
        {
            if (buf_data_avail(parent, child) > 0)
            {
                tfDeviceSelectNotify(NA_SELECT_READ, child->select_context);
            }
        }
        if (child->checkMask & NA_SELECT_WRITE)
        {
            if (is_port_writeable(child->channel))
            {
                tfDeviceSelectNotify(NA_SELECT_WRITE, 
                                    child->select_context);
            }
        }

    }
    if ((status = tx_mutex_put(&parent->mutex)) != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial: tx_mutex_get failed %d\n", status); 
        assert(0);
        return;
    }
}


static int open_parent_port(SharedSerialParent *parent)
{
    unsigned long status;
    int ret;

    memset(&parent->mutex, 0, sizeof(TX_MUTEX));
    memset(&parent->listener, 0, sizeof(TX_THREAD));
    memset(&parent->event, 0, sizeof(TX_EVENT_FLAGS_GROUP));

    /* Used to protect linked list of children */
    status = tx_mutex_create(&parent->mutex, "Shared serial mutex", 
                                 TX_INHERIT);
    if (status != TX_SUCCESS) 
    {
        trace(SHARE_TRACE, TRACE_CRITICAL,
        "shared_serial_open: cannot create mutex [%d]", status);
        return EAGAIN;
    }

    /*
     * Malloc the shared buffer, this is filled in by the parent
     * and read by all the children.
     */
    parent->shared_buf = malloc(SHARED_BUF_SIZE);

    if (parent->shared_buf == 0) 
    {
        trace(SHARE_TRACE, TRACE_CRITICAL,
        "shared_serial_open: out of memory, port=\n");
        return EAGAIN;
    }

    memset(parent->shared_buf, 0, SHARED_BUF_SIZE);

    /* Shared buffer is empty */
    parent->shared_buf_in = parent->shared_buf;

    /*
     * This is the first time this serial port has been opened.
     * Initialize the parent structure and create a thread
     * to listen for incomming serial data.
     */
  
    /* Open non-blocking/read/write */
    if ((ret = majorTable[parent->major].open(parent->minor, 
                                              O_RDWR | O_NONBLOCK)) != 0)
    {
        trace(SHARE_TRACE, TRACE_CRITICAL,
        "shared_serial_open: could not open serial port\n");
        return ret;
    }

    parent->event_flag_mask = 0;

    status = tx_event_flags_create(&parent->event, "Shared Serial Event");             
    if (status != TX_SUCCESS) 
    {
        trace(SHARE_TRACE, TRACE_CRITICAL,
        "shared_serial_open: Could not create event");
        free(parent->shared_buf);
        return EAGAIN; 
    }

    parent->open = 1;
    /* 
     * Create a thread to wait for data from this serial port. 
     */
    
     status = tx_thread_create(&parent->listener,
             "Shared serial listener",
             port_listener_thread,
             parent->minor,  /* channel passed to listener thread */
             NULL, /* Dyamic stack */
             SHARED_THREAD_STACK_SIZE,    
             16,    
             16,    
             10,    
             TX_AUTO_START );

     if (status != TX_SUCCESS) 
     {
         trace(SHARE_TRACE, TRACE_CRITICAL,
          "shared_serial_open: cannot create thread [%d]", status);
         free(parent->shared_buf);
         return EAGAIN;
    }


    return ret;

}

/*
 * Allocate a child fd only to be used for config accesses.  We
 * don't attach it to a parent, it's only used for ioctl.
 */
static int open_for_config(int channel)
{
    SharedSerialChild    *child;
    int i;

    for (i = 0; i < NUM_SHARED_SERIAL_PORTS; i++)
    {
        if (shared_serial_child_array[i] == NULL)
            break;
    }

    if (i == NUM_SHARED_SERIAL_PORTS)
    {

        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: to many open children\n");
        return -1;
    } 


    child = malloc(sizeof(SharedSerialChild));
    if (child == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: malloc failed on port %d\n", channel);
        return -1;
    }

    memset(child, 0, sizeof(SharedSerialChild));

    shared_serial_child_array[i] = child;

    child->channel         = channel;
    child->open_for_config = 1;

    return (i | DDI_DEV_ID_BIT);
}


/*
 * Open a child serial port for a parent.
 * Allocate a shared serial child structure and link it into
 * the parents list of children.
 */
static int open_child_port(int channel)
{
    SharedSerialChild    *child;
    SharedSerialParent   *parent;
    struct child_queue_t *child_queue_element;
    unsigned long        status;
    int i;

    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
        return -1;
    }

    if (num_open_children >= NUM_SHARED_SERIAL_PORTS)
    {
        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: to many open children\n");

    }

    /* Add to the link list of children for this port */

    /*
     * Find an open slot in the shared_serial_child_array
     * and use that as the index
     */ 
    for (i = 0; i < NUM_SHARED_SERIAL_PORTS; i++)
    {
        if (shared_serial_child_array[i] == NULL)
            break;
    }

    if (i == NUM_SHARED_SERIAL_PORTS)
    {

        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: to many open children\n");
	return -1;
    } 


    child = malloc(sizeof(SharedSerialChild));
    if (child == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: malloc failed on port %d\n", channel);
        return -1;
    }

    shared_serial_child_array[i] = child;

    memset(child, 0, sizeof(SharedSerialChild));

    /* Add to the link list of children for this port */
    if ((child_queue_element = malloc(sizeof(struct child_queue_t))) == NULL)
    {
        trace(SHARE_TRACE, TRACE_DEBUG,
        "open_child_port: malloc failed on port %d\n", channel);
	free(child);
        return -1;
    }

    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
         != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial: tx_mutex_get failed %d\n", status); 
	free(child_queue_element);
	free(child);
        return -1;
    } 

    child_queue_element->child = child;

    if (parent->child_head == NULL) /* First on list */
    {
       parent->child_head = parent->child_tail = child_queue_element;
       child_queue_element->next = NULL;
       child_queue_element->prev = NULL;
    }
    else /* Add to the end of the linked list */
    {
       child_queue_element->prev = parent->child_tail;
       parent->child_tail->next  = child_queue_element;
       child_queue_element->next = NULL;
       parent->child_tail        = child_queue_element;
    }

    if (parent->config.write_policy == SHARING_WRITE_FIRST) 
    {
        child->can_write = (parent->open_count >= 1) ? 0 : 1;
    }
    else
    {
        /* Default allows writes */
        child->can_write = 1;
    }

    child->channel       = channel;
    child->timed_out     = 0;
    child->timeout_start = 0;

    if ((status = tx_mutex_put(&parent->mutex)) != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial_open: tx_mutex_get failed %d\n", status); 
	free(child_queue_element);
	free(child);
        return -1;
    }

    num_open_children++;
    return i;
}

/*
 * This is the port listener thread, there is one of these threads
 * for each open serial port. When data is available, this thread
 * will read the data into the port parent buffer, an event is then
 * used to wake up any children that are waiting for data on this 
 * port.
 */
static void port_listener_thread(unsigned long arg)
{
    int channel = (int)arg, ret;
    SharedSerialParent *parent;
    unsigned int    status;

    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
        assert(0);
        return;
    }

    parent->in_thread = 1;
    for (;;)
    {
        /* 
         * Wait for data to become available, also loop here until a child
         * has opened the serial port.
         */
        while (!is_port_readable(channel) || (parent->child_head == NULL))
        {
            if ((parent->open_count == 0) && (parent->no_longer_shared == 1))
            {
               parent->in_thread = 0;
	       return;
            }
            tx_thread_sleep(SERIAL_READ_POLL_TIME);
        }

        /* 
         * Fill in the parent buffer, update the shared buffer 
         * in and out pointers.
         */
        tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER);
        ret = update_parent_buffer(channel);
        tx_mutex_put(&parent->mutex);
        check_for_select(parent);
        if (ret < 0)
        {
            /* Return < 0 indicates there was no room in the shared buffers */
            tx_thread_sleep(SERIAL_READ_POLL_TIME);
            continue;
        }

        /*
         * The child buffer pointers have been updated, set the event
         * to wake up all children which are waiting for data on
         * this port.
         */
        status = tx_event_flags_set(&parent->event, parent->event_flag_mask,
                                   TX_OR);
        if (status != TX_SUCCESS)  
        {
            parent->in_thread = 0;
            return;
        }
    }
}

/*
 * update_parent_buffer
 *
 * Called by the listener thread to fill the parent buffer. Update the
 * shared buffer in and out pointers.
 *
 * It reads from the serial port and stores the data in the
 * shared buffer, reading as much data as available or as much
 * as there is room for, whichever is less.
 *
 */
static int update_parent_buffer(int channel)
{
    int  size, space_avail, contig, bytes_read, ret=0, total_read=0, sa;
    int  min_sa=SHARED_BUF_SIZE;
    SharedSerialParent *parent;
    SharedSerialChild  *child;
    BOOL timed_out;
    struct child_queue_t *tmp;

    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
        return EBADF;
    }

    if (!parent->open)
    {
        return EBADF;
    }

    /* 
     * See how much data is available in the shared byffer, we need to loop 
     * through and see which child has the least amount of shared buffer space
     * available.
     */
    min_sa    = SHARED_BUF_SIZE; 
    timed_out = 1;

    update_timeout(parent); /* Check for timeouts */

    if (parent->child_head == NULL)
    {
        /* No children (no open ports), so don't start reading */
        return -1;
    }

    for (tmp = parent->child_head; tmp != NULL; tmp = tmp->next)
    {
        child   = tmp->child;
        sa      = buf_space_avail(parent, child);

        /* 
         * If a child has timed out, ignore it's space available until
         * it calls read() and frees up some space, the child starts
         * loosing data once the timeout starts.
         */
        if (!child->timed_out)
        {
            min_sa  = min(min_sa, sa);
            /* 
             * Set a flag indicating not all children are in the timed
             * out state, if so we need to wait for someone to call read.
             */
             timed_out = 0;
        }
        else
        {
            /* This child is now loosing data */
            /*trace(SHARE_TRACE, TRACE_DEBUG, 
           "update_parent_buffer: ignoring child timed out [%p]\n", child); */
        }
    }

    if ((min_sa == 0) || (timed_out == 1))
    {
        /* No room to read */
        return -1;
    }

    size = min_sa;

    contig = parent->shared_buf + SHARED_BUF_SIZE - parent->shared_buf_in;

    /* 
     * Read directly into the parent buffer.
     * If the buffer wraps, read the first part.
     */
    if (size >= contig)
    {
        if (is_port_readable(channel))
        {	    
            ret = majorTable[parent->major].read(channel, 
                                                 parent->shared_buf_in, 
                                                 contig, &bytes_read);
	    }
	    else
        {
            return 0;
        }

        if (ret < 0)
        {
            return ret;
        }

        if (bytes_read < contig)  
        {
            parent->shared_buf_in += bytes_read;

            total_serial_read  += bytes_read;
            if (bytes_read > 0)
                trace(SHARE_TRACE, TRACE_DEBUG, 
                      "total_serial_read  %d\n", total_serial_read); 

            return bytes_read;
        }
        parent->shared_buf_in = parent->shared_buf; /* wrap the in pointer */
        size        -= bytes_read;
        space_avail -= contig;
        total_read   = contig;
    }

    if (is_port_readable(channel))
    {	    
        ret = majorTable[parent->major].read(channel, parent->shared_buf_in, 
                                             size,
                                             &bytes_read);
    }
    else
    {
        bytes_read = 0;
    }	    

    if (ret < 0)
    {
        return ret;
    } 

    parent->shared_buf_in += bytes_read;
    total_read            += bytes_read;
 
    total_serial_read  += total_read;

    if (total_read > 0)
        trace(SHARE_TRACE, TRACE_DEBUG, 
            "total_serial_read  %d\n", total_serial_read); 

    return total_read;
}

/*
 * buf_data_avail
 *
 * Returns the amount of data available in the shared buffer.
 */
static int buf_data_avail(SharedSerialParent *parent, SharedSerialChild *child)
{
    int data_avail;

    data_avail = (SHARED_BUF_SIZE + parent->shared_buf_in 
                  - child->shared_buf_out)
                  % SHARED_BUF_SIZE;

    return data_avail;
}

/*
 * buf_space_avail
 *
 * Returns the amount of space available in the shared buffer.
 */
static int buf_space_avail(SharedSerialParent *parent, SharedSerialChild *child)
{
    char *buf_out, *buf_in;
    int space_avail;

    buf_out = child->shared_buf_out; 
    buf_in  = parent->shared_buf_in;

    space_avail = (SHARED_BUF_SIZE + buf_out - buf_in - 1)
                  % SHARED_BUF_SIZE;

    return space_avail;
}

/*
 * close down a parent serial port, this is called when the port
 * is no longer shared.
 */
int close_parent_port(SharedSerialParent *parent)
{
    unsigned long status;
    int ret=0, tbl_index;

    if (parent->open == 1)
    {

        trace(SHARE_TRACE, TRACE_DEBUG, 
              "shared_serial_close: closing parent %d\n", parent->minor); 
        trace(SHARE_TRACE, TRACE_DEBUG, 
            "shared_serial_close: port no longer shared %d\n", parent->minor); 

        status = tx_event_flags_delete(&parent->event);             
        if (status != TX_SUCCESS) 
        {
            trace(SHARE_TRACE, TRACE_CRITICAL,
                  "shared_serial_open: Could not delete event flags");
            free(parent->shared_buf);
            return EINVAL;
        }

        status = tx_mutex_delete(&parent->mutex);
        if (status != TX_SUCCESS) 
        {
            trace(SHARE_TRACE, TRACE_CRITICAL,
                "shared_serial_open: cannot create mutex [%d]", status);
            return EINVAL;
        }

        // Make sure the thread exits before deleting.
        while (parent->in_thread == 1)
        {
            tx_thread_sleep(SERIAL_READ_POLL_TIME);
        }

        tx_thread_delete(&parent->listener);

        if ((ret = majorTable[parent->major].close(parent->minor) != 0))
        {
            trace(SHARE_TRACE, TRACE_CRITICAL,
                  "shared_serial_close: could not close serial port\n");
            return ret;
        }

        free(parent->shared_buf);

	parent->open = 0;
    }

    /* Revert back to old serial driver */
    if ((tbl_index = portno_to_dtable(parent->minor)) < 0)
    {
        assert(0);
        return ret;
    }


    /* Put back the previous major/minor */
    device_names[tbl_index].major = parent->major;
    device_names[tbl_index].minor = parent->minor;


    /* The parent structure remains allocated */

    return ret;
}

/*
 * A child has been in the timed out state, dropping
 * data because it's no longer reading, cancel this
 * state and reset (flush) it's out pointer.
 */
static void cancel_timeout(SharedSerialChild *child)
{
    SharedSerialParent *parent;

    if (child->timed_out == 0)
    {
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial: cancel timeout invalid call\n"); 
        return;
    }

   trace(SHARE_TRACE, TRACE_DEBUG, 
   "shared_serial: cancel timeout for child [%p]\n", child); 

    child->timed_out     = 0;
    child->timeout_start = 0;

    parent = child->parent;
    child->shared_buf_out = parent->shared_buf_in;
}

/*
 * update_timeout
 *
 * Update_timeout detects when some child is flow controlled
 * (no data in the child buffer, but the parent buffer is
 * full, preventing that child from reading new data) and
 * all other children that have full buffers (preventing
 * the first child from reading new data) have held their
 * full buffers for their timeout time.  In this case,
 * the children with full buffers have some of their data
 * flushed to make room for the flow controlled child to
 * read more data.
 *
 */
void update_timeout(SharedSerialParent *parent)
{
  
    int space_avail,sa;
    unsigned long timeout, status;
    SharedSerialChild  *child;
    struct child_queue_t *tmp;


    timeout = parent->config.timeout; /* Read from parent */
    if (timeout == 0) /* Zero timeout indicates no timeout */
    {
        return;
    }

    space_avail      = SHARED_BUF_SIZE - 1;

    /* Lock the linked list of children for this parent */
    if ((status = tx_mutex_get(&parent->mutex, TX_WAIT_FOREVER)) 
        != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial: tx_mutex_get failed %d\n", status); 
        assert(0);
        return;
    } 
    for (tmp = parent->child_head; tmp != NULL; tmp = tmp->next)
    {
        child   = tmp->child;

        sa = buf_space_avail(parent, child);

        /* A child with a full buffer */
        if (sa == 0)
        {
            /*
             *  See if this child just timeout out.
             */  
            if (child->timeout_start == 0) 
            {
                /* Mark the time at which this child will be timed_out */
                child->timeout_cutoff = tx_time_get() + timeout;
                child->timeout_start = tx_time_get();
                trace(SHARE_TRACE, TRACE_DEBUG, 
                "timeout start for child %p\n", child); 
                trace(SHARE_TRACE, TRACE_DEBUG, 
                "timeout start %d\n", child->timeout_start); 
                trace(SHARE_TRACE, TRACE_DEBUG, 
                "timeout cutoff %d\n", child->timeout_cutoff); 
            }
 
            if (tx_time_get() > child->timeout_cutoff) 
            {
                /* 
                 * Indicate the child is in a timed out state at this 
                 * point the child will start to loose data, in
                 * update_parent_buffer we won't block waiting
                 * for this child.
                 */
                if (child->timed_out == 0)
                {
                    trace(SHARE_TRACE, TRACE_DEBUG, 
                    "update_timeout: child has timed out child [%p]\n", child); 
                    trace(SHARE_TRACE, TRACE_DEBUG, 
                    "update_timeout: child after [%d] ticks\n", timeout); 
                }
                /* 
                 * Child stays timed out until read is called
                 */
                child->timed_out = 1;
            }        
        }

    }

    if ((status = tx_mutex_put(&parent->mutex)) 
        != TX_SUCCESS) 
    { 
        trace(SHARE_TRACE, TRACE_DEBUG, 
        "shared_serial: tx_mutex_get failed %d\n", status); 
        assert(0);
        return;
    } 


}

/*
 * Return a pointer to a shared_serial_parent structure based on the
 * channel.
 */
static SharedSerialParent *get_parent_ptr(int channel)
{
    if ((channel > SHARED_PARENT_CNT) || (channel < 0))
    {
        trace(SHARE_TRACE, TRACE_CRITICAL,
        "shared_serial_parent, out of range port=%d\n", channel);
        return (SharedSerialParent *)NULL; /* out of range */
    }

    return shared_serial_parent_array[channel];
}

/*
 * Return a pointer to a shared_serial_child structure based on the
 * channel.
 */
static SharedSerialChild *get_child_ptr(int child_index)
{
    if ((child_index > NUM_SHARED_SERIAL_PORTS) || 
        (child_index < 0))
    {
        return (SharedSerialChild *)NULL;
    }

    return shared_serial_child_array[child_index];
}

/*
 * This function is used by UI to get the number of clients
 * hooked to a given serial port.
 */
unsigned char sharing_get_current_clients(int channel)
{
    SharedSerialParent *parent;
    
    /* 
     * One parent per serial port, the channel is the index into the parent 
     * array
     */
    if ((parent = get_parent_ptr(channel)) == (SharedSerialParent *)NULL)
    {
        return 0;
    }

    return (unsigned char)parent->open_count;
}

int portno_to_dtable(int portno)
{
// error check portno
    char targetName[20];
    int i = 0;
     if (portno < 0 || portno >= max_serial_ports()) {
           return -1; // Illegal ports are always open
     }


     // Find in deviceTable
     sprintf(targetName, "/com/%d", portno);
    for (i = 0 ; i < MAX_NAMES ; i++ ) {
          if (strcmp(device_names[i].name, targetName) == 0) {
               break;
      	}
    }

    return i;
}

/*
 * Get the original minor number for a shared serial port, this
 * was overwritten with the index into the child array.
 */
unsigned short shared_serial_get_original_minor(unsigned short child_index)
{
    SharedSerialChild *child;
    SharedSerialParent *parent;

    if ((child = get_child_ptr(child_index)) == (SharedSerialChild *)NULL)
    {
        trace(SHARE_TRACE, TRACE_INFO,
          "shared_serial_get_original_minor: BADF\n");
        return EBADF;
    }

    parent = child->parent;

    if (parent == (SharedSerialParent *)NULL)
    {
        trace(SHARE_TRACE, TRACE_INFO,
        "shared_serial_get_original_minor: NULL parent pointer\n");
        return EBADF;
    }

    return parent->minor;
}

#endif /* SHARING_ENABLED */
