From: Eric Schott Date: Fri, 4 Jan 2019 00:55:59 +0000 (-0500) Subject: Add program to test flush (purge) functionality. X-Git-Tag: v1.5rc1~26 X-Git-Url: http://developer.intra2net.com/git/?p=libftdi;a=commitdiff_plain;h=41b114dab8a6a6324d56f9b4e04466316ae23e8a Add program to test flush (purge) functionality. --- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6155756..6fc3c09 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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 index 0000000..2d42aa4 --- /dev/null +++ b/examples/purge_test.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +/* Prevent deprecated messages when building library */ +#define _FTDI_DISABLE_DEPRECATED +#include + +#include // For baudcodes & linux UARTs +#include +#include +#include + + +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 \n" + "Copyright (C) 2009 Micah Dowty \n" + "Adapted for use with libftdi (C) 2010 Uwe Bonnes \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; +}