Add program to test flush (purge) functionality.
authorEric Schott <els6@psu.edu>
Fri, 4 Jan 2019 00:55:59 +0000 (19:55 -0500)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 18 Jan 2019 14:28:56 +0000 (15:28 +0100)
examples/CMakeLists.txt
examples/purge_test.c [new file with mode: 0644]

index 6155756..6fc3c09 100644 (file)
@@ -15,6 +15,7 @@ add_executable(baud_test baud_test.c)
 add_executable(stream_test stream_test.c)
 add_executable(eeprom eeprom.c)
 add_executable(async async.c)
+add_executable(purge_test purge_test.c)
 
 # Linkage
 target_link_libraries(simple ftdi1)
@@ -28,6 +29,7 @@ target_link_libraries(baud_test ftdi1)
 target_link_libraries(stream_test ftdi1)
 target_link_libraries(eeprom ftdi1)
 target_link_libraries(async ftdi1)
+target_link_libraries(purge_test ftdi1)
 
 # libftdi++ examples
 if( FTDIPP )
diff --git a/examples/purge_test.c b/examples/purge_test.c
new file mode 100644 (file)
index 0000000..2d42aa4
--- /dev/null
@@ -0,0 +1,674 @@
+/* purge_test.c
+ *
+ * Test for purge TX/RX functions.
+ *
+ * The chip must be wired to loop TX data to RX data (loopback).
+ *
+ * This program works with "standard" linux drivers and the FTDI1 library.
+ *
+ * Usage: purge_test [-b baud] [-i interface] [-n msg-size] [-N note] device-specifier
+ *   See usage below for more information on command usage.
+ *
+ * This program works well with the FT4231H which is newer and has large
+ * FIFOs. This program does not work well with FT232, either pre or post
+ * switching the SIO_RESET_PURGE_TX/SIO_RESET_PURGE_RX values.
+ *
+ * This needs testing with other devices, which I do not have.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <signal.h>
+#include <errno.h>
+/* Prevent deprecated messages when building library */
+#define _FTDI_DISABLE_DEPRECATED
+#include <ftdi.h>
+
+#include <termios.h>           // For baudcodes & linux UARTs
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+static struct ftdi_context *ftdi = NULL;
+static int dev_fd = -1;
+static char * dev_string = NULL;
+static int latency_specified = 0;
+static int latency = 5;
+static int baud = 9600;
+static int baud_code = -1;
+static enum ftdi_interface interface = INTERFACE_A;
+static int msg_size = 80;
+static int broken_purge_test = 0;
+
+static const int latency_min = 2;
+static const int latency_max = 255;
+
+static volatile long long usec_test_start;
+
+
+
+static int ascii2int(const char * str, const char * pgm_name);
+static int baud_2_baud_code(int baud);
+static long int char_cnt_2_usec(int char_count);
+static long int drain();
+static int flush(int queue_selector);
+static long long int get_time_usec();
+
+static const int flushQueueSelector[] = {
+    TCIFLUSH, TCOFLUSH, TCIOFLUSH }; /* See /usr/include/bits/termios.h */
+static const char * flushTestName[] = {
+  "Input-only", "Output-only", "Input+Output" };
+static const char * expected[] = {
+    "last portion of message",
+    "first portion of message",
+    "mid-message characters",
+};
+
+
+static const char * chip_types[] = {
+    "am",
+    "bm",
+    "2232C",
+    "R",
+    "2232H",
+    "4232H",
+    "232H",
+    "230X",
+};
+
+#ifndef ARRAY_SIZE
+#  define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#endif
+
+
+
+/**********************************************************************
+ */
+static void
+usage(const char *argv0)
+{
+   fprintf(stderr,
+           "Usage: %s [options...] device-specifier\n"
+           "Flush test for UARTS.\n"
+          " with loopback connector\n"
+           "    [-b baud]        baud rate (e.g., 300, 600, 1200, ...230400)\n"
+           "    [-i {a|b|c|d}]   FTDI interface for chips which have multiple UARTS\n"
+          "    [-l latency]     Latency (%d..%d)\n"
+           "    [-n msg-size]    Number of bytes in test message\n"
+           "    [-N note]        Note for the output\n"
+          "    [-P]             Use broken libftdi1 purge methods (over new flush)\n"
+           "\n"
+           "    device-specifier String specifying the UART.  If the first character\n"
+          "                     is the '/' character, the program assumes a Linux UART\n"
+          "                     is to be tested and the string would be something like\n"
+          "                     '/dev/ttyS0' or '/dev/ttyUSB0'. Otherwise, the program\n"
+          "                     assumes an FTDI device is being tested with the FTDI1\n"
+          "                     library. The device-specifier must be a string\n"
+          "                     accepted by the ftdi_usb_open_string function. An\n"
+          "                     example would be 'i:0x0403:0x6011[:index]'.\n"
+          "\n"
+          "NOTE: To function correctly, this program requires a loopback connector\n"
+          "      attached to the UART under test.\n"
+           "\n"
+           "Adapted from stream_test.c 2018. Eric Schott <els6@psu.edu>\n"
+           "Copyright (C) 2009 Micah Dowty <micah@navi.cx>\n"
+           "Adapted for use with libftdi (C) 2010 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>\n",
+           argv0, latency_min, latency_max);
+   exit(1);
+}
+
+
+/**********************************************************************
+ */
+int main(int argc, char **argv)
+{
+    int c, i;
+    int option_index;
+    int test;
+    unsigned char * msg;
+    unsigned char * retMsg;
+    char * note = NULL;
+    char * note_default = NULL;
+    size_t retMsgSize;
+    long int msg_xmit_time_us;
+    static struct option long_options[] = {{NULL},};
+
+    while ((c = getopt_long(argc, argv, "n:b:i:l:N:P", long_options, &option_index)) !=- 1)
+        switch (c)
+        {
+        case -1:
+            break;
+        case 'b':
+            baud = ascii2int(optarg, argv[0]);
+            break;
+        case 'i':
+            if (optarg == NULL || strlen(optarg) != 1)
+                usage(argv[0]);
+            switch (optarg[0])
+            {
+            case 'a':
+            case 'A':
+                interface = INTERFACE_A;
+                break;
+
+            case 'b':
+            case 'B':
+                interface = INTERFACE_B;
+                break;
+
+            case 'c':
+            case 'C':
+                interface = INTERFACE_C;
+                break;
+
+            case 'd':
+            case 'D':
+                interface = INTERFACE_D;
+                break;
+
+            default:
+                usage(argv[0]);
+            }
+            break;
+        case 'l':
+            latency = ascii2int(optarg, argv[0]);
+            if (latency < latency_min || latency > latency_max)
+            {
+             fprintf(stderr, "latency [-l] must be an integer in the range %d..%d\n",
+                       latency_min, latency_max);
+                usage(argv[0]);
+            }
+           latency_specified = 1;
+            break;
+        case 'n':
+            msg_size = ascii2int(optarg, argv[0]);
+            if (msg_size < 1)
+            {
+                fprintf(stderr, "msg-size [-n] must be an integer greater than 0\n");
+                usage(argv[0]);
+            }
+            break;
+        case 'N':
+            note = optarg;
+            break;
+       case 'P':
+           broken_purge_test = 1;
+           break;
+        default:
+            usage(argv[0]);
+        }
+
+    if (optind == argc)
+        usage(argv[0]);
+
+    if (optind == argc - 1)
+    {
+        // Exactly one extra argument- a dump file
+        dev_string = argv[optind];
+    }
+    else if (optind < argc)
+    {
+        // Too many extra args
+        usage(argv[0]);
+    }
+
+    baud_code = baud_2_baud_code(baud);
+    if (baud_code < 1)
+    {
+        fprintf(stderr, "Invalid baud [-b]\n");
+        usage(argv[0]);
+    }
+
+    if (dev_string[0] == '/')
+    {
+        struct termios termios;
+
+       if (latency_specified) {
+         fprintf(stderr, "Latency (-l) option not support on this device; ignored\n");
+       }
+
+       if (broken_purge_test) {
+         fprintf(stderr, "Broken-purge (-P) option not support with Linux kernel driver\n");
+         return EXIT_FAILURE;
+       }
+
+        dev_fd = open(dev_string, O_NOCTTY | O_RDWR);
+        if (dev_fd < 0)
+        {
+            fprintf(stderr, "Error opening Linux device \"%s\": %s\n",
+                    dev_string, strerror(errno));
+            return EXIT_FAILURE;
+        }
+
+        if (! isatty(dev_fd))
+        {
+            fprintf(stderr, "Not a TTY device: \"%s\"\n", dev_string);
+            return EXIT_FAILURE;
+        }
+
+        if (tcgetattr(dev_fd, &termios) == -1)
+        {
+            fprintf(stderr, "Error getting TTY attributes for \"%s\": %s\n",
+                    dev_string, strerror(errno));
+            return EXIT_FAILURE;
+        }
+
+       note_default = "Linux kernel driver";
+
+        cfmakeraw(&termios);
+
+        termios.c_cflag &=
+            ~(CSTOPB | CRTSCTS);
+
+        termios.c_cflag &= ~CSIZE;
+        termios.c_cflag |= CS8;
+
+        cfsetspeed(&termios, baud_code);
+
+        termios.c_cflag |=
+            CLOCAL;
+
+        termios.c_cc[VMIN] = 1;        // Character at a time input
+        termios.c_cc[VTIME] = 0;       // with blocking
+
+        if (tcsetattr(dev_fd, TCSAFLUSH, &termios) == -1) {
+            fprintf(stderr, "Error setting TTY attributes for \"%s\": %s\n", 
+                    dev_string, strerror(errno));
+            return EXIT_FAILURE;
+        }
+    }
+    else
+    {
+
+        if ((ftdi = ftdi_new()) == 0)
+        {
+            fprintf(stderr, "ftdi_new failed\n");
+            return EXIT_FAILURE;
+        }
+
+        if (ftdi_set_interface(ftdi, interface) < 0)
+        {
+            fprintf(stderr, "ftdi_set_interface failed\n");
+            ftdi_free(ftdi);
+            return EXIT_FAILURE;
+        }
+
+        if (ftdi_usb_open_string(ftdi, dev_string) < 0)
+        {
+            fprintf(stderr,"Error opening ftdi device \"%s\": %s\n", dev_string,
+                    ftdi_get_error_string(ftdi));
+            ftdi_free(ftdi);
+            return EXIT_FAILURE;
+        }
+
+       if(ftdi_set_latency_timer(ftdi, (unsigned char) latency))
+       {
+           if (latency_specified &&
+                (ftdi->type == TYPE_AM || ftdi->type == TYPE_232H)) {
+               fprintf(stderr, "Latency (-l) option not support on this device; ignored\n");
+           } else if (ftdi->type != TYPE_AM && ftdi->type != TYPE_232H) {
+                fprintf(stderr,"Error setting latency for ftdi device \"%s\" (%d): %s\n",
+                        dev_string, ftdi->type, ftdi_get_error_string(ftdi));
+                ftdi_free(ftdi);
+                return EXIT_FAILURE;
+            }
+       }
+
+        if (ftdi_set_line_property2(ftdi, BITS_8, STOP_BIT_1, NONE, BREAK_OFF) < 0)
+        {
+            fprintf(stderr,"Error setting line properties ftdi device \"%s\": %s\n", dev_string,
+                    ftdi_get_error_string(ftdi));
+            ftdi_free(ftdi);
+            return EXIT_FAILURE;
+        }
+
+        if (ftdi_set_baudrate(ftdi, baud) < 0)
+        {
+            fprintf(stderr,"Error setting baud rate for ftdi device \"%s\": %s\n", dev_string,
+                    ftdi_get_error_string(ftdi));
+            ftdi_free(ftdi);
+            return EXIT_FAILURE;
+        }
+
+        if (ftdi_setflowctrl(ftdi, SIO_DISABLE_FLOW_CTRL))
+        {
+            fprintf(stderr,"Error setting flow control for ftdi device \"%s\": %s\n", dev_string,
+                    ftdi_get_error_string(ftdi));
+            ftdi_free(ftdi);
+            return EXIT_FAILURE;
+        }
+
+       if (broken_purge_test)
+           note_default = "libftdi w/ deprecated purge";
+       else
+           note_default = "libftdi w/ new flush methods";
+    }
+
+    printf("Purge (tcflush) test for device %s\n", dev_string);
+    printf("Note: %s\n", (note ? note : note_default));
+
+    if (dev_fd < 0)
+    {
+        if (ftdi->type >0 && ftdi->type < ARRAY_SIZE(chip_types))
+            printf("FTDI chip type is %d (%s)\n",
+                   ftdi->type, chip_types[ftdi->type]);
+        else
+            printf("FTDI chip type is %d (unknown)\n", ftdi->type);
+    }
+
+    printf("# purge_test" );
+    for (c = 1; c < argc; ++c)
+    {
+        const char *p = argv[c];
+        while (*p != '\0')
+        {
+            if (*p == ' ')
+                break;
+            ++p;
+        }
+        if (*p == ' ')
+            printf(" '%s'", argv[c]);
+        else
+            printf(" %s", argv[c]);
+    }
+    printf("\n");
+
+    msg_xmit_time_us = char_cnt_2_usec(msg_size);
+    printf("%d chars at %d baud takes about %.0f ms to transmit\n", msg_size,
+           baud, msg_xmit_time_us * .001);
+
+    msg = malloc(msg_size + 1);
+    if (msg == NULL)
+    {
+        fprintf(stderr, "Could not allocate send message buffer\n");
+        return EXIT_FAILURE;
+    }
+
+    {
+        char dataChar = '0' + ((get_time_usec() / 1000) % 31);
+        char next = 'A';
+        for (i = 0; i < msg_size; ++i) {
+            if (dataChar == '`')
+            {
+                msg[i] = next++;
+                ++dataChar;
+            }
+            else
+                msg[i] = dataChar++;
+
+            if (dataChar > 'z') {
+                dataChar = '`';
+            }
+        }
+        msg[msg_size] = '\0';
+    }
+
+    printf("TX Message is \"%s\"\n", msg);
+
+    retMsgSize = 2 * msg_size;
+    retMsg = malloc(retMsgSize);
+    if (retMsg == NULL)
+    {
+        fprintf(stderr, "Could not allocate received message buffer\n");
+        return EXIT_FAILURE;
+    }
+
+    flush(TCIOFLUSH);
+
+    for (test = 0; test <= 2; ++test)
+    {
+        long long usec_delay;
+        long long usec_to_now;
+        int rc;
+
+        printf("\n********  Test purge %s; expect %s  ********\n"
+              "  --              Flushing UART\n",
+               flushTestName[test], expected[test]);
+        flush(TCIOFLUSH);
+        usleep(msg_xmit_time_us);
+        flush(TCIOFLUSH);
+        usleep(100000);
+
+        usec_test_start = get_time_usec();
+        if (dev_fd >= 0)
+            rc = write(dev_fd, msg, msg_size);
+        else
+            rc = ftdi_write_data(ftdi, msg, msg_size);
+
+        if (rc != msg_size)
+        {
+            fprintf(stderr, "Data write was short: %d: %s\n",
+                    rc, ftdi_get_error_string(ftdi));
+            exit(1);
+        }
+        usec_to_now = get_time_usec() - usec_test_start;
+        usec_delay = msg_xmit_time_us / 2 - usec_to_now;
+        if (usec_delay < 0)
+            usec_delay = 0;
+        printf("  -- %9.1f ms Write completes; delaying to TX midpoint (%.1f ms)\n", 
+               usec_to_now * .001, usec_delay * .001);
+        if (usec_delay > 0)
+            usleep(usec_delay);
+
+        printf("  -- %9.1f ms Issuing %s flush (purge)\n", 
+               (get_time_usec() - usec_test_start) * .001,
+               flushTestName[test]);
+        flush(flushQueueSelector[test]);
+
+        printf("  -- %9.1f ms Calling drain to wait for transmit to complete\n", 
+               (get_time_usec() - usec_test_start) * .001);
+        drain();
+
+        usec_to_now = get_time_usec() - usec_test_start;
+
+        /* If input only flush, check drain time. */
+        if (flushQueueSelector[test] == TCIFLUSH &&
+            usec_to_now < (msg_xmit_time_us * 90ll) / 100ll)
+        {
+            usec_delay = (msg_xmit_time_us * 110ll) / 100ll - usec_to_now;
+            printf("  -- %9.1f ms Drain() completed too early; expected at least %.1f ms\n"
+                   "                  Delaying for %.1f ms\n", 
+                   usec_to_now * .001,
+                   ((msg_xmit_time_us * 90ll) / 100ll) * .001,
+                   usec_delay * .001);
+            usleep(usec_delay);
+        }
+        else
+       {
+            printf("  -- %9.1f ms Drain() reports completed; timing OK; delaying for 4 bytes\n", 
+                   (get_time_usec() - usec_test_start) * .001);
+            usleep(char_cnt_2_usec(4));
+        }
+
+        printf("  -- %9.1f ms Reading data.\n",
+               (get_time_usec() - usec_test_start) * .001);
+        if (dev_fd >= 0)
+            rc = read(dev_fd, retMsg, retMsgSize);
+        else
+            rc = ftdi_read_data(ftdi, retMsg, retMsgSize - 1);
+
+        usec_to_now = get_time_usec() - usec_test_start;
+        if (rc < 0)
+        {
+            fprintf(stderr, "  -- %9.1f ms Read returned error %s\n",
+                    usec_to_now * .001,
+                    (dev_fd >= 0 ? strerror(errno) : ftdi_get_error_string(ftdi)));
+            exit(1);
+        }
+        retMsg[rc] = '\0';
+        printf("  -- %9.1f ms Read returns %d bytes; msg: \"%s\"\n",
+               usec_to_now * .001, rc, retMsg);
+
+        usleep(char_cnt_2_usec(10));
+
+    }
+
+
+    if (dev_fd >= 0)
+    {
+        close(dev_fd);
+    }
+    else
+    {
+        ftdi_usb_close(ftdi);
+        ftdi_free(ftdi);
+    }
+
+    exit (0);
+}
+
+/**********************************************************************
+ */
+static int ascii2int(const char * str, const char * pgm_name)
+{
+    int rc;
+    char * endptr;
+    if (str == NULL || strlen(str) == 0)
+        usage(pgm_name);
+    rc = strtol(str, &endptr, 10);
+    if (endptr == str || *endptr != '\0')
+        usage(pgm_name);
+    return rc;
+}
+
+
+/**********************************************************************
+ */
+static struct Baud_Table {
+       int32_t baud, baud_code;
+} baud_table [] =
+{
+       { 50,     B50     },
+       { 75,     B75     },
+       { 110,    B110    },
+       { 134,    B134    },
+       { 150,    B150    },
+       { 200,    B200    },
+       { 300,    B300    },
+       { 600,    B600    },
+       { 1200,   B1200   },
+       { 1800,   B1800   },
+       { 2400,   B2400   },
+       { 4800,   B4800   },
+       { 9600,   B9600   },
+       { 19200,  B19200  },
+       { 38400,  B38400  },
+       { 57600,  B57600  },
+       { 115200, B115200 },
+       { 230400, B230400 },
+       { -1,     -1,     }
+};
+
+/**********************************************************************
+ */
+static int baud_2_baud_code(int baud)
+{
+    struct Baud_Table *p;
+
+    for (p = baud_table ; p->baud != -1; ++p) {
+        if (p->baud == baud)
+            break;
+    }
+    return p->baud_code;
+}
+
+
+static long int char_cnt_2_usec(int char_count)
+{
+    long long bits = 8 + 1 + 1;                 /* Number of bits in each character */
+    bits *= (char_count == 0 ? 1 : char_count); /* Total number of bits */
+    bits *= 1000000;                            /* Convert to us */
+    lldiv_t parts = lldiv(bits, baud);          /* Number of us for message */
+    return (parts.quot + 1);
+}
+
+
+static long int drain()
+{
+    long int rc = 0;
+    long long start_time = get_time_usec();
+    if (dev_fd >= 0)
+        rc = tcdrain(dev_fd);
+    else
+    {
+        long int sleep_interval = char_cnt_2_usec(10);
+        while (1) {
+            unsigned short modem_status = 0;
+            int rc = ftdi_poll_modem_status(ftdi, &modem_status);
+            if (rc < 0)
+                return -1;
+            if (modem_status & (1 << (6 + 8))) {
+                break;
+            }
+            usleep(sleep_interval);
+        }
+    }
+    if (rc < 0)
+        return rc;
+    usleep(char_cnt_2_usec(2));
+    return get_time_usec() - start_time;
+}
+
+
+static int flush(int queue_selector)
+{
+    int rc;
+    if (dev_fd >= 0)
+        rc = tcflush(dev_fd, queue_selector);
+    else if (! broken_purge_test)
+    {
+        switch (queue_selector) {
+
+        case TCIOFLUSH:
+            rc = ftdi_tcioflush(ftdi);
+            break;
+
+        case TCIFLUSH:
+            rc = ftdi_tciflush(ftdi);
+            break;
+
+        case TCOFLUSH:
+            rc = ftdi_tcoflush(ftdi);
+            break;
+
+        default:
+            errno = EINVAL;
+            return -1;
+        }
+    }
+    else
+    {
+        switch (queue_selector) {
+
+        case TCIOFLUSH:
+            rc = ftdi_usb_purge_buffers(ftdi);
+            break;
+
+        case TCIFLUSH:
+            rc = ftdi_usb_purge_rx_buffer(ftdi);
+            break;
+
+        case TCOFLUSH:
+            rc = ftdi_usb_purge_tx_buffer(ftdi);
+            break;
+
+        default:
+            errno = EINVAL;
+            return -1;
+        }
+    }
+
+    return rc;
+}
+
+
+static long long int get_time_usec()
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec * 1000000ll + tv.tv_usec;
+}