/*****************************************************************************
* 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
*
*****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ind_io.h>
#include <naerrno.h>
#include <sockapi.h>
#include "ddi.h"
#include "errno.h"
#include "event_handler.h"
#include "libutils.h"
#include "serial_help.h"
#include "trace.h"
#include "termios.h"
#include "termios_int.h"
#include "tx_api.h"
#include "gsm_mux.h"

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


//
// GSM/TS 07.10 multiplexer frame formats
//

// Mux start AT command
#define MUX_START       "AT+CMUX=0\r"

#define N1              98      // Max data payload

// General mux frame
typedef struct mux_frame {
    struct mux_frame *next;     // Next frame in queue
    uint8 flag;                 // Start FLAG
    uint8 address;              // Address/DLCI
    uint8 control;              // Control frame type
    uint8 length;               // Information length
    uint8 info[N1+2];           // Information, FCS, end FLAG
} mux_frame_t;

#define	FLAG		0xf9    // Start/end flag value

// Bits in address, length, and MCC type fields
#define EA		0x01    // Extended address
#define	CR		0x02    // Command/response

// Get DLCI (channel number) from address field
#define	GET_DLCI(addr)	(((addr) & 0xfc) >> 2)
// Make address field from DLCI
#define	MAKE_ADDR(dlci)	(((dlci) << 2) | CR | EA)

#define	DLCI_MCC	0x00    // Mux control channel

// Control field values
#define	CONTROL_PF	0x10    // Poll/final bit

#define	CONTROL_SABM	0x2f    // Connect channel
#define	CONTROL_UA	0x63    // Acknowledge
#define	CONTROL_DM	0x0f    // Disconnected
#define	CONTROL_DISC	0x43    // Disconnect channel
#define	CONTROL_UIH	0xef    // Information

// Get length value from length field
#define	GET_LENGTH(n)	(((n) & 0xfe) >> 1)
// Make length field from length value
#define	MAKE_LENGTH(n)	(((n) << 1) | EA)

// Mux control channel frame format.
// These commands are sent as information frames on channel 0.
typedef struct mcc_frame {
    uint8 type;                 // Command type
    uint8 length;               // Value length
    uint8 value[];              // Command parameters
} mcc_frame_t;

// Type field values
#define TYPE_CLD        0xc1    // Mux close down command
#define TYPE_TEST       0x21    // Test (echo) command
#define TYPE_MSC        0xe1    // Modem status command
#define	TYPE_NSC	0x11    // Nonsupported command

// Test command payload to select Siemens protocol version 3
#define TEST_VERSION         "\x04TEMUXVERSION003"
#define TEST_VERSION_SIZE    ((sizeof TEST_VERSION)-1)

// Modem status command format
typedef struct msc_frame {
    uint8 command;              // Command: TYPE_MSC
    uint8 length;               // Value length: 2
    uint8 dlci;                 // DLCI (channel)
    uint8 v24_signals;          // Control signals
} msc_frame_t;

// Control signal values
#define	V24_FC		0x02    // Flow control
#define	V24_RTC		0x04    // DSR or DTR
#define	V24_RTR		0x08    // CTS or RTS
#define	V24_RING	0x40    // Ring indicator
#define	V24_DCD		0x80    // DCD


//
// Mux driver data
//

// Get mux port index from minor number
#define	INDEX_PORT(minor)	((minor) >> 8)
// Get channel index from minor number
#define	INDEX_CHAN(minor)	((minor) & 0xff)

#define BUF_RX_NUM      32      // Receive buffers per mux port

// Receive flow control low/high water marks
#define RX_LOW_MARK     (BUF_RX_NUM/4)
#define RX_HIGH_MARK    (3*BUF_RX_NUM/4)

// Receive buffer pool size
#define BUF_RX_SIZE     (BUF_RX_NUM * (sizeof(mux_frame_t) + sizeof(void *)))

#define TX_MARK         2048    // Transmit event max level

// Channel events
#define EVENT_CONNECT   0x01    // Channel connected
#define EVENT_RX        0x02    // Receive data available
#define EVENT_TX        0x04    // Ready to transmit

// Time to wait for mux start command
#define WAIT_START      NS_MILLISECONDS_TO_TICKS(500)
// Time to wait for channel connect command
#define WAIT_CONNECT    NS_MILLISECONDS_TO_TICKS(500)
// Time to wait for transmit mutex
#define WAIT_TX_MUTEX   NS_MILLISECONDS_TO_TICKS(5000)
// Time to wait for receive mutex
#define WAIT_RX_MUTEX   NS_MILLISECONDS_TO_TICKS(500)
// Time between transmit polls
#define WAIT_TX_POLL    NS_MILLISECONDS_TO_TICKS(10)
// Time between receive/event polls
#define WAIT_RX_POLL    100     // milliseconds

// Debug printf
#define DPRINTF(fmt, args...)  \
        trace_printf (MODEM_TRACE, TRACE_INFO, "%s: " fmt "\r\n", __func__, ##args)

// Mux and channel state
enum {
    STATE_DISCONNECTED,
    STATE_CONNECTING,
    STATE_CONNECTED,
    STATE_OPEN
};

// Channel information
typedef struct channel_info {
    int state;				// One of STATE_xxx
    int dlci;                           // Mux channel number
    int serial_channel;			// Channel number for serl_cb_event()
    int blocking;			// TRUE if blocking open
    TX_MUTEX tx_mutex;                  // Transmit mutex
    TX_EVENT_FLAGS_GROUP events;	// Events for blocking operations
    struct serial_buffer_t buffers;	// Pending byte counters
    struct serial_cntr_t counters;	// Event counters
    struct serial_status_t status;	// Control line state
    struct sigchange_stats_t sigchange;	// Control line change counters
    int prev_mstat;                     // Previous value of status.mstat
    ulong prev_rxbuf;                   // Previous value of buffers.rxbuf
#if NETWORK_STACK == TRECK
    int select_checkMask;		// Treck select() mask
    ulong select_context;		// Treck select() context
#endif

// Receive data
    TX_MUTEX rx_mutex;			// Receive mutex
    mux_frame_t *rx_head;		// Receive queue
    mux_frame_t *rx_tail;
    int rx_offset;			// Byte offset into head buffer
    int rx_pending;			// Buffers on queue
} channel_info_t;

// Received frame state and data
typedef struct rx_state {
    int state;				// Framing state
    int	length;				// Frame length
    int index;				// Index into received data
    mux_frame_t *frame;			// Received frame
} rx_state_t;

// Mulitplexer port information
typedef struct mux_info {
    int state;                          // One of STATE_xxx
    int fd;                             // Mux port file descriptor
    int chan_count;                     // Number of channels
    channel_info_t channels[MUX_CHANS]; // Channel information
    TX_MUTEX tx_mutex;			// Transmit mutex
    rx_state_t rx;			// Received frame and state
    TX_BLOCK_POOL rx_pool;		// Receive buffer pool
    uint8 rx_bufs[BUF_RX_SIZE];		// Receive buffer memory
    TX_THREAD thread;                   // Receive/event thread
    char stack[4096];                   // Thread stack
} mux_info_t;

static mux_info_t mux_ports[MUX_PORTS];

// Local functions
static void init_crc (void);
static int send_frame (mux_info_t *mux, mux_frame_t *frame, int length, BOOL block);
static int send_msc_frame (mux_info_t *mux, channel_info_t *chan);
static void gsm_mux_thread (ULONG);


/*
 *  Function: gsm_mux_enter
 *
 *  Description: First level initialization. Called before
 *               kernel starts.
 *
 *  called by: ANSI layer _enter() (DDIFirstLevelInitalize())
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 */
int gsm_mux_enter (int channel)
{
    static BOOL inited = FALSE;
    
    (void)channel;
    
    if (!inited)
    {
        memset (mux_ports, 0, sizeof mux_ports);
        init_crc();
        inited = TRUE;
    }

    return EXIT_SUCCESS;
}

/*
 *  Function: gsm_mux_init
 *
 *  Description: Second level initialization, after kernel is loaded.
 *               Complete any initialization needed by the driver.
 *
 *  called by: DDISecondLevelInitialize()
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 */
int gsm_mux_init (int channel)
{
    channel_info_t *chan;
    
    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
        return EINVAL;
        
    chan = &mux_ports[INDEX_PORT(channel)].channels[INDEX_CHAN(channel)];

    chan->serial_channel = serial_channel (MAJOR_GSM_MUX, channel);

    tx_mutex_create (&chan->tx_mutex, "GSM mux TX mutex", TX_INHERIT);
    tx_mutex_create (&chan->rx_mutex, "GSM mux RX mutex", TX_INHERIT);

    tx_event_flags_create (&chan->events, "GSM mux event");

    return EXIT_SUCCESS;
}

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

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
        return EINVAL;

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

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

    chan = &mux_ports[INDEX_PORT(channel)].channels[INDEX_CHAN(channel)];
    if (chan->state == STATE_OPEN)
        return EBUSY;
    if (chan->state != STATE_CONNECTED)
        return ENODEV;

    chan->state = STATE_OPEN;
    chan->blocking = !(flags & O_NONBLOCK);
    chan->status.mstat = 0;
    chan->status.estat = 0;
    chan->prev_mstat = 0;
    chan->prev_rxbuf = 0;

    return EXIT_SUCCESS;
}

/*
 *  Function: gsm_mux_close()
 *
 *  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
 *      EBADF
 */
int gsm_mux_close (int channel)
{
    channel_info_t *chan;

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
        return EINVAL;

    chan = &mux_ports[INDEX_PORT(channel)].channels[INDEX_CHAN(channel)];
    if (chan->state != STATE_OPEN)
        return EBADF;

    chan->state = STATE_CONNECTED;

    // Unblock gsm_mux_write()
    tx_event_flags_set (&chan->events, EVENT_TX, TX_OR);

    // Flush receive queue
    gsm_mux_ioctl (channel, TCFLUSH, (void *)TCIFLUSH);

    // Send close event
    serl_cb_event (chan->serial_channel, CLOSE_EVENT);

    return EXIT_SUCCESS;
}

/*
 * Function: gsm_mux_write()
 *
 * Description: write data to driver
 *
 * Called by: DDI write
 *
 * Input : channel - mux and channel number
 *         buffer - data to be sent
 *         length - size of data
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF
 *         EINVAL
 *         EAGAIN
 *         EXIT_SUCCESS
 */
int gsm_mux_write (int channel, void *buffer, int length, int *bytesWritten)
{
    uint8 *data = (uint8 *) buffer;
    mux_info_t *mux;
    channel_info_t *chan;
    mux_frame_t frame;
    ULONG flags;
    int len;
    int written = 0;
    int ret = 0;

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
	return EINVAL;

    mux = &mux_ports[INDEX_PORT(channel)];
    chan = &mux->channels[INDEX_CHAN(channel)];
    if (chan->state != STATE_OPEN)
        return EBADF;

    // Get channel transmit mutex
    ret = tx_mutex_get (&chan->tx_mutex, chan->blocking ? TX_WAIT_FOREVER : 0);
    if (ret == TX_NOT_AVAILABLE)
        return EAGAIN;
    else if (ret != 0)
        return ret;

    // Break data into N1 sized frames
    while (written < length && ret == 0)
    {
        // If flow controlled, block or return EAGAIN
        if (chan->status.estat & EV_OPS)
        {
            if (chan->blocking && written == 0)
            {
                ret = tx_event_flags_get (&chan->events, EVENT_TX, TX_OR_CLEAR, &flags, TX_WAIT_FOREVER);
                if (chan->state != STATE_OPEN)
                    ret = EBADF;
            }
            else
                ret = EAGAIN;
            continue;
        }

        // Send information frame
        len = min (length-written, N1);
        frame.address = MAKE_ADDR(chan->dlci);
        frame.control = CONTROL_UIH;
        memcpy (frame.info, data, len);

        ret = send_frame (mux, &frame, len, chan->blocking);
        if (ret == 0)
        {
            data += len;
            written += len;
            chan->counters.tbytes += len;
        }
    }

    tx_mutex_put (&chan->tx_mutex);

    if (bytesWritten)
	*bytesWritten = written;

    if (written > 0)
	return EXIT_SUCCESS;
    else
	return ret;
}

/*
 * Function: gsm_mux_read()
 *
 * Description: read data from driver
 *
 * Called by: DDI read
 *
 * Input : channel - mux and channel number
 *         buffer - data to be stored
 *         length - size of data buffer
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF
 *         EINVAL
 *         EAGAIN
 *         EXIT_SUCCESS
 */
int gsm_mux_read (int channel, void *buffer, int length, int *bytesRead)
{
    uint8 *data = (uint8 *) buffer;
    mux_info_t *mux;
    channel_info_t *chan;
    mux_frame_t *frame;
    ULONG flags;
    int head_len, len, ret;
    int count = 0;

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
	return EINVAL;

    mux = &mux_ports[INDEX_PORT(channel)];
    chan = &mux->channels[INDEX_CHAN(channel)];
    if (chan->state != STATE_OPEN)
        return EBADF;

    // Get receive mutex
    ret = tx_mutex_get (&chan->rx_mutex, chan->blocking ? TX_WAIT_FOREVER : 0);
    if (ret == TX_NOT_AVAILABLE)
	return EAGAIN;
    else if (ret != 0)
	return ret;

    while (count < length && ret == 0)
    {
	// Get buffer at head of receive queue
	if (chan->rx_head)
	{
	    frame = chan->rx_head;
	    head_len = GET_LENGTH(frame->length);

	    // Copy data to application, starting at rx_offset
	    len = min (length-count, head_len - chan->rx_offset);
	    memcpy (data, frame->info + chan->rx_offset, len);

	    data += len;
	    count += len;
	    chan->rx_offset += len;
	    chan->buffers.rxbuf -= len;

	    // Free frame if empty
	    if (chan->rx_offset >= head_len)
	    {
		// Remove from receive queue
		chan->rx_head = frame->next;
		chan->rx_offset = 0;

		chan->rx_pending--;
		tx_block_release (frame);

                // Release flow control
                if (chan->rx_pending <= RX_LOW_MARK && (chan->status.estat & EV_IPS))
                {
                    chan->status.estat &= ~EV_IPS;
                    send_msc_frame (mux, chan);
                }
	    }
	}

	// Block if no data has been read
	else if (chan->blocking && count == 0)
	    ret = tx_event_flags_get (&chan->events, EVENT_RX, TX_OR_CLEAR, &flags, TX_WAIT_FOREVER);
	else
            ret = EAGAIN;
    }

    tx_mutex_put (&chan->rx_mutex);

    if (bytesRead)
	*bytesRead = count;

    if (count > 0)
	return EXIT_SUCCESS;
    else
	return ret;
}

//
// Close a multiplexer port
//
static int mux_close (mux_info_t *mux)
{
    mux_frame_t frame;
    mcc_frame_t *mcc = (mcc_frame_t *) frame.info;
    int i;
    
    if (mux->state == STATE_DISCONNECTED)
        return EBADF;

    // Protect against multiple threads
    if (tx_mutex_get (&mux->tx_mutex, WAIT_TX_MUTEX) != TX_SUCCESS)
        return EBADF;

    if (mux->state == STATE_DISCONNECTED)
    {
        tx_mutex_put (&mux->tx_mutex);
        return EBADF;
    }

    // Force all channls to close
    for (i = 0; i < mux->chan_count; i++)
    {
        gsm_mux_close (MUX_MINOR(mux-mux_ports, i));
        
        if (mux->channels[i].state == STATE_CONNECTED)
        {
            // Send channel disconnect command
            frame.address = MAKE_ADDR(mux->channels[i].dlci);
            frame.control = CONTROL_DISC|CONTROL_PF;
            send_frame (mux, &frame, 2, TRUE);
        }
    }

    // Send mux close down command
    frame.address = MAKE_ADDR(DLCI_MCC);
    frame.control = CONTROL_UIH;
    mcc->type = TYPE_CLD|CR;
    mcc->length = MAKE_LENGTH(0);
    send_frame (mux, &frame, 2, TRUE);

    // Wait for transmits to complete
    ioctl (mux->fd, TCDRAIN, NULL);
    
    mux->state = STATE_DISCONNECTED;
    mux->fd = -1;
    
    tx_mutex_put (&mux->tx_mutex);
    return EXIT_SUCCESS;
}

//
// Open a multiplexer port
//
static int mux_open (mux_info_t *mux, mux_param_t *param)
{
    mux_frame_t frame;
    int i, ret;

    if (param == NULL || param->chan_count > MUX_CHANS)
        return EINVAL;
    
    if (mux->state != STATE_DISCONNECTED)
        return EBUSY;

    // Save params
    mux->fd = param->mux_fd;
    mux->chan_count = param->chan_count;
    for (i = 0; i < mux->chan_count; i++)
    {
        mux->channels[i].state = STATE_CONNECTING;
        mux->channels[i].dlci = param->chan_dlci[i];
    }
    
    // Send mux start command
    ret = write (mux->fd, MUX_START, strlen (MUX_START));
    if (ret < 0)
        return getErrno();
    tx_thread_sleep (WAIT_START);
    
    tx_mutex_create (&mux->tx_mutex, "GSM mux TX mutex", TX_INHERIT);

    tx_block_pool_create (&mux->rx_pool, "GSM mux RX pool",
        sizeof(mux_frame_t), mux->rx_bufs, BUF_RX_SIZE);
        
    tx_block_allocate (&mux->rx_pool, (void **)&mux->rx.frame, 0);

    mux->state = STATE_CONNECTING;
    mux->rx.state = 0;
        
    // Start receive thread
    tx_thread_delete (&mux->thread);
    tx_thread_create (&mux->thread, "GSM mux thread", 
        gsm_mux_thread, (ULONG)mux, mux->stack, sizeof mux->stack, 
        14, 14, 10, TX_AUTO_START);

    // Connect mux control channel
    frame.address = MAKE_ADDR(DLCI_MCC);
    frame.control = CONTROL_SABM|CONTROL_PF;
    send_frame (mux, &frame, 0, TRUE);

    // Wait for all channels to connect
    for (i = 0; i < mux->chan_count; i++)
    {
        channel_info_t *chan = &mux->channels[i];
        ULONG flags;

        while (chan->state == STATE_CONNECTING)
        {
            if (tx_event_flags_get (&chan->events, EVENT_CONNECT, 
                    TX_OR_CLEAR, &flags, WAIT_CONNECT) != TX_SUCCESS ||
                chan->state == STATE_DISCONNECTED ||
                mux->state == STATE_DISCONNECTED)
            {
                DPRINTF ("chan %d connect error", chan->dlci);
                mux_close (mux);
                return EIO;
            }
        }
    }
    
    mux->state = STATE_OPEN;
    return EXIT_SUCCESS;
}

/*
 * Function: gsm_mux_ioctl()
 *
 * Description: Perform am I/O control operation on a channel
 *
 * Called by: DDI ioctl
 *
 * Input : channel - mux and channel number
 *         request - request type
 *         arg - request parameter
 *
 * Return: EINVAL
 *         ENOSYS
 *         EXIT_SUCCESS
 */
int gsm_mux_ioctl (int channel, int request, char *arg)
{
    mux_info_t *mux;
    channel_info_t *chan;
    int ret;

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
        return EINVAL;

    mux = &mux_ports[INDEX_PORT(channel)];
    chan = &mux->channels[INDEX_CHAN(channel)];
        
    switch (request)
    {
    case MUX_OPEN:
        return mux_open (mux, (mux_param_t *)arg);
        
    case MUX_CLOSE:
        return mux_close (mux);
        
    case TCSETA:
        // Ignore this
        return EXIT_SUCCESS;
        
    case TCGETA:
    case TCDRAIN:
    case TCTXDONE:
        // Send to port driver
        return ioctl (mux->fd, request, arg);

    case TCSETSIGNALS:
        if (chan->state != STATE_OPEN)
            return EBADF;

        // Send modem status command
        chan->status.mstat &= ~(MS_RTS|MS_DTR);
        chan->status.mstat |= (int)arg & (MS_RTS|MS_DTR);
        return send_msc_frame (mux, chan);

    case TCFLUSH:
        if ((int)arg & TCIFLUSH)
        {
            // Get receive mutex
            if (tx_mutex_get (&chan->rx_mutex, WAIT_RX_MUTEX) != TX_SUCCESS)
                return EIO;
            
            // Flush receive queue
            while (chan->rx_head)
            {
                // Remove frame from receive queue
                mux_frame_t *frame = chan->rx_head;
                chan->rx_head = frame->next;
                tx_block_release (frame);
            }
            
            chan->rx_offset = 0;
            chan->rx_pending = 0;
            chan->buffers.rxbuf = 0;
            
            // Release flow control
            if (chan->status.estat & EV_IPS)
            {
                chan->status.estat &= ~EV_IPS;
                send_msc_frame (mux, chan);
            }
            
            tx_mutex_put (&chan->rx_mutex);
        }
        return EXIT_SUCCESS;

    case TCGETBUFFERS:
        // Get transmit count from port driver
        ret = ioctl (mux->fd, request, arg);
        if (ret == 0)
        {
            // Get channel receive count
            struct serial_buffer_t *bufs = (struct serial_buffer_t *)arg;
            bufs->rxbuf = chan->buffers.rxbuf;
        }
        return ret;

    case TCGETCOUNTERS:
        memcpy (arg, &chan->counters, sizeof chan->counters);
        return EXIT_SUCCESS;

    case TCGETSTATS:
        memcpy (arg, &chan->status, sizeof chan->status);
        return EXIT_SUCCESS;

    case TCGETSIGCHANGE:
        memcpy (arg, &chan->sigchange, sizeof chan->sigchange);
        return EXIT_SUCCESS;

    default:
        return ENOSYS;
    }
}

#if NETWORK_STACK == TRECK
/*
 * Function: gsm_mux_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 gsm_mux_poll (int channel, int insert_remove, int *checkMask, ulong context)
{
    mux_info_t *mux;
    channel_info_t *chan;
    struct serial_buffer_t buffers;
    int ret = 0;

    if (INDEX_PORT(channel) > MUX_PORTS || INDEX_CHAN(channel) > MUX_CHANS)
        return EINVAL;

    mux = &mux_ports[INDEX_PORT(channel)];
    chan = &mux->channels[INDEX_CHAN(channel)];
    if (chan->state != STATE_OPEN)
        return EBADF;

    // save checkMask and context.  We only support one select at a time
    // in this driver.
    if (insert_remove == NA_SELECT_INSERT)
    {
        chan->select_context = context;
        chan->select_checkMask = *checkMask;
    }
    else
    {
        chan->select_context = 0;
        chan->select_checkMask = 0;
    }

    // Get pending byte counts
    gsm_mux_ioctl (channel, TCGETBUFFERS, (char *)&buffers);

    if ((*checkMask & NA_SELECT_READ) && buffers.rxbuf > 0)
            ret |= NA_SELECT_READ;
    if ((*checkMask & NA_SELECT_WRITE) && buffers.txbuf < TX_MARK)
            ret |= NA_SELECT_WRITE;

    *checkMask = ret;
    return 0;
}
#endif /* NETWORK_STACK == TRECK */

//
// Internal functions
//

// CRC-8 lookup table in LSBit-first order
#define POLY    0xe0
static uint8 crctable[256];

//
// Initialize CRC table
//
void init_crc (void)
{
    int i, j;
    for (i = 0; i < 256; i++)
    {
        uint8 crc = i;
        for (j = 0; j < 8; j++)
        {
            if (crc & 1)
                crc = (crc >> 1) ^ POLY;
            else
                crc >>= 1;
        }
        crctable[i] = crc;
    }
}

//
// Calculate CRC
//
static uint8 calc_crc (uint8 *data, int length)
{
    uint8 crc = 0xff;

    while (length-- > 0)
        crc = crctable[crc ^ *data++];

    return ~crc;
}

//
// Print frame for debugging
//
static void dump_frame (char *label, mux_frame_t *frame)
{
    if (frame->control != CONTROL_UIH)
        DPRINTF ("%s addr=0x%x control=0x%x", label, frame->address, frame->control);
    else if (GET_DLCI(frame->address) == DLCI_MCC)
    {
        if ((frame->info[0]|CR) == (TYPE_MSC|CR))
            DPRINTF ("%s MSC dlci=0x%x sigs=0x%x", label, frame->info[2], frame->info[3]);
        else
            DPRINTF ("%s MCC type=0x%x", label, frame->info[0]);
    }
}

//
// Send a mux frame.
// control, address, and information fields must be set in frame.
// block is TRUE to block on write to mux port.
//
static int send_frame (mux_info_t *mux, mux_frame_t *frame, int length, BOOL block)
{
    int written = 0;
    int ret;

    if (length < 0 || length > N1)
        return EINVAL;

    frame->flag = FLAG;
    frame->length = MAKE_LENGTH(length);
    frame->info[length] = calc_crc (&frame->address, 3);
    frame->info[length+1] = FLAG;
    length += 6;

    dump_frame ("mux TX", frame);
    
    // Get transmit mutex
    ret = tx_mutex_get (&mux->tx_mutex, WAIT_TX_MUTEX);
    if (ret != TX_SUCCESS)
        return EIO;

    while (written < length)
    {
        ret = write (mux->fd, &frame->flag, length-written);
        if (ret > 0)
            written += ret;
        else if (getErrno() == EAGAIN && (written > 0 || block))
            // Can't use select() here because serial drivers allow only 
            // one select() at a time.  The receive thread uses select().
            tx_thread_sleep (WAIT_TX_POLL);
        else
            break;
    }

    tx_mutex_put (&mux->tx_mutex);

    if (written == length)
        return 0;
    
    ret = EAGAIN;
    if (ret != EAGAIN)
        DPRINTF ("errno=%d", ret);
        
    return ret;
}

//
// Send modem status command
//
static int send_msc_frame (mux_info_t *mux, channel_info_t *chan)
{
    mux_frame_t frame;
    msc_frame_t *msc = (msc_frame_t *) frame.info;

    // Create modem status command frame
    frame.address = MAKE_ADDR(DLCI_MCC);
    frame.control = CONTROL_UIH;
    msc->command = TYPE_MSC|CR;
    msc->length = MAKE_LENGTH(2);
    msc->dlci = MAKE_ADDR(chan->dlci);
    
    msc->v24_signals = EA;
    if (chan->status.estat & EV_IPS)
        msc->v24_signals |= V24_FC;
    if (chan->status.mstat & MS_DTR)
        msc->v24_signals |= V24_RTC;
    if (chan->status.mstat & MS_RTS)
        msc->v24_signals |= V24_RTR;

    return send_frame (mux, &frame, sizeof *msc, TRUE);
}

//
// Find the mux channel information for an address
//
static channel_info_t *find_channel (mux_info_t *mux, uint8 address)
{
    int dlci, i;

    dlci = GET_DLCI(address);
    
    // Mux control channel
    if (dlci == DLCI_MCC)
    	return NULL;

    for (i = 0; i < mux->chan_count; i++)
        if (mux->channels[i].dlci == dlci)
            return &mux->channels[i];

    return NULL;
}

//
// Receive a mux control channel frame
//
static void rx_mcc_frame (mux_info_t *mux, mux_frame_t *frame)
{
    mcc_frame_t *mcc = (mcc_frame_t *) frame->info;
    msc_frame_t *msc;
    channel_info_t *chan;
    int i;

    switch (mcc->type)
    {
        // Commands sent by modem (CR bit set)
        
        // Mux close down
        case TYPE_CLD|CR:
            mcc->type = TYPE_CLD;
            send_frame (mux, frame, GET_LENGTH(frame->length), TRUE);
            mux_close (mux);
            break;

        // Echo the same frame contents
	case TYPE_TEST|CR:
            mcc->type = TYPE_TEST;
            send_frame (mux, frame, GET_LENGTH(frame->length), TRUE);
	    break;

        // Modem status command
	case TYPE_MSC|CR:
	    msc = (msc_frame_t *) mcc;
            chan = find_channel (mux, msc->dlci);
	    if (chan)
	    {
                // Flow control asserted
                if (msc->v24_signals & V24_FC)
                    chan->status.estat |= EV_OPS;
                else
                {
                    // Flow control released, unblock gsm_mux_write()
                    chan->status.estat &= ~EV_OPS;
                    tx_event_flags_set (&chan->events, EVENT_TX, TX_OR);
                }
                    
                chan->status.mstat &= ~(MS_DSR|MS_CTS|MS_RI|MS_DCD);
                if (msc->v24_signals & V24_RTC)
                    chan->status.mstat |= MS_DSR;
                if (msc->v24_signals & V24_RTR)
                    chan->status.mstat |= MS_CTS;
                if (msc->v24_signals & V24_RING)
                    chan->status.mstat |= MS_RI;
                if (msc->v24_signals & V24_DCD)
                    chan->status.mstat |= MS_DCD;
	    }
            
            // Send response with same data
            mcc->type = TYPE_MSC;
            send_frame (mux, frame, GET_LENGTH(frame->length), TRUE);
	    break;

        // Responses to commands sent to modem
        
        // Ignore these
        case TYPE_CLD:
        case TYPE_MSC:
            break;

        // Response to test version command, mux control channel is connected
        case TYPE_TEST:
            if (mux->state == STATE_CONNECTING)
            {
                mux->state = STATE_CONNECTED;
                
                // Connect each channel
                for (i = 0; i < mux->chan_count; i++)
                {
                    frame->address = MAKE_ADDR(mux->channels[i].dlci);
                    frame->control = CONTROL_SABM|CONTROL_PF;
                    send_frame (mux, frame, 0, TRUE);
                }
            }
            break;

        // Modem doesn't support command
        case TYPE_NSC:
            DPRINTF ("nonsupported cmd=0x%x", mcc->value[0]);
            break;

        // This driver doesn't support command
	default:
            DPRINTF ("unknown type=0x%x, frame dropped", mcc->type);
            
            // Send unsupported command response
            mcc->value[0] = mcc->type;
            mcc->type = TYPE_NSC;
            mcc->length = MAKE_LENGTH(1);
            send_frame (mux, frame, 3, TRUE);
	    break;
    }
}

//
// Receive an information frame on a mux channel
//
static void rx_info_frame (mux_info_t *mux, channel_info_t *chan, mux_frame_t *frame)
{
    mux_frame_t *new_frame;
    int ret;

    // Drop frames while channel is closed
    if (chan->state != STATE_OPEN)
        return;

    // Allocate a new frame to replace the received frame.
    // Block the receive thread until a buffer is available.
    ret = tx_block_allocate (&mux->rx_pool, (void **)&new_frame, TX_WAIT_FOREVER);
    if (ret != TX_SUCCESS)
    {
	DPRINTF ("alloc error=%d, frame dropped", ret);
	return;
    }

    // Get receive mutex
    ret = tx_mutex_get (&chan->rx_mutex, WAIT_RX_MUTEX);
    if (ret != TX_SUCCESS)
    {
	DPRINTF ("mutex error=%d, frame dropped", ret);
        tx_block_release (new_frame);
	return;
    }

    chan->rx_pending++;
    chan->buffers.rxbuf   += GET_LENGTH(frame->length);
    chan->counters.rbytes += GET_LENGTH(frame->length);

    // Add frame to receive queue
    frame->next = NULL;
    if (chan->rx_head)
    	chan->rx_tail->next = frame;
    else
    	chan->rx_head = frame;
    chan->rx_tail = frame;

    tx_mutex_put (&chan->rx_mutex);

    // Replace frame with newly allocated one
    mux->rx.frame = new_frame;

    // If queue getting full, flow control the channel
    if (chan->rx_pending >= RX_HIGH_MARK)
    {
        chan->status.estat |= EV_IPS;
        send_msc_frame (mux, chan);
    }

    // Notify gsm_mux_read() that data was received
    if (chan->rx_pending == 1)
	tx_event_flags_set (&chan->events, EVENT_RX, TX_OR);
}

//
// Receive a frame from the mux port
//
static void rx_frame (mux_info_t *mux, mux_frame_t *frame)
{
    channel_info_t *chan = NULL;

    dump_frame ("mux RX", frame);
    
    if (GET_DLCI(frame->address) != DLCI_MCC)
    {
        chan = find_channel (mux, frame->address);
        if (chan == NULL)
        {
            DPRINTF ("unknown addr=0x%x, frame dropped", frame->address);
            return;
        }
    }

    switch (frame->control)
    {
        // Channel open acknowledged
	case CONTROL_UA|CONTROL_PF:
            if (chan && chan->state == STATE_CONNECTING)
            {
                chan->state = STATE_CONNECTED;
                tx_event_flags_set (&chan->events, EVENT_CONNECT, TX_OR);
            }
            else if (chan == NULL && mux->state == STATE_CONNECTING)
            {
                mcc_frame_t *mcc = (mcc_frame_t *) frame->info;

                // Send Siemens version negotiation frame.
                // All modems will echo this back in a test response.
                frame->address = MAKE_ADDR(DLCI_MCC);
                frame->control = CONTROL_UIH;
                mcc->type = TYPE_TEST|CR;
                mcc->length = MAKE_LENGTH(TEST_VERSION_SIZE);
                memcpy (mcc->value, TEST_VERSION, TEST_VERSION_SIZE);
                send_frame (mux, frame, TEST_VERSION_SIZE+2, TRUE);
            }
	    break;

        // Channel disconnected
        case CONTROL_DM:
        case CONTROL_DM|CONTROL_PF:
            if (chan)
            {
                gsm_mux_close (MUX_MINOR(mux-mux_ports, chan-mux->channels));
                chan->state = STATE_DISCONNECTED;
                tx_event_flags_set (&chan->events, EVENT_CONNECT, TX_OR);
            }
            else
                mux->state = STATE_DISCONNECTED;
	    break;

        // Information frame
	case CONTROL_UIH:
	    if (chan)
                rx_info_frame (mux, chan, frame);
            else
		rx_mcc_frame (mux, frame);
	    break;

	default:
            DPRINTF ("unknown control=0x%x, frame dropped", frame->control);
	    break;
    }
}

//
// Process a block of serial data from the mux port.
// This function does serial receive framing.
//
static void rx_data (mux_info_t *mux, uint8 *dp, int length)
{
    rx_state_t *rx = &mux->rx;

    // Process each byte of data
    for (; length > 0; dp++, length--)
	switch (rx->state)
	{
	    // Looking for start flag
	    case 0:
		if (*dp == FLAG)
		    rx->state++;
		break;

	    // Address field
	    case 1:
                // Ignore repeated flags
		if (*dp == FLAG)
		    break;

		rx->frame->address = *dp;
		rx->state++;
		break;

	    // Control field
	    case 2:
		rx->frame->control = *dp;
		rx->state++;
		break;

	    // Length field
	    case 3:
		rx->frame->length = *dp;
		rx->length = GET_LENGTH(*dp);
		rx->index = 0;

		if (rx->length > N1)
		{
		    DPRINTF ("frame size error");
		    rx->state = 0;
		}
		else
		    rx->state++;
		break;

	    // Frame data or checksum byte
	    case 4:
		if (rx->index < rx->length)
		    rx->frame->info[rx->index++] = *dp;

		// Have frame data plus checksum byte
		else if (calc_crc (&rx->frame->address, 3) == *dp)
		    rx->state++;
		else
		{
		    DPRINTF ("checksum error");
		    rx->state = 0;
		}
		break;

	    // End flag
	    case 5:
		if (*dp == FLAG)
		{
		    rx_frame (mux, rx->frame);
		    rx->state = 1;
		}
		else
		{
		    DPRINTF ("framing error");
		    rx->state = 0;
		}
		break;

	    default:
		rx->state = 0;
		break;
	}
}

//
// Mux port receive and event handling thread
//
static void gsm_mux_thread (ULONG param)
{
    mux_info_t *mux = (mux_info_t *) param;
    channel_info_t *chan;
    fd_set fds;
    struct timeval timeout;
    uint8 buf[512];
    int ret, i, diff;

    while (mux->state != STATE_DISCONNECTED)
    {
        // Wait for receive data, time out to check for events
	FD_ZERO(&fds);
	FD_SET(mux->fd, &fds);
        timeout.tv_sec = 0;
        timeout.tv_usec = 1000*WAIT_RX_POLL;
        
        ret = select (mux->fd+1, &fds, NULL, NULL, &timeout);
        if (ret < 0)
        {
            DPRINTF ("select error=%d", getErrno());
            break;
        }
        
        // Process receive data
        while (ret > 0)
        {
            ret = read (mux->fd, buf, sizeof(buf));
            if (ret > 0)
                rx_data (mux, buf, ret);
        }
        
        if (ret < 0 && getErrno() != EAGAIN)
        {
            if (getErrno() != EBADF)  // Normal exit
                DPRINTF ("read error=%d", getErrno());
            break;
        }
        
        // Check each active channel for events
        for (i = 0; i < mux->chan_count; i++)
        {
            chan = &mux->channels[i];
            if (chan->state != STATE_OPEN)
                continue;

            // Handle changes in control line state
            diff = chan->status.mstat ^ chan->prev_mstat;
            if (diff & MS_DSR) {
                chan->sigchange.dsr_counter++;
                serl_cb_event(chan->serial_channel, DSR_DELTA);
            }
            if (diff & MS_DCD) {
                chan->sigchange.dcd_counter++;
                serl_cb_event(chan->serial_channel, DCD_DELTA);
            }
            if (diff & MS_RI) {
                chan->sigchange.ri_counter++;
                serl_cb_event(chan->serial_channel, RI_DELTA);
            }
            if (diff & MS_CTS) {
                chan->sigchange.cts_counter++;
                serl_cb_event(chan->serial_channel, CTS_DELTA);
            }
            if (diff & MS_RTS) {
                chan->sigchange.rts_counter++;
                serl_cb_event(chan->serial_channel, RTS_DELTA);
            }
            if (diff & MS_DTR) {
                chan->sigchange.dtr_counter++;
                serl_cb_event(chan->serial_channel, DTR_DELTA);
            }

            // Handle new receive data
            if (chan->buffers.rxbuf > chan->prev_rxbuf) {
                serl_cb_event(chan->serial_channel, RX_DATA);
#if NETWORK_STACK == TRECK
                if (chan->select_checkMask & NA_SELECT_READ)
                    tfDeviceSelectNotify(NA_SELECT_READ, chan->select_context);
#endif
            }

#if NETWORK_STACK == TRECK
            // Handle space available to transmit data
            if (chan->buffers.txbuf < TX_MARK) {
                if (chan->select_checkMask & NA_SELECT_WRITE)
                    tfDeviceSelectNotify(NA_SELECT_WRITE, chan->select_context);
            }
#endif

            // Save for next event check
            chan->prev_mstat = chan->status.mstat;
            chan->prev_rxbuf = chan->buffers.rxbuf;
        }
    }

    // Force mux to close if thread exits on an error
    mux_close (mux);
    
    tx_mutex_delete (&mux->tx_mutex);
    tx_block_pool_delete (&mux->rx_pool);
}
