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