//==========================================================================
//
//        ser_test_protocol.c
//
//        Serial device driver testing protocol
//
//==========================================================================
//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 or (at your option) any later version.
//
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with eCos; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
//
// As a special exception, if other files instantiate templates or use macros
// or inline functions from this file, or you compile this file and link it
// with other works to produce a work based on this file, this file does not
// by itself cause the resulting work to be covered by the GNU General Public
// License. However the source code for this file must still be made available
// in accordance with section (3) of the GNU General Public License.
//
// This exception does not invalidate any other reasons why a work based on
// this file might be covered by the GNU General Public License.
//
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
// at http://sources.redhat.com/ecos/ecos-license/
// -------------------------------------------
//####ECOSGPLCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     jskov
// Contributors:  jskov, gthomas
// Date:          1999-03-17
// Description:   Protocol implementation used to test eCos serial devices.
//                Relies on ser_filter to be present on the host side to
//                respond to test requests.
// 
// To Do:
//  o Clean up.
//  o Clean up config change magic.
//  o Figure out how to handle kernel dependency
//    : without kernel, no timeout. Without timeout, no filter auto detection.
//
//####DESCRIPTIONEND####

#include <pkgconf/system.h>
#include <pkgconf/io.h>
#include <pkgconf/io_serial.h>

#include <cyg/io/io.h>
#include <cyg/io/devtab.h>
#include <cyg/io/ttyio.h>
#include <cyg/infra/diag.h>
#include <cyg/infra/cyg_ass.h>

#include <cyg/hal/hal_intr.h>           // for reclaiming interrupt vector

//----------------------------------------------------------------------------
// Definition of which device to run tests on on various platforms.

#define NA_MSG "No test device specified"

// Cleaned up drivers will export the testing parameters via CDL.
// When all drivers are changed, replace the TEST_ macros throughout
// with the CYGPRI_ equivalents.
#ifdef CYGPRI_SER_TEST_CRASH_ID
# define TEST_CRASH_ID CYGPRI_SER_TEST_CRASH_ID
# define TEST_SER_DEV  CYGPRI_SER_TEST_SER_DEV
# define TEST_TTY_DEV  CYGPRI_SER_TEST_TTY_DEV
#endif

// Note that CYGPRI_SER_TEST_OVERRIDE_INT_1 and CYGPRI_SER_TEST_OVERRIDE_INT_2
// may also be exported. These identify interrupts that should be reclaimed
// from the ROM monitor before the test is started.

#if defined(CYGPKG_HAL_POWERPC_MBX)                          \
    && defined(CYGPKG_HAL_QUICC)                 \
    && defined(CYGPKG_IO_SERIAL_POWERPC_QUICC_SMC)                 \
    && defined(CYGPKG_IO_SERIAL_POWERPC_QUICC_SMC_SMC1)
# define TEST_CRASH_ID "ppcmbx"
# define TEST_SER_DEV CYGDAT_IO_SERIAL_POWERPC_QUICC_SMC_SMC1_NAME
# if defined(CYGPKG_IO_SERIAL_TTY_TTY1)
#  define TEST_TTY_DEV CYGDAT_IO_SERIAL_TTY_TTY1_DEV
# endif
#endif
#if defined(CYGPKG_HAL_MN10300_AM31_STDEVAL1)           \
    && defined(CYGPKG_IO_SERIAL_MN10300)                \
    && defined(CYGPKG_IO_SERIAL_MN10300_SERIAL2)
# define TEST_CRASH_ID "am31st"
# define TEST_SER_DEV CYGDAT_IO_SERIAL_MN10300_SERIAL2_NAME
# if defined(CYGPKG_IO_SERIAL_TTY_TTY2)
#  define TEST_TTY_DEV CYGDAT_IO_SERIAL_TTY_TTY1_DEV
# endif
#endif
#if defined(CYGPKG_HAL_MN10300_AM33_STB)                \
    && defined(CYGPKG_IO_SERIAL_MN10300)                \
    && defined(CYGPKG_IO_SERIAL_MN10300_SERIAL0)
# define TEST_CRASH_ID "am33st"
# define TEST_SER_DEV CYGDAT_IO_SERIAL_MN10300_SERIAL0_NAME
# if defined(CYGPKG_IO_SERIAL_TTY_TTY0)
#  define TEST_TTY_DEV CYGDAT_IO_SERIAL_TTY_TTY0_DEV
# endif
#endif

// We can't rely on haldiag for ser_filter detection - it may not define
// a working character reading function.
#ifndef   TEST_SER_DEV
# define  SER_NOP_TEST
# define  TTY_NOP_TEST
# define  TEST_SER_DEV "/dev/null"
# define  TEST_TTY_DEV "/dev/null"
#else
# ifndef  TEST_TTY_DEV
#  define TTY_NOP_TEST
# define  TEST_TTY_DEV "/dev/null"
# endif
#endif

#ifndef TEST_CRASH_ID
#define TEST_CRASH_ID "......"
#endif

//----------------------------------------------------------------------------
// Crash types
// Eventually this will be moved into a separate header file so a script
// can read the definitions and use the output formats/codes to analyze
// test results. For now we just keep it here...

// FAILCODE:<tttttt:cccc:[optional data, separated by :]!>
//  tttttt: 6 letter target code
//  cccc:   crash code (16bit hex value)

#define TEST_CRASH(__h, __code, __msg, args...)                         \
    CYG_MACRO_START                                                     \
    cyg_uint32 __len = 1;                                               \
    /* Try to flush remaining input */                                  \
    cyg_thread_delay(50);                                               \
    cyg_io_get_config(__h, CYG_IO_GET_CONFIG_SERIAL_INPUT_FLUSH,        \
                      0, &__len);                                       \
    diag_printf("FAILCODE:<" TEST_CRASH_ID ":%04x:" __code, ## args);   \
    diag_printf("!>\n");                                                \
    CYG_FAIL(__msg);                                                    \
    hang();                                                             \
    CYG_MACRO_END

// Target IO
#define TEST_CRASH_IO                     0x0000
#define TEST_CRASH_IO_READ                "%d",            0x0001
#define TEST_CRASH_IO_WRITE               "%d",            0x0002
#define TEST_CRASH_IO_DRAIN               "%d",            0x0003
#define TEST_CRASH_IO_GET_CFG             "%d",            0x0004
#define TEST_CRASH_IO_SET_CFG             "%d",            0x0005

// Target
#define TEST_CRASH_CRC                    0x0010
#define TEST_CRASH_CRC_CHAR               "%02x",          0x0011
#define TEST_CRASH_CRC_BAD                "%08x:%08x",     0x0012
#define TEST_CRASH_CRC_HOST               "",              0x0013

// Protocol errors
#define TEST_CRASH_PROT                   0x1000
#define TEST_CRASH_PROT_BIN_MODE          "%d",            0x1080
#define TEST_CRASH_PROT_TEXT              "%d",            0x1100

#define TEST_CRASH_HOST_xx                0xf000
#define TEST_CRASH_HOST_TIMEOUT           "%d:%d:%d:%d",      0xf000
        // command#, read invocation#, expected, actual
#define TEST_CRASH_HOST_CRC_BAD           "%d:%08x:%08x:%d:%02x:%02x", 0xf010
        // command#, expected CRC, actual, index, expected char, actual
#define TEST_CRASH_HOST_DUPLEX_BAD        "%d:%d:%02x:%02x",     0xf020
        // command#, index, expected char, actual

//----------------------------------------------------------------------------
// The data in buffer and the cmd buffer
#ifndef IN_BUFFER_SIZE
# define IN_BUFFER_SIZE 1024
#endif
cyg_uint8 in_buffer[IN_BUFFER_SIZE];

char cmd_buffer[128];

//----------------------------------------------------------------------------
// Some types specific to the testing protocol.
typedef enum {
    MODE_NO_ECHO = 0,
    MODE_EOP_ECHO,
    MODE_DUPLEX_ECHO
} cyg_mode_t;

typedef enum {
    TEST_RETURN_OK = ENOERR,
    TEST_RETURN_NA
} cyg_test_return_t;

typedef struct ser_cfg {
    cyg_serial_baud_rate_t    baud_rate;
    cyg_serial_word_length_t  data_bits;
    cyg_serial_stop_bits_t    stop_bits;
    cyg_serial_parity_t       parity;
    cyg_uint32                flags;
    // etc...
} cyg_ser_cfg_t;

typedef enum {
    OPT_SERIAL_DEBUG = 0,
    OPT_VERBOSE_LEVEL
} cyg_option_t;

typedef enum {
    _NONE = 0,
    PROTOCOL_PROGRESS,
    PROTOCOL_DATA,
} cyg_verbosity_level_t;


// A few predifined option macros. Use after test_ping().
#define TEST_OPTIONS(__handle, __array) \
  test_options(__handle, sizeof(__array)/8, __array)

#define TEST_HOST_DEBUG(__handle)                               \
    CYG_MACRO_START                                             \
    cyg_uint32 __options[] = {OPT_SERIAL_DEBUG, 1};             \
    test_options((__handle), sizeof(__options)/8,               \
                 __options);                                    \
    CYG_MACRO_END

#define TEST_HOST_PROGRESS(__handle)                            \
    CYG_MACRO_START                                             \
    cyg_uint32 __options[] =                                    \
        {OPT_SERIAL_DEBUG, 1,                                   \
         OPT_VERBOSE_LEVEL, PROTOCOL_PROGRESS};                 \
    test_options((__handle), sizeof(__options)/8,               \
                 __options);                                    \
    CYG_MACRO_END

#define TEST_HOST_DATA(__handle)                                \
    CYG_MACRO_START                                             \
    cyg_uint32 __options[] =                                    \
        {OPT_SERIAL_DEBUG, 1,                                   \
         OPT_VERBOSE_LEVEL, PROTOCOL_DATA};                     \
    test_options((__handle), sizeof(__options)/8,               \
                 __options);                                    \
    CYG_MACRO_END

//----------------------------------------------------------------------------
// A few predefined configurations. These must all be valid for any
// given target until change_config is behaving correctly.
cyg_ser_cfg_t test_configs[] = {
#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_9600)
    { CYGNUM_SERIAL_BAUD_9600, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_14400)
#if !defined(CYGPKG_HAL_MN10300_AM31) &&    \
    !defined(CYGPKG_HAL_MN10300_AM33)
    { CYGNUM_SERIAL_BAUD_14400, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif
#endif

    { CYGNUM_SERIAL_BAUD_19200, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_38400)
    { CYGNUM_SERIAL_BAUD_38400, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_57600)
#if !defined(CYGPKG_HAL_MN10300_AM33)
    { CYGNUM_SERIAL_BAUD_57600, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_115200)
#if !defined(CYGPKG_HAL_MN10300_STDEVAL1)
    { CYGNUM_SERIAL_BAUD_115200, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_PARITY_EVEN)
    // One stop bit, even parity
    { CYGNUM_SERIAL_BAUD_19200, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_1, CYGNUM_SERIAL_PARITY_EVEN,
      CYGNUM_SERIAL_FLOW_NONE },
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_PARITY_EVEN)
#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_STOP_2)
    // Two stop bits, even parity
    { CYGNUM_SERIAL_BAUD_19200, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_2, CYGNUM_SERIAL_PARITY_EVEN,
      CYGNUM_SERIAL_FLOW_NONE },
#endif
#endif

#if (0 == CYGINT_IO_SERIAL_TEST_SKIP_STOP_2)
    // Two stop bits, no parity
    { CYGNUM_SERIAL_BAUD_19200, CYGNUM_SERIAL_WORD_LENGTH_8, 
      CYGNUM_SERIAL_STOP_2, CYGNUM_SERIAL_PARITY_NONE,
      CYGNUM_SERIAL_FLOW_NONE },
#endif
};

//----------------------------------------------------------------------------
// Macros to help extract values from the argument string.
// Note: This is probably not an ideal solution, but it was easy to make :)

#define INIT_VALUE(__args)                      \
    unsigned int v;                             \
    char *__ptr1, *__ptr2 = (__args)

#define SET_VALUE(__slot)                       \
do {                                            \
    __ptr1 = index(__ptr2, (int) ':');          \
    if (__ptr1)                                 \
        *__ptr1 = 0;                            \
    v = atoi(__ptr2);                           \
    __ptr2 = __ptr1+1;                          \
    (__slot) = v;                               \
} while (0)


//----------------------------------------------------------------------------
// CRC magic - it's a bit of a hack for now.
// FIXME: standard definition?
#define ADD_CRC_BYTE(__crc, __c)                \
    CYG_MACRO_START                             \
    (__crc) = ((__crc) << 1) ^ (__c);           \
    CYG_MACRO_END

// FIXME: Hack to allow easy ASCII transfer.
#define FIX_CRC(__crc, __icrc)                  \
    CYG_MACRO_START                             \
    __icrc = (int) (__crc);                     \
    if (__icrc < 0)                             \
        __icrc = -__icrc;                       \
    CYG_MACRO_END

//----------------------------------------------------------------------------
// Macros for read/write to serial with error cheking.
static volatile cyg_uint32 r_stamp;
static volatile int aborted;

// This routine will be called if the read "times out"
static void
do_abort(void *handle)
{
    cyg_io_handle_t io_handle = (cyg_io_handle_t)handle;
    cyg_uint32 len = 1;  // Need something here
    cyg_io_get_config(io_handle, CYG_IO_GET_CONFIG_SERIAL_ABORT, 0, &len);
    aborted = 1;
}
#include "timeout.inl"

// Read with timeout (__t = timeout in ticks, int* __r = result)
#define Tcyg_io_read_timeout(__h, __d, __l, __t, __r)           \
    CYG_MACRO_START                                             \
    int __res;                                                  \
    r_stamp = timeout((__t), do_abort, (__h));                  \
    __res = cyg_io_read((__h), (__d), (__l));                   \
    if (ENOERR != __res && -EINTR != __res) {                   \
        TEST_CRASH(__h, TEST_CRASH_IO_READ,                     \
                   "cyg_io_read/timeout failed", __res);        \
    }                                                           \
    *(__r) = __res;                                             \
    untimeout(r_stamp);                                         \
    CYG_MACRO_END

#define Tcyg_io_read(__h, __d, __l)                     \
    CYG_MACRO_START                                     \
    int __res = cyg_io_read((__h), (__d), (__l));       \
    if (ENOERR != __res) {                              \
        TEST_CRASH(__h, TEST_CRASH_IO_READ,             \
                   "cyg_io_read failed", __res);        \
    }                                                   \
    CYG_MACRO_END

#define Tcyg_io_write(__h, __d, __l)                                    \
    CYG_MACRO_START                                                     \
    int __res;                                                          \
    cyg_uint32 __len = 1;                                               \
    __res = cyg_io_write((__h), (__d), (__l));                          \
    if (ENOERR != __res) {                                              \
        TEST_CRASH(__h, TEST_CRASH_IO_WRITE,                            \
                   "cyg_io_write failed", __res);                       \
    }                                                                   \
    __res = cyg_io_get_config((__h),                                    \
                              CYG_IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN,    \
                              0, &__len);                               \
    if (ENOERR != __res) {                                              \
        TEST_CRASH(__h, TEST_CRASH_IO_DRAIN,                            \
                   "DRAIN failed", __res);                              \
    }                                                                   \
    CYG_MACRO_END


//----------------------------------------------------------------------------
// Some libc like functions that are handy to have around.
static int
strlen(const char *c)
{
    int l = 0;
    while (*c++) l++;
    return l;
}

static char*
strcpy(char* dest, const char* src)
{
    char c;
    while ((c = *src++)) {
        *dest++ = c;
    }
    *dest = c;

    return dest;
}

static char*
itoa(char* dest, int v)
{
    char b[16];
    char* p = &b[16];

    *--p = 0;
    if (v) {
        while (v){
            *--p = (v % 10) + '0';
            v = v / 10;
        }
    } else
        *--p = '0';

    return strcpy(dest, p);
}

#define min(_a, _b) ((_a) < (_b)) ? (_a) : (_b)

void
hang(void)
{
    while (1);
}

//-----------------------------------------------------------------------------
// Configuration changing function.
//
// First change to the new config and back again to determine if the driver
// can handle the config.
// If not, return error.
//
// Then query the host for its capability to use the config:
// Format out:
//  "@CONFIG:<baud rate code>:<#data bits>:<#stop bits>:<parity on/off>:<flow control code>!"
// Format in:
//  OK/ER
//
// On ER, return error.
//
// On OK, change to the new configuration. Resynchronize with the host:
//  Target waits for host to send S(ync) 
//     [host will delay at least .1 secs after changing baud rate so the 
//      line has time to settle.]
//
//  When receiving S(ync), target replies OK to the host which then
//  acknowledges with D(one).
//
//  Host can also send R(esync) which means it didn't receieve the OK. If
//  so the target resends its S(ync) message.
//
// If the synchronization has not succeeded within 1 second
// (configurable in the protocol), both host and target will revert to
// the previous configuration and attempt to synchronize again. If
// this fails, this call will hang and the host will consider the test
// a failure.
//
// To Do:
//  Host&protocol currently only supports:
//   - no/even parity
int
change_config(cyg_io_handle_t handle, cyg_ser_cfg_t* cfg)
{
    cyg_serial_info_t old_cfg, new_cfg;
    const char cmd[] = "@CONFIG:";
    char reply[2];
    cyg_uint32 msglen;
    int res;
    cyg_uint32 len;
    char *p1;

    // Prepare the command.
    p1 = &cmd_buffer[0];
    p1 = strcpy(p1, &cmd[0]);
    p1 = itoa(p1, cfg->baud_rate);
    *p1++ = ':';
    p1 = itoa(p1, cfg->data_bits);
    *p1++ = ':';
    p1 = itoa(p1, cfg->stop_bits);
    *p1++ = ':';
    p1 = itoa(p1, cfg->parity);
    *p1++ = ':';
    p1 = itoa(p1, cfg->flags);
    *p1++ = '!';
    *p1 = 0;                            // note: we may append to this later

    // Tell user what we're up to.
    CYG_TEST_INFO((char *)&cmd_buffer[1]);

    // Change to new config and then back to determine if the driver likes it.
    len = sizeof(old_cfg);
    res = cyg_io_get_config(handle, CYG_IO_GET_CONFIG_SERIAL_INFO, 
                            &old_cfg, &len);
    res = cyg_io_get_config(handle, CYG_IO_GET_CONFIG_SERIAL_INFO, 
                            &new_cfg, &len);

    if (res != ENOERR) {
        TEST_CRASH(handle, TEST_CRASH_IO_GET_CFG, 
                   "Can't get serial config", res);
    }

    new_cfg.baud = cfg->baud_rate;
    new_cfg.word_length = cfg->data_bits;
    new_cfg.stop = cfg->stop_bits;
    new_cfg.parity = cfg->parity;
    new_cfg.flags = cfg->flags;

    res = cyg_io_set_config(handle, CYG_IO_SET_CONFIG_SERIAL_INFO, 
                            &new_cfg, &len);
    cyg_thread_delay(10);  // Some chips don't like changes to happen to fast...

    // Driver didn't like it. It will not have changed anything, so it's
    // safe to return now.
    if (ENOERR != res) {
        // Let user know that the config was skipped due to the target.
        const char txt_tskipped[] = "- skipped by target!";
        p1 = strcpy(p1, txt_tskipped);
        *p1 = 0;
        CYG_TEST_INFO(&cmd_buffer[1]);
        return res;
    }

    // Succeeded. Change back to the original config so we can communicate
    // with the host.
    res = cyg_io_set_config(handle, CYG_IO_SET_CONFIG_SERIAL_INFO, 
                            &old_cfg, &len);
    cyg_thread_delay(10); // Some chips don't like changes to happen to fast...

    if (res != ENOERR) {
        TEST_CRASH(handle, TEST_CRASH_IO_SET_CFG, 
                   "Can't set serial config", res);
    }

    // Send command to host and read host's reply.
    msglen = strlen(&cmd_buffer[0]);
    Tcyg_io_write(handle, &cmd_buffer[0], &msglen);
    msglen = 2;
    Tcyg_io_read(handle, &reply[0], &msglen);

    // Did host accept configuration?
    if ('O' != reply[0] || 'K' != reply[1]) {
        // Let user know that the config was skipped due to the host.
        const char txt_hskipped[] = "- skipped by host!";
        p1 = strcpy(p1, txt_hskipped);
        *p1 = 0;
        CYG_TEST_INFO(&cmd_buffer[1]);
        diag_printf("Host didn't accept config (%02x, %02x).\n",
                    reply[0], reply[1]);

        res = ENOSUPP;
        return res;
    }

    // Now change config and wait for host to send us a S(ync)
    // character.  
    // Loop until protocol exchange completed. This may hang (as seen
    // from the host), but only when we get totally lost, in which
    // case there's not much else to do really. In this case the host
    // will consider the test a FAIL.
    len = sizeof(new_cfg);
    res = cyg_io_set_config(handle, CYG_IO_SET_CONFIG_SERIAL_INFO, 
                            &new_cfg, &len);
    cyg_thread_delay(10);  // Some chips don't like changes to happen to fast...
    if (res != ENOERR) {
        TEST_CRASH(handle, TEST_CRASH_IO_SET_CFG, 
                   "Can't set serial config/2", res);
    }

    {
        int change_succeeded = 0;
        int using_old_config = 0;
        char in_buf[1];
        cyg_uint32 len;
        int saw_host_sync;

        for (;;) {
            aborted = 0;                    // global abort flag

            // FIXME: Timeout time needs to be configurable, and needs to
            // be sent to the host before getting here. That would allow
            // changing the timeout by just rebuilding the test - without
            // changing the host software.
            saw_host_sync = 0;
            r_stamp = timeout(100, do_abort, handle);
            while(!aborted) {
                len = 1;
                in_buf[0] = 0;
                res = cyg_io_read(handle, in_buf, &len);
                if (ENOERR != res && -EINTR != res) {
                    // We may have to reset the driver here if the fail
                    // was due to a framing or parity error.
                    break;
                }
                if ('R' == in_buf[0]) {
                    // Resync - host didn't see our message. Try again.
                    saw_host_sync = 0;
                } else if ('S' == in_buf[0] && !saw_host_sync) {
                    // In sync - reply to host if we haven't already
                    char ok_msg[2] = "OK";
                    cyg_uint32 ok_len = 2;
                    Tcyg_io_write(handle, ok_msg, &ok_len);
                    saw_host_sync = 1;
                } else if ('D' == in_buf[0] && saw_host_sync) {
                    // Done - exchange completed.
                    change_succeeded = 1;
                    break;
                }
            }
            untimeout(r_stamp);

            if (change_succeeded) {
                // If we had to revert to the old configuration, return error.
                if (using_old_config)
                    return -EIO;
                else
                    return ENOERR;
            }

            // We didn't synchronize with the host. Due to an IO error?
            if (ENOERR != res && -EINTR != res) {
                // We may have to reset the driver if the fail was due to
                // a framing or parity error.
            }

            // Revert to the old configuration and try again.
            len = sizeof(old_cfg);
            res = cyg_io_set_config(handle, CYG_IO_SET_CONFIG_SERIAL_INFO, 
                                    &old_cfg, &len);
            cyg_thread_delay(10);  // Some chips don't like changes to happen to fast...
            if (res != ENOERR) {
                TEST_CRASH(handle, TEST_CRASH_IO_SET_CFG,
                           "Can't set serial config/3", res);
            }
            using_old_config = 1;
        }

    }
}


//-----------------------------------------------------------------------------
// Host sends CRC in decimal ASCII, terminated with !
int
read_host_crc(cyg_io_handle_t handle)
{
    int crc;
    cyg_uint32 len;
    cyg_uint8 ch;

    crc = 0;
    while (1) {
        len = 1;
        Tcyg_io_read(handle, &ch, &len);
        if ('!' == ch)
            break;

        if (!((ch >= '0' && ch <= '9'))){
            TEST_CRASH(handle, TEST_CRASH_CRC_CHAR,
                       "Illegal CRC format from host", ch);
        }

        crc = crc*10 + (ch - '0');
    }

    return crc;
}

//---------------------------------------------------------------------------
// Test binary data transmission.
// Format out:
//  "@BINARY:<byte size>:<mode>!"
// Format in:
//  <checksum>!<#size bytes data>
// For echo modes, also:
//     Format out:
//      <#size bytes data>
//     Format in:
//      OK/ER
// Format out:
//  DONE
//
//  The last DONE allows the host to eat bytes if target is sending too many.
//
// Mode:
//   MODE_NO_ECHO:  
//       Just receive data and verify CRC.
//   MODE_EOP_ECHO:
//       Receive data, verify CRC, resend data.
//       Expect OK/ER reply from host when done.
//   MODE_DUPLEX_ECHO:
//       Receive data, echo data, verify CRC.
//       Expect OK/ER reply from host when done.
//
// Note:
//   Using diag_printf while talking with the host may cause some funky
//   errors (bytes from the host side being lost!?!?)
//
// To Do:
//   MODE_DUPLEX_ECHO:
//     The current implementation is simple and may not stress the
//     driver enough. Also, it's command packet format doesn't match
//     that of the other modes.

cyg_test_return_t
test_binary(cyg_io_handle_t handle, int size, cyg_mode_t mode)
{
    const char cmd[] = "@BINARY:";
    cyg_uint32 msglen;
    cyg_uint32 xcrc;
    int icrc, host_crc;
    char *p1;
    cyg_int8 host_status = 'O';         // host is happy by default

    // Verify that the test can be run with available ressources.
    if (MODE_EOP_ECHO == mode && size > IN_BUFFER_SIZE)
        return TEST_RETURN_NA;

    // Prepare and send the command.
    p1 = &cmd_buffer[0];
    p1 = strcpy(p1, &cmd[0]);
    p1 = itoa(p1, size);
    *p1++ = ':';
    p1 = itoa(p1, mode);
    *p1++ = '!';
    *p1++ = 0;

    CYG_TEST_INFO(&cmd_buffer[1]);
  
    msglen = strlen(&cmd_buffer[0]);
    Tcyg_io_write(handle, &cmd_buffer[0], &msglen);

    // Get CRC back.
    host_crc = read_host_crc(handle);

    // Depending on mode, start reading data.
    xcrc = 0;
    switch (mode) {
    case MODE_NO_ECHO:
    {
        // Break transfers into chunks no larger than the buffer size.
        int tx_len, i;
        cyg_uint32 chunk_len;
        while (size > 0) {
            chunk_len = min(IN_BUFFER_SIZE, size);
            tx_len = chunk_len;
            size -= chunk_len;

            Tcyg_io_read(handle, &in_buffer[0], &chunk_len);

            for (i = 0; i < tx_len; i++) {
                ADD_CRC_BYTE(xcrc, in_buffer[i]);
            }
        }

        // Reply that we have completed the test.
        {
            const char msg_done[] = "DONE";

            chunk_len = strlen(&msg_done[0]);
            Tcyg_io_write(handle, &msg_done[0], &chunk_len);
        }

    }
    break;
    case MODE_EOP_ECHO:
    {
        // We have already checked that the in buffer is large enough.
        int i, tx_len;
        cyg_uint32 chunk_len;
        chunk_len = tx_len = size;
        Tcyg_io_read(handle, &in_buffer[0], &chunk_len);

        for (i = 0; i < tx_len; i++) {
            ADD_CRC_BYTE(xcrc, in_buffer[i]);
        }

        // Echo data back.
        chunk_len = size;
        Tcyg_io_write(handle, &in_buffer[0], &chunk_len);
        
        // Now read host side's status
        chunk_len = 2;
        Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
        host_status = in_buffer[0];

        // Reply that we have completed the test.
        {
            const char msg_done[] = "DONE";

            chunk_len = strlen(&msg_done[0]);
            Tcyg_io_write(handle, &msg_done[0], &chunk_len);
        }
    }
    break;
    case MODE_DUPLEX_ECHO:
    {
        cyg_uint32 chunk_len;
        int block_size = 64;

        // This is a simple implementation (maybe too simple).
        // Host sends 4 packets with the same size (64 bytes atm).
        // Target echoes in this way:
        //  packet1 -> packet1
        //  packet2 -> packet2, packet2
        //  packet3 -> packet3
        //  packet4 -> /dev/null
        //
        // The reads/writes are interleaved in a way that should ensure
        // the target out buffer to be full before the target starts to read
        // packet3. That is, the target should be both receiving (packet3)
        // and sending (packet2) at the same time.

        while (size--) {
            // block_size -> block_size
            chunk_len = block_size;
            Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
            chunk_len = block_size;
            Tcyg_io_write(handle, &in_buffer[0], &chunk_len);

            // block_size -> 2 x block_size
            chunk_len = block_size;
            Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
            chunk_len = block_size;
            Tcyg_io_write(handle, &in_buffer[0], &chunk_len);
            chunk_len = block_size;
            Tcyg_io_write(handle, &in_buffer[0], &chunk_len);

            // block_size -> block_size
            chunk_len = block_size;
            Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
            chunk_len = block_size;
            Tcyg_io_write(handle, &in_buffer[0], &chunk_len);
        
            // block_size -> 0
            chunk_len = block_size;
            Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
        }

        // Kill the CRC. Leave packet verification to the host for now.
        xcrc = host_crc = 0;

        // Now read host side's status
        chunk_len = 2;
        Tcyg_io_read(handle, &in_buffer[0], &chunk_len);
        host_status = in_buffer[0];

        // Reply that we have completed the test.
        {
            const char msg_done[] = "DONE";

            chunk_len = strlen(&msg_done[0]);
            Tcyg_io_write(handle, &msg_done[0], &chunk_len);
        }
    }
    break;
    default:
        TEST_CRASH(handle, TEST_CRASH_PROT_BIN_MODE, 
                   "Unknown mode", mode);
        break;
    }


    // Verify that the CRC matches the one from the host.
    FIX_CRC(xcrc, icrc);
    if (host_crc != icrc) {
        TEST_CRASH(handle, TEST_CRASH_CRC_BAD,
                   "Input CRC failed", icrc, host_crc);
    }

    // Verify that the host is happy with the data we echoed.
    if ('O' != host_status) {
        TEST_CRASH(handle, TEST_CRASH_CRC_HOST, 
                   "Output CRC failed");
    }

    CYG_TEST_PASS("Binary test completed");
    return TEST_RETURN_OK;
}

//---------------------------------------------------------------------------
// Test transformations on text transmissions
// Format out:
//  "@TEXT:<mode>!<4 bytes binary checksum><C string>"
// Format in:
//  "<C string>"
//  OK/ER
//
// Mode:
//   MODE_EOP_ECHO:
//       Receive data, verify CRC, resend data.
//       Expect OK/ER reply from host when done.
//   MODE_DUPLEX_ECHO:
//       Receive data, echo data, verify CRC.
//       Expect OK/ER reply from host when done.
//
cyg_test_return_t
test_text(cyg_io_handle_t handle, cyg_mode_t mode, const char* s_base,
           const char* s_out, const char* s_in)
{
    return TEST_RETURN_NA;
}

//---------------------------------------------------------------------------
// Send PING to host, verifying the filter's presence.
// Format out:
//  "@PING:<crash id>!"
// Format in:
//  "OK"
// or
//  No response if directly connected to GDB.
//
// This call only returns if the ser_filter is listening. Otherwise it
// sends N/A and hangs.
void
test_ping(cyg_io_handle_t handle)
{
    char msg[] = "@PING:" TEST_CRASH_ID "!";
    char msg2[] = "\n";
    cyg_uint32 msglen = strlen(msg);
    int res;

    msglen = strlen(msg);
    Tcyg_io_write(handle, msg, &msglen);

    // Now read host side's status
    msglen = 2;
    Tcyg_io_read_timeout(handle, &in_buffer[0], &msglen, 100, &res);
    if (ENOERR == res && 'O' == in_buffer[0] && 'K' == in_buffer[1])
        return;

    msglen = strlen(msg2);
    Tcyg_io_write(handle, msg2, &msglen);

    CYG_TEST_NA("No host side testing harness detected.");
}


//---------------------------------------------------------------------------
// Send OPT to host, setting options in the filter.
// Format out:
//  "@OPT:option1,value1:...:optionN,valueN!"
// Format in:
//  "OK"
//
// Only integer values can be used. Any option not recognized by the
// filter will be silently ignored.
void
test_options(cyg_io_handle_t handle, int count, cyg_uint32* options)
{
    const char cmd[] = "@OPT:";
    cyg_uint32 msglen;
    char *p1;

    // Prepare and send the command.
    p1 = &cmd_buffer[0];
    p1 = strcpy(p1, &cmd[0]);
    while(count--) {
        p1 = itoa(p1, *options++);      // option
        *p1++ = ':';
        p1 = itoa(p1, *options++);      // value
        *p1++ = ':';
    }
    *(p1-1) = '!';
    *p1++ = 0;

    CYG_TEST_INFO(&cmd_buffer[1]);
  
    msglen = strlen(&cmd_buffer[0]);
    Tcyg_io_write(handle, &cmd_buffer[0], &msglen);

    // Now read host side's status
    msglen = 2;
    Tcyg_io_read(handle, &in_buffer[0], &msglen);
}


//---------------------------------------------------------------------------
// Some helper functions to get a test started.
void
test_open_ser( cyg_io_handle_t* handle )
{
#if defined(CYGPKG_IO_SERIAL_DEVICES) && !defined(SER_NOP_TEST)
    Cyg_ErrNo res;

    if (cyg_test_is_simulator)
        CYG_TEST_NA("Cannot run from simulator");

#if defined(HAL_VSR_SET_TO_ECOS_HANDLER)
# if defined(CYGPRI_SER_TEST_OVERRIDE_INT_1)
    HAL_VSR_SET_TO_ECOS_HANDLER(CYGPRI_SER_TEST_OVERRIDE_INT_1, NULL);
# endif
# if defined(CYGPRI_SER_TEST_OVERRIDE_INT_2)
    HAL_VSR_SET_TO_ECOS_HANDLER(CYGPRI_SER_TEST_OVERRIDE_INT_2, NULL);
# endif
#endif

    res = cyg_io_lookup(TEST_SER_DEV, handle);
    if (res != ENOERR) {
        CYG_TEST_FAIL_FINISH("Can't lookup " TEST_SER_DEV);
    }
#else
    CYG_TEST_NA(NA_MSG);
#endif
}

void
test_open_tty( cyg_io_handle_t* handle )
{
#if defined(CYGPKG_IO_SERIAL_TTY) && !defined(TTY_NOP_TEST)
    Cyg_ErrNo res;

    if (cyg_test_is_simulator)
        CYG_TEST_NA("Cannot run from simulator");

#if defined(HAL_VSR_SET_TO_ECOS_HANDLER)
# if defined(CYGPRI_SER_TEST_OVERRIDE_INT_1)
    HAL_VSR_SET_TO_ECOS_HANDLER(CYGPRI_SER_TEST_OVERRIDE_INT_1, NULL);
# endif
# if defined(CYGPRI_SER_TEST_OVERRIDE_INT_2)
    HAL_VSR_SET_TO_ECOS_HANDLER(CYGPRI_SER_TEST_OVERRIDE_INT_2, NULL);
# endif
#endif

    res = cyg_io_lookup(TEST_TTY_DEV, handle);
    if (res != ENOERR) {
        CYG_TEST_FAIL_FINISH("Can't lookup " TEST_TTY_DEV);
    }
#else
    CYG_TEST_NA(NA_MSG);
#endif
}

//---------------------------------------------------------------------------
// end of ser_test_protocol.inl
