diff mbox series

[2/2] device_drivers/uart01: Add uart01 test

Message ID 20200327134707.4532-3-chrubis@suse.cz
State New
Headers show
Series Add needs_devices && basic uart test | expand

Commit Message

Cyril Hrubis March 27, 2020, 1:47 p.m. UTC
Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/kernel_misc                           |   5 +
 .../kernel/device-drivers/uart/.gitignore     |   1 +
 testcases/kernel/device-drivers/uart/Makefile |   4 +
 testcases/kernel/device-drivers/uart/uart01.c | 522 ++++++++++++++++++
 4 files changed, 532 insertions(+)
 create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
 create mode 100644 testcases/kernel/device-drivers/uart/Makefile
 create mode 100644 testcases/kernel/device-drivers/uart/uart01.c

Comments

Petr Vorel March 27, 2020, 3:11 p.m. UTC | #1
Hi Cyril,

> +uart01_9600 uart01 -b 9600
> +uart01_19200 uart01 -b 19200
> +uart01_38400 uart01 -b 38400
> +uart01_57600 uart01 -b 57600
> +uart01_115200 uart01 -b 115200
Tested-by: Petr Vorel <pvorel@suse.cz>
on all speeds.

Reviewed-by: Petr Vorel <pvorel@suse.cz>

Thanks for porting serialcheck.c to LTP.

Kind regards,
Petr
Cixi Geng March 28, 2020, 8:27 a.m. UTC | #2
Hi Cyril:
Thank you porting the serialcheck.c into LTP
I am sorry to find the serialcheck have not LOOPBACK mode support
the LOOPBACK mode is a better test than HW flow , because most machine's
uart have not connect the Rx & TX
in LOOPBACK mode. we test the uart port directly, So we can test one uart
port Rx and Tx functions at the same time .
here is the diff  serialcheck with loopback patch
So I'd prefer use loopback mode test the uart in case.
$ diff serialcheck.c serialcheck-with-loopback.c
14a15,16
> #define TIOCM_LOOP    0x8000
>
42a45
>     unsigned char loopback;
53a57
>     {"loopback",    'k', NULL,   0, "loopback mode", 0},
69a74
>         go->loopback = 0;
115a121,123
>     case 'k':
>         go->loopback = 1;
>         break;
316c324
<         ret = poll(&pfd, 1, 10 * 1000);
---
>         ret = poll(&pfd, 1, 100 * 1000);
421a430
>     unsigned int mcr;
489a499,511
>     if (opts.loopback) {
>         ret = ioctl(fd, TIOCMGET, &mcr);
>         if (ret < 0)
>             die("mcr get failed: %m\n");
>
>         mcr |= TIOCM_LOOP;
>
>         ret = ioctl(fd, TIOCMSET, &mcr);
>         if (ret < 0)
>             die ("mcr set failed: %m\n");
>
>     }
>
514a537,542
>     if(opts.loopback){
>         mcr &= ~(TIOCM_LOOP);
>         ret = ioctl(fd,TIOCMSET,&mcr);
>     }
>     if(ret)
>         printf("disabling loopback failed:%m\n");


Cyril Hrubis <chrubis@suse.cz> 于2020年3月27日周五 下午9:47写道:

> Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
> ---
>  runtest/kernel_misc                           |   5 +
>  .../kernel/device-drivers/uart/.gitignore     |   1 +
>  testcases/kernel/device-drivers/uart/Makefile |   4 +
>  testcases/kernel/device-drivers/uart/uart01.c | 522 ++++++++++++++++++
>  4 files changed, 532 insertions(+)
>  create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
>  create mode 100644 testcases/kernel/device-drivers/uart/Makefile
>  create mode 100644 testcases/kernel/device-drivers/uart/uart01.c
>
> diff --git a/runtest/kernel_misc b/runtest/kernel_misc
> index 7937c7bbf..a7f1d9b56 100644
> --- a/runtest/kernel_misc
> +++ b/runtest/kernel_misc
> @@ -13,3 +13,8 @@ zram01 zram01.sh
>  zram02 zram02.sh
>  zram03 zram03
>  umip_basic_test umip_basic_test
> +uart01_9600 uart01 -b 9600
> +uart01_19200 uart01 -b 19200
> +uart01_38400 uart01 -b 38400
> +uart01_57600 uart01 -b 57600
> +uart01_115200 uart01 -b 115200
> diff --git a/testcases/kernel/device-drivers/uart/.gitignore
> b/testcases/kernel/device-drivers/uart/.gitignore
> new file mode 100644
> index 000000000..9333e8db9
> --- /dev/null
> +++ b/testcases/kernel/device-drivers/uart/.gitignore
> @@ -0,0 +1 @@
> +uart01
> diff --git a/testcases/kernel/device-drivers/uart/Makefile
> b/testcases/kernel/device-drivers/uart/Makefile
> new file mode 100644
> index 000000000..1c90e5cd6
> --- /dev/null
> +++ b/testcases/kernel/device-drivers/uart/Makefile
> @@ -0,0 +1,4 @@
> +
> +top_srcdir     ?= ../../../..
> +include $(top_srcdir)/include/mk/testcases.mk
> +include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/kernel/device-drivers/uart/uart01.c
> b/testcases/kernel/device-drivers/uart/uart01.c
> new file mode 100644
> index 000000000..4647c55e3
> --- /dev/null
> +++ b/testcases/kernel/device-drivers/uart/uart01.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> +   Copyright (c) 2014 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> +   Copyright (c) 2020 Cyril Hrubis <chrubis@suse.cz>
> +
> + */
> +
> +#include <stdio.h>
> +#include <ctype.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <termios.h>
> +#include <stdarg.h>
> +#include <unistd.h>
> +#include <sys/mman.h>
> +#include <stdint.h>
> +#include <poll.h>
> +#include <sys/ioctl.h>
> +#include <linux/serial.h>
> +
> +#include "tst_test.h"
> +
> +static const char hex_asc[] = "0123456789abcdef";
> +#define hex_asc_lo(x)  hex_asc[((x) & 0x0f)]
> +#define hex_asc_hi(x)  hex_asc[((x) & 0xf0) >> 4]
> +
> +struct g_opt {
> +       char *uart_dev;
> +       char *file_trans;
> +       int baud_rate;
> +       unsigned int loops;
> +       unsigned char hwflow;
> +       unsigned char do_termios;
> +#define MODE_TX_ONLY    (1 << 0)
> +#define MODE_RX_ONLY    (1 << 1)
> +#define MODE_DUPLEX     (MODE_TX_ONLY | MODE_RX_ONLY)
> +       unsigned int mode;
> +       unsigned char *cmp_buff;
> +};
> +
> +static int vscnprintf(char *buf, size_t size, const char *fmt, va_list
> args)
> +{
> +       int i;
> +
> +       i = vsnprintf(buf, size, fmt, args);
> +
> +       if (i < (int)size)
> +               return i;
> +       if (size != 0)
> +               return size - 1;
> +       return 0;
> +}
> +
> +static int scnprintf(char *buf, size_t size, const char *fmt, ...)
> +{
> +       va_list args;
> +       int i;
> +
> +       va_start(args, fmt);
> +       i = vscnprintf(buf, size, fmt, args);
> +       va_end(args);
> +
> +       return i;
> +}
> +
> +
> +static void hex_dump_to_buffer(const void *buf, int len, int rowsize,
> +               int groupsize, char *linebuf, int linebuflen, int ascii)
> +{
> +       const uint8_t *ptr = buf;
> +       uint8_t ch;
> +       int j, lx = 0;
> +       int ascii_column;
> +
> +       if (rowsize != 16 && rowsize != 32)
> +               rowsize = 16;
> +
> +       if (!len)
> +               goto nil;
> +       if (len > rowsize)      /* limit to one line at a time */
> +               len = rowsize;
> +       if ((len % groupsize) != 0)     /* no mixed size output */
> +               groupsize = 1;
> +
> +       switch (groupsize) {
> +       case 8: {
> +               const uint64_t *ptr8 = buf;
> +               int ngroups = len / groupsize;
> +
> +               for (j = 0; j < ngroups; j++)
> +                       lx += scnprintf(linebuf + lx, linebuflen - lx,
> +                                       "%s%16.16llx", j ? " " : "",
> +                                       (unsigned long long)*(ptr8 + j));
> +               ascii_column = 17 * ngroups + 2;
> +               break;
> +               }
> +
> +       case 4: {
> +               const uint32_t *ptr4 = buf;
> +               int ngroups = len / groupsize;
> +
> +               for (j = 0; j < ngroups; j++)
> +                       lx += scnprintf(linebuf + lx, linebuflen - lx,
> +                                       "%s%8.8x", j ? " " : "", *(ptr4 +
> j));
> +               ascii_column = 9 * ngroups + 2;
> +               break;
> +               }
> +
> +       case 2: {
> +               const uint16_t *ptr2 = buf;
> +               int ngroups = len / groupsize;
> +
> +               for (j = 0; j < ngroups; j++)
> +                       lx += scnprintf(linebuf + lx, linebuflen - lx,
> +                                       "%s%4.4x", j ? " " : "", *(ptr2 +
> j));
> +               ascii_column = 5 * ngroups + 2;
> +               break;
> +               }
> +
> +       default:
> +               for (j = 0; (j < len) && (lx + 3) <= linebuflen; j++) {
> +                       ch = ptr[j];
> +                       linebuf[lx++] = hex_asc_hi(ch);
> +                       linebuf[lx++] = hex_asc_lo(ch);
> +                       linebuf[lx++] = ' ';
> +                       if (j == 7)
> +                               linebuf[lx++] = ' ';
> +               }
> +               if (j)
> +                       lx--;
> +
> +               ascii_column = 3 * rowsize + 2 + 2;
> +               break;
> +       }
> +       if (!ascii)
> +               goto nil;
> +
> +       while (lx < (linebuflen - 1) && lx < (ascii_column - 1))
> +               linebuf[lx++] = ' ';
> +       for (j = 0; (j < len) && (lx + 2) < linebuflen; j++) {
> +               ch = ptr[j];
> +               linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.';
> +       }
> +nil:
> +       linebuf[lx++] = '\0';
> +}
> +
> +static void print_hex_dump(const void *buf, int len, int offset)
> +{
> +       const uint8_t *ptr = buf;
> +       int i, linelen, remaining = len;
> +       char linebuf[32 * 3 + 2 + 32 + 2 + 1];
> +       int rowsize = 16;
> +       int groupsize = 1;
> +
> +       if (rowsize != 16 && rowsize != 32)
> +               rowsize = 16;
> +
> +       for (i = 0; i < len; i += rowsize) {
> +               linelen = MIN(remaining, rowsize);
> +               remaining -= rowsize;
> +
> +               hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
> +                               linebuf, sizeof(linebuf), 1);
> +
> +               printf("%.8x: %s\n", i + offset, linebuf);
> +       }
> +}
> +
> +static int stress_test_uart_once(struct g_opt *opts, int fd, unsigned
> char *data,
> +               off_t data_len)
> +{
> +       unsigned char *cmp_data = opts->cmp_buff;
> +       ssize_t size;
> +       int wait_rx;
> +       int wait_tx;
> +       ssize_t progress_rx = 0;
> +       ssize_t progress_tx = 0;
> +       unsigned int reads = 0;
> +       unsigned int writes = 0;
> +
> +       do {
> +               struct pollfd pfd = {
> +                       .fd = fd,
> +               };
> +               int ret;
> +
> +               if (opts->mode & MODE_RX_ONLY && progress_rx < data_len) {
> +                       pfd.events |= POLLIN;
> +                       wait_rx = 1;
> +               } else {
> +                       wait_rx = 0;
> +               }
> +
> +               if (opts->mode & MODE_TX_ONLY && progress_tx < data_len) {
> +                       pfd.events |= POLLOUT;
> +                       wait_tx = 1;
> +               } else {
> +                       wait_tx = 0;
> +               }
> +
> +               ret = poll(&pfd, 1, 10 * 1000);
> +               if (ret == 0) {
> +                       tst_res(TFAIL, "timeout, RX/TX: %zd/%zd\n",
> progress_rx, progress_tx);
> +                       break;
> +               }
> +               if (ret < 0) {
> +                       tst_res(TFAIL | TERRNO, "poll() failed");
> +                       return 1;
> +               }
> +
> +               if (pfd.revents & POLLIN) {
> +                       size = read(fd, cmp_data + progress_rx, data_len -
> progress_rx);
> +                       if (size < 0) {
> +                               tst_res(TFAIL | TERRNO, "read() failed");
> +                               return 1;
> +                       }
> +                       reads++;
> +                       progress_rx += size;
> +                       if (progress_rx >= data_len)
> +                               wait_rx = 0;
> +               }
> +
> +               if (pfd.revents & POLLOUT) {
> +
> +                       size = write(fd, data + progress_tx, data_len -
> progress_tx);
> +                       if (size < 0) {
> +                               tst_res(TFAIL | TERRNO, "write() failed");
> +                               return 1;
> +                       }
> +                       writes++;
> +                       progress_tx += size;
> +                       if (progress_tx >= data_len)
> +                               wait_tx = 0;
> +               }
> +       } while (wait_rx || wait_tx);
> +
> +       tst_res(TINFO, "Needed %u reads %u writes ", reads, writes);
> +
> +       if (opts->mode & MODE_RX_ONLY) {
> +               unsigned int i;
> +               int found = 0;
> +               unsigned int min_pos;
> +               unsigned int max_pos;
> +
> +               if (!memcmp(data, cmp_data, data_len)) {
> +                       tst_res(TPASS, "RX passed");
> +                       return 0;
> +               }
> +
> +               for (i = 0; i < data_len && !found; i++) {
> +                       if (data[i] != cmp_data[i]) {
> +                               found = 1;
> +                               break;
> +                       }
> +               }
> +
> +               if (!found) {
> +                       tst_res(TFAIL, "memcmp() didn't match but manual
> cmp did");
> +                       return 1;
> +               }
> +
> +               max_pos = (i & ~0xfULL) + 16 * 3;
> +               if (max_pos > data_len)
> +                       max_pos = data_len;
> +
> +               min_pos = i & ~0xfULL;
> +               if (min_pos > 16 * 3)
> +                       min_pos -= 16 * 3;
> +               else
> +                       min_pos = 0;
> +
> +               tst_res(TFAIL, "Oh oh, inconsistency at pos %d (0x%x)", i,
> i);
> +
> +               printf("\nOriginal sample:\n");
> +               print_hex_dump(data + min_pos, max_pos - min_pos, min_pos);
> +
> +               printf("\nReceived sample:\n");
> +               print_hex_dump(cmp_data + min_pos, max_pos - min_pos,
> min_pos);
> +               return 1;
> +       }
> +
> +       if (opts->mode & MODE_TX_ONLY)
> +               tst_res(TPASS, "TX passed");
> +
> +       return 0;
> +}
> +
> +static int stress_test_uart(struct g_opt *opts, int fd, unsigned char
> *data, off_t data_len)
> +{
> +       unsigned int loops = 0;
> +       int status;
> +
> +       opts->cmp_buff = SAFE_MALLOC(data_len);
> +       memset(opts->cmp_buff, 0, data_len);
> +
> +       do {
> +               status = stress_test_uart_once(opts, fd, data, data_len);
> +               memset(opts->cmp_buff, 0, data_len);
> +       } while (++loops < opts->loops && !status);
> +
> +       free(opts->cmp_buff);
> +
> +       return status;
> +}
> +
> +static int setup_uart(struct g_opt *opts, int open_mode, struct termios
> *old_term)
> +{
> +       struct termios new_term;
> +       int fd;
> +       int ret;
> +
> +       tst_res(TINFO, "Setting up %s speed %u hwflow=%u",
> +               opts->uart_dev, opts->baud_rate, opts->hwflow);
> +
> +       fd = SAFE_OPEN(opts->uart_dev, open_mode | O_NONBLOCK);
> +
> +       ret = tcgetattr(fd, old_term);
> +       if (ret < 0)
> +               tst_brk(TBROK, "tcgetattr() failed: %m\n");
> +
> +       new_term = *old_term;
> +
> +       /* or c_cflag |= BOTHER and c_ospeed for any speed */
> +       ret = cfsetspeed(&new_term, opts->baud_rate);
> +       if (ret < 0)
> +               tst_brk(TBROK, "cfsetspeed(, %u) failed %m\n",
> opts->baud_rate);
> +       cfmakeraw(&new_term);
> +       new_term.c_cflag |= CREAD;
> +       if (opts->hwflow)
> +               new_term.c_cflag |= CRTSCTS;
> +       else
> +               new_term.c_cflag &= ~CRTSCTS;
> +       new_term.c_cc[VMIN] = 64;
> +       new_term.c_cc[VTIME] = 8;
> +
> +       ret = tcsetattr(fd, TCSANOW, &new_term);
> +       if (ret < 0)
> +               tst_brk(TBROK, "tcsetattr failed: %m\n");
> +
> +       if (opts->do_termios) {
> +               ret = tcflush(fd, TCIFLUSH);
> +               if (ret < 0)
> +                       tst_brk(TBROK, "tcflush failed: %m\n");
> +       }
> +
> +       ret = fcntl(fd, F_SETFL, 0);
> +       if (ret)
> +               printf("Failed to remove nonblock mode\n");
> +
> +       return fd;
> +}
> +
> +static void restore_uart(int fd, struct termios *old_term)
> +{
> +       int ret = tcsetattr(fd, TCSAFLUSH, old_term);
> +       if (ret)
> +               printf("tcsetattr() of old ones failed: %m\n");
> +}
> +
> +static void print_counters(const char *prefix,
> +                           struct serial_icounter_struct *old,
> +                           struct serial_icounter_struct *new)
> +{
> +#define CNT(x) (new->x - old->x)
> +       printf("%scts: %d dsr: %d rng: %d dcd: %d rx: %d tx: %d "
> +               "frame %d ovr %d par: %d brk: %d buf_ovrr: %d\n", prefix,
> +               CNT(cts), CNT(dsr), CNT(rng), CNT(dcd), CNT(rx),
> +               CNT(tx), CNT(frame), CNT(overrun), CNT(parity),
> +               CNT(brk), CNT(buf_overrun));
> +#undef CNT
> +}
> +
> +static struct g_opt opts = {
> +       .baud_rate = 115200,
> +       .loops = 1,
> +       .do_termios = 1,
> +};
> +
> +static char *uart_rx;
> +static char *uart_tx;
> +
> +unsigned char *data;
> +static long data_len;
> +
> +void run(void)
> +{
> +       struct serial_icounter_struct old_counters;
> +       struct serial_icounter_struct new_counters;
> +       int ret, fd_rx, fd_tx;
> +       struct termios old_term_rx, old_term_tx;
> +
> +       struct g_opt opts_in = opts;
> +       struct g_opt opts_out = opts;
> +
> +       opts_in.uart_dev = uart_rx;
> +       opts_out.uart_dev = uart_tx;
> +
> +       opts_in.mode = MODE_RX_ONLY;
> +       opts_out.mode = MODE_TX_ONLY;
> +
> +       fd_rx = setup_uart(&opts_in, O_RDONLY, &old_term_rx);
> +
> +       if (!strcmp(uart_rx, uart_tx))
> +               fd_tx = SAFE_OPEN(uart_tx, O_WRONLY);
> +       else
> +               fd_tx = setup_uart(&opts_out, O_WRONLY, &old_term_tx);
> +
> +       if (!SAFE_FORK()) {
> +               ioctl(fd_rx, TIOCGICOUNT, &old_counters);
> +               stress_test_uart(&opts_in, fd_rx, data, data_len);
> +               ret = ioctl(fd_rx, TIOCGICOUNT, &new_counters);
> +               if (ret > 0)
> +                       print_counters("RX:", &old_counters,
> &new_counters);
> +               exit(0);
> +       }
> +
> +       if (!SAFE_FORK()) {
> +               ioctl(fd_tx, TIOCGICOUNT, &old_counters);
> +               stress_test_uart(&opts_out, fd_tx, data, data_len);
> +               ret = ioctl(fd_tx, TIOCGICOUNT, &new_counters);
> +               if (ret > 0)
> +                       print_counters("TX:", &old_counters,
> &new_counters);
> +               exit(0);
> +       }
> +
> +       SAFE_WAIT(NULL);
> +       SAFE_WAIT(NULL);
> +
> +       restore_uart(fd_rx, &old_term_rx);
> +
> +       if (strcmp(uart_rx, uart_tx))
> +               restore_uart(fd_tx, &old_term_tx);
> +
> +       close(fd_rx);
> +       close(fd_tx);
> +}
> +
> +static void map_file(const char *fname)
> +{
> +       struct stat st;
> +       int fd;
> +
> +       fd = SAFE_OPEN(fname, O_RDONLY);
> +
> +       SAFE_FSTAT(fd, &st);
> +
> +       data_len = st.st_size;
> +
> +       data = SAFE_MMAP(NULL, data_len, PROT_READ,
> +                        MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0);
> +
> +       tst_res(TINFO, "Mapped file '%s' size %li bytes", fname, data_len);
> +
> +       SAFE_CLOSE(fd);
> +}
> +
> +static void map_buffer(long buf_size)
> +{
> +       size_t i;
> +
> +       data_len = buf_size;
> +
> +       data = SAFE_MMAP(NULL, data_len, PROT_READ | PROT_WRITE,
> +                        MAP_ANONYMOUS | MAP_SHARED | MAP_LOCKED, -1, 0);
> +
> +       long *p = (void*)data;
> +
> +       srandom(time(NULL));
> +
> +       for (i = 0; i < data_len / sizeof(long); i++)
> +               p[i] = random();
> +
> +       tst_res(TINFO, "Mapped anynymous memory size %li bytes", data_len);
> +}
> +
> +static char *baud_rate;
> +static char *hwflow;
> +static char *fname;
> +static char *buf_size;
> +
> +static void setup(void)
> +{
> +       long size = 1024;
> +
> +       if (baud_rate)
> +               tst_parse_int(baud_rate, &(opts.baud_rate), 0, INT_MAX);
> +
> +       if (hwflow)
> +               opts.hwflow = 1;
> +
> +       if (fname && buf_size)
> +               tst_brk(TBROK, "Only one of -f and -s could be set!");
> +
> +       if (buf_size)
> +               tst_parse_long(buf_size, &size, 0, LONG_MAX);
> +
> +       uart_rx = getenv("UART_RX");
> +       uart_tx = getenv("UART_TX");
> +
> +       if (fname)
> +               map_file(fname);
> +       else
> +               map_buffer(size);
> +}
> +
> +static struct tst_test test = {
> +       .setup = setup,
> +       .test_all = run,
> +       .options = (struct tst_option[]) {
> +               {"b:", &baud_rate, "-b       Baud rate (9600, ...)"},
> +               {"w",  &hwflow   , "-w       Enable hwflow (RTS/CTS)"},
> +               {"f:",  &fname,    "-f       Binary file for transfers"},
> +               {"s:",  &buf_size, "-s       Binary buffer size"},
> +               {}
> +       },
> +       .needs_devices = (const char *const[]) {"UART_RX", "UART_TX",
> NULL},
> +       .forks_child = 1,
> +};
> --
> 2.24.1
>
>
Cyril Hrubis March 30, 2020, 3:21 p.m. UTC | #3
Hi!
> Thank you porting the serialcheck.c into LTP
> I am sorry to find the serialcheck have not LOOPBACK mode support
> the LOOPBACK mode is a better test than HW flow , because most machine's
> uart have not connect the Rx & TX
> in LOOPBACK mode. we test the uart port directly, So we can test one uart
> port Rx and Tx functions at the same time .
> here is the diff  serialcheck with loopback patch
> So I'd prefer use loopback mode test the uart in case.
> $ diff serialcheck.c serialcheck-with-loopback.c

Thanks for the hint, I had no idea that subset serial port hardware has
a loopback test that could be enabled in modem control register which is
meant for testing. I will have a closer look tomorrow.
Cyril Hrubis March 31, 2020, 6:08 p.m. UTC | #4
Hi!
> > Thank you porting the serialcheck.c into LTP
> > I am sorry to find the serialcheck have not LOOPBACK mode support
> > the LOOPBACK mode is a better test than HW flow , because most machine's
> > uart have not connect the Rx & TX
> > in LOOPBACK mode. we test the uart port directly, So we can test one uart
> > port Rx and Tx functions at the same time .
> > here is the diff  serialcheck with loopback patch
> > So I'd prefer use loopback mode test the uart in case.
> > $ diff serialcheck.c serialcheck-with-loopback.c
> 
> Thanks for the hint, I had no idea that subset serial port hardware has
> a loopback test that could be enabled in modem control register which is
> meant for testing. I will have a closer look tomorrow.

If I understand this properly if we set a bit in the modem control
register we will test mostly the circuits between CPU and UART
controller which is better than nothing, but we are not really testing
if the port speed was set correctly since the data are just being copied
between registers in the UART controller, so it does not make sense to
change the speed in this mode. Or am I mistaken?

Also it does not seem to work for me and I've tried with both serial
ports on my desktop PC as well as with USB-to-Serial dongle. I can set
the bit just fine but loopback does not work.
Cixi Geng April 1, 2020, 11:57 a.m. UTC | #5
>If I understand this properly if we set a bit in the modem control
>register we will test mostly the circuits between CPU and UART
>controller which is better than nothing, but we are not really testing
>if the port speed was set correctly since the data are just being copied
>between registers in the UART controller, so it does not make sense to
>change the speed in this mode. Or am I mistaken?

>Also it does not seem to work for me and I've tried with both serial
>ports on my desktop PC as well as with USB-to-Serial dongle. I can set
>the bit just fine but loopback does not work.

In the loopback mode , the data will be transferred in buffer ,and back to
CPU
by FIFO way.
I understand the test flow is CPU->uart Tx-> buffer file->uart Rx->CPU,
so it does make sense to the uart driver .
 BTW, I found the latest seriacheck git is
https://github.com/nsekhar/serialcheck.git
and I test on my arm64 machine of sprdtream. and it does works.
the test log I had send in another patch
https://patchwork.ozlabs.org/patch/1264553/


Cyril Hrubis <chrubis@suse.cz> 于2020年4月1日周三 上午2:08写道:

> Hi!
> > > Thank you porting the serialcheck.c into LTP
> > > I am sorry to find the serialcheck have not LOOPBACK mode support
> > > the LOOPBACK mode is a better test than HW flow , because most
> machine's
> > > uart have not connect the Rx & TX
> > > in LOOPBACK mode. we test the uart port directly, So we can test one
> uart
> > > port Rx and Tx functions at the same time .
> > > here is the diff  serialcheck with loopback patch
> > > So I'd prefer use loopback mode test the uart in case.
> > > $ diff serialcheck.c serialcheck-with-loopback.c
> >
> > Thanks for the hint, I had no idea that subset serial port hardware has
> > a loopback test that could be enabled in modem control register which is
> > meant for testing. I will have a closer look tomorrow.
>
> If I understand this properly if we set a bit in the modem control
> register we will test mostly the circuits between CPU and UART
> controller which is better than nothing, but we are not really testing
> if the port speed was set correctly since the data are just being copied
> between registers in the UART controller, so it does not make sense to
> change the speed in this mode. Or am I mistaken?
>
> Also it does not seem to work for me and I've tried with both serial
> ports on my desktop PC as well as with USB-to-Serial dongle. I can set
> the bit just fine but loopback does not work.
>
> --
> Cyril Hrubis
> chrubis@suse.cz
>
Cyril Hrubis April 1, 2020, 1:12 p.m. UTC | #6
Hi!
> >If I understand this properly if we set a bit in the modem control
> >register we will test mostly the circuits between CPU and UART
> >controller which is better than nothing, but we are not really testing
> >if the port speed was set correctly since the data are just being copied
> >between registers in the UART controller, so it does not make sense to
> >change the speed in this mode. Or am I mistaken?
> 
> >Also it does not seem to work for me and I've tried with both serial
> >ports on my desktop PC as well as with USB-to-Serial dongle. I can set
> >the bit just fine but loopback does not work.
> 
> In the loopback mode , the data will be transferred in buffer ,and back to
> CPU
> by FIFO way.
> I understand the test flow is CPU->uart Tx-> buffer file->uart Rx->CPU,
> so it does make sense to the uart driver .

Indeed but it does not make sense tu run it with a different baud rates,
since the data are not transmitted at all.

>  BTW??? I found the latest seriacheck git is
> https://github.com/nsekhar/serialcheck.git
> and I test on my arm64 machine of sprdtream. and it does works.
> the test log I had send in another patch
> https://patchwork.ozlabs.org/patch/1264553/

Unfortunately it does not seem to work on my AMD based desktop at all,
my guess is that the loopback bit is silently ignored by the hardware.

Which means that we cannot enable the test by default in loopback mode
after all.
Cixi Geng April 2, 2020, 1:30 a.m. UTC | #7
>Indeed but it does not make sense tu run it with a different baud rates,
>since the data are not transmitted at all.
The data exchanged between Tx|Rx and buffer have nothing to do with
baudrate?
I think the baudrate is control Tx|Rx send and receive date rate to|from
buffer.
>Unfortunately it does not seem to work on my AMD based desktop at all,
>my guess is that the loopback bit is silently ignored by the hardware.
>Which means that we cannot enable the test by default in loopback mode
>after all
I will test on my laptop and feedback result today, if it does no work , we
should
check the uart driver what different between x86 and arm64.

Cyril Hrubis <chrubis@suse.cz> 于2020年4月1日周三 下午9:12写道:

> Hi!
> > >If I understand this properly if we set a bit in the modem control
> > >register we will test mostly the circuits between CPU and UART
> > >controller which is better than nothing, but we are not really testing
> > >if the port speed was set correctly since the data are just being copied
> > >between registers in the UART controller, so it does not make sense to
> > >change the speed in this mode. Or am I mistaken?
> >
> > >Also it does not seem to work for me and I've tried with both serial
> > >ports on my desktop PC as well as with USB-to-Serial dongle. I can set
> > >the bit just fine but loopback does not work.
> >
> > In the loopback mode , the data will be transferred in buffer ,and back
> to
> > CPU
> > by FIFO way.
> > I understand the test flow is CPU->uart Tx-> buffer file->uart Rx->CPU,
> > so it does make sense to the uart driver .
>
> Indeed but it does not make sense tu run it with a different baud rates,
> since the data are not transmitted at all.
>
> >  BTW??? I found the latest seriacheck git is
> > https://github.com/nsekhar/serialcheck.git
> > and I test on my arm64 machine of sprdtream. and it does works.
> > the test log I had send in another patch
> > https://patchwork.ozlabs.org/patch/1264553/
>
> Unfortunately it does not seem to work on my AMD based desktop at all,
> my guess is that the loopback bit is silently ignored by the hardware.
>
> Which means that we cannot enable the test by default in loopback mode
> after all.
>
> --
> Cyril Hrubis
> chrubis@suse.cz
>
Cyril Hrubis April 2, 2020, 9:31 a.m. UTC | #8
Hi!
> >Indeed but it does not make sense tu run it with a different baud rates,
> >since the data are not transmitted at all.
> The data exchanged between Tx|Rx and buffer have nothing to do with
> baudrate?
> I think the baudrate is control Tx|Rx send and receive date rate to|from
> buffer.

That's what I'm not sure about, the documentation says that in loopback
mode data written to the port immediatelly appears on the receiving end,
which would mean that the uart speed does not matter at all.

Can you try a quick test? If you measure the time the test spends
writing data in loopback mode for a different uart speeds and they do
not differ the uart speed does not matter.

> >Unfortunately it does not seem to work on my AMD based desktop at all,
> >my guess is that the loopback bit is silently ignored by the hardware.
> >Which means that we cannot enable the test by default in loopback mode
> >after all
> I will test on my laptop and feedback result today, if it does no work , we
> should check the uart driver what different between x86 and arm64.

I bet that this differs chipset by chipset and I do not think there is
anything wrong with the uart driver per se.
Cixi Geng April 2, 2020, 11:16 a.m. UTC | #9
Hi
I am sorry that when I run the serialcheck on my laptop,
there always has some error as follow,which mean I cannot
verify the serialcheck on my computer for now.
Failed to ioctl(,TIOCGICOUNT,)  -- test ttyUSB0
tcgetattr() failed: Input/output error -- test ttyS0
I am trying to find available machine and then run test.

Cyril Hrubis <chrubis@suse.cz> 于2020年4月2日周四 下午5:31写道:

> Hi!
> > >Indeed but it does not make sense tu run it with a different baud rates,
> > >since the data are not transmitted at all.
> > The data exchanged between Tx|Rx and buffer have nothing to do with
> > baudrate?
> > I think the baudrate is control Tx|Rx send and receive date rate to|from
> > buffer.
>
> That's what I'm not sure about, the documentation says that in loopback
> mode data written to the port immediatelly appears on the receiving end,
> which would mean that the uart speed does not matter at all.
>
> Can you try a quick test? If you measure the time the test spends
> writing data in loopback mode for a different uart speeds and they do
> not differ the uart speed does not matter.
>
> > >Unfortunately it does not seem to work on my AMD based desktop at all,
> > >my guess is that the loopback bit is silently ignored by the hardware.
> > >Which means that we cannot enable the test by default in loopback mode
> > >after all
> > I will test on my laptop and feedback result today, if it does no work ,
> we
> > should check the uart driver what different between x86 and arm64.
>
> I bet that this differs chipset by chipset and I do not think there is
> anything wrong with the uart driver per se.
>
> --
> Cyril Hrubis
> chrubis@suse.cz
>
Cyril Hrubis April 2, 2020, 11:23 a.m. UTC | #10
Hi!
> I am sorry that when I run the serialcheck on my laptop,
> there always has some error as follow,which mean I cannot
> verify the serialcheck on my computer for now.
> Failed to ioctl(,TIOCGICOUNT,)  -- test ttyUSB0

This one can be ignored, that just means that the counters are not
implemented and the statistics are not printed at the test end.

> tcgetattr() failed: Input/output error -- test ttyS0
> I am trying to find available machine and then run test.

That looks like there is no UART to begin with.
Cixi Geng April 3, 2020, 1:50 a.m. UTC | #11
Hi Cyril:
Here is my test result, in this test we can make sure that the baudrate is
needed in test.
the test hope help you

1. test ttyUSB0 in loopback use the same baudrate of Rx and Tx , test pass
with 5loop
root@Y50:/home/gcx/project/serialcheck# serialcheck  -b 115200 -d
/dev/ttyUSB0 -f binary -l 5 -m r -k
Needed 64 reads 0 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
gcx@Y50:~/project/serialcheck$ serialcheck -b 115200 -d /dev/ttyUSB0 -f
binary -m t -l 5 -k
Needed 0 reads 1 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
2. test ttyUSB0 in loopback use different baudrate ,Error at the fist loop
root@Y50:/home/gcx/project/serialcheck# serialcheck  -b 115200 -d
/dev/ttyUSB0 -f binary -l 5 -m r -k &
[1] 20764
gcx@Y50:~/project/serialcheck$ serialcheck -b 115200 -d /dev/ttyUSB0 -f
binary -m t -l 5 -k
Needed 0 reads 1 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
Needed 64 reads 0 writes Oh oh, inconsistency at pos 502 (0x1f6).

Original sample:
000001c0: 91 95 eb b6 82 e9 2a e6  16 5a da a3 c2 51 c4 c9
......*..Z...Q..
000001d0: c5 51 e1 b7 c9 76 67 d5  09 57 80 77 eb bf 6d d7
.Q...vg..W.w..m.
000001e0: 08 a6 7b fd 52 1b 12 8e  f2 50 c1 b7 a7 52 35 39
..{.R....P...R59
000001f0: 54 d4 50 96 49 55 35 30  33 52 80 89 8e a9 1e a2
T.P.IU503R......
00000200: 2c a5 0d 1a 26 f6 ea 77  a4 4a b9 69 34 17 cc bc
,...&..w.J.i4...
00000210: e2 6e 0c f9 e1 11 39 9f  fd ce 94 9e 19 30 f4 1c
.n....9......0..

Received sample:
000001c0: 91 95 eb b6 82 e9 2a e6  16 5a da a3 c2 51 c4 c9
......*..Z...Q..
000001d0: c5 51 e1 b7 c9 76 67 d5  09 57 80 77 eb bf 6d d7
.Q...vg..W.w..m.
000001e0: 08 a6 7b fd 52 1b 12 8e  f2 50 c1 b7 a7 52 35 39
..{.R....P...R59
000001f0: 54 d4 50 96 49 55 06 9a  92 01 89 8e a9 1e a2 2c
T.P.IU.........,
00000200: a5 0d 1a 26 f6 ea 77 a4  4a b9 69 34 17 cc bc e2
...&..w.J.i4....
00000210: 6e 0c f9 e1 11 39 9f fd  ce 94 9e 19 30 f4 1c 74
n....9......0..t
loops 1 / 5



Cyril Hrubis <chrubis@suse.cz> 于2020年4月2日周四 下午7:22写道:

> Hi!
> > I am sorry that when I run the serialcheck on my laptop,
> > there always has some error as follow,which mean I cannot
> > verify the serialcheck on my computer for now.
> > Failed to ioctl(,TIOCGICOUNT,)  -- test ttyUSB0
>
> This one can be ignored, that just means that the counters are not
> implemented and the statistics are not printed at the test end.
>
> > tcgetattr() failed: Input/output error -- test ttyS0
> > I am trying to find available machine and then run test.
>
> That looks like there is no UART to begin with.
>
> --
> Cyril Hrubis
> chrubis@suse.cz
>
Cixi Geng April 8, 2020, 4:15 a.m. UTC | #12
> Cyril Hrubis <chrubis@suse.cz> 于2020年4月2日周四 下午7:22写道:
>>
>> Hi!
>> > I am sorry that when I run the serialcheck on my laptop,
>> > there always has some error as follow,which mean I cannot
>> > verify the serialcheck on my computer for now.
>> > Failed to ioctl(,TIOCGICOUNT,)  -- test ttyUSB0
>>
>> This one can be ignored, that just means that the counters are not
>> implemented and the statistics are not printed at the test end.
>>
>> > tcgetattr() failed: Input/output error -- test ttyS0
>> > I am trying to find available machine and then run test.
>>
>> That looks like there is no UART to begin with.
>>
>> --
>> Cyril Hrubis
>> chrubis@suse.cz

Hi Cyril:
I would like to know how is it going? lastweek I send the test data .
hope it will helpful.
In my test result  we can found if Rx Tx in different rate,  the test will fail.

1. test ttyUSB0 in loopback use the same baudrate of Rx and Tx , test
pass with 5loop
root@Y50:/home/gcx/project/serialcheck# serialcheck  -b 115200 -d
/dev/ttyUSB0 -f binary -l 5 -m r -k
Needed 64 reads 0 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
gcx@Y50:~/project/serialcheck$ serialcheck -b 115200 -d /dev/ttyUSB0
-f binary -m t -l 5 -k
Needed 0 reads 1 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
2. test ttyUSB0 in loopback use different baudrate ,Error at the fist loop
root@Y50:/home/gcx/project/serialcheck# serialcheck  -b 9600 -d
/dev/ttyUSB0 -f binary -l 5 -m r -k &
[1] 20764
gcx@Y50:~/project/serialcheck$ serialcheck -b 115200 -d /dev/ttyUSB0
-f binary -m t -l 5 -k
Needed 0 reads 1 writes loops 5 / 5
Failed to ioctl(,TIOCGICOUNT,)
Needed 64 reads 0 writes Oh oh, inconsistency at pos 502 (0x1f6).

Original sample:
000001c0: 91 95 eb b6 82 e9 2a e6  16 5a da a3 c2 51 c4 c9   ......*..Z...Q..
000001d0: c5 51 e1 b7 c9 76 67 d5  09 57 80 77 eb bf 6d d7   .Q...vg..W.w..m.
000001e0: 08 a6 7b fd 52 1b 12 8e  f2 50 c1 b7 a7 52 35 39   ..{.R....P...R59
000001f0: 54 d4 50 96 49 55 35 30  33 52 80 89 8e a9 1e a2   T.P.IU503R......
00000200: 2c a5 0d 1a 26 f6 ea 77  a4 4a b9 69 34 17 cc bc   ,...&..w.J.i4...
00000210: e2 6e 0c f9 e1 11 39 9f  fd ce 94 9e 19 30 f4 1c   .n....9......0..

Received sample:
000001c0: 91 95 eb b6 82 e9 2a e6  16 5a da a3 c2 51 c4 c9   ......*..Z...Q..
000001d0: c5 51 e1 b7 c9 76 67 d5  09 57 80 77 eb bf 6d d7   .Q...vg..W.w..m.
000001e0: 08 a6 7b fd 52 1b 12 8e  f2 50 c1 b7 a7 52 35 39   ..{.R....P...R59
000001f0: 54 d4 50 96 49 55 06 9a  92 01 89 8e a9 1e a2 2c   T.P.IU.........,
00000200: a5 0d 1a 26 f6 ea 77 a4  4a b9 69 34 17 cc bc e2   ...&..w.J.i4....
00000210: 6e 0c f9 e1 11 39 9f fd  ce 94 9e 19 30 f4 1c 74   n....9......0..t
loops 1 / 5
Cyril Hrubis April 8, 2020, 2:18 p.m. UTC | #13
Hi!
> I would like to know how is it going?

I had a time off and was planting plants in my garden, I will get back
to the uart ASAP, but then we have a public holiday on Friday and Monday
so I may not manage to do much until next week.
Cixi Geng May 11, 2020, 9:12 a.m. UTC | #14
> I had a time off and was planting plants in my garden, I will get back
> to the uart ASAP, but then we have a public holiday on Friday and Monday
> so I may not manage to do much until next week.
>
> --
> Cyril Hrubis
> chrubis@suse.cz

Hi Cyril:
I had study the ltp execution framework and found that the LTP
detection device is
not suitable for device driver test at present.
Like the UART test, I want have a auto-detect way to find the device
needed to test
 in the current running LtP machines.
Now I have test the uart case in several sprdtream Soc
these board  have different /dev/tty* device。and I nedd run in CI
manual export device puzzled me to do the auto-test-job.
moreover I wil  porting other device driver testcases in the future.
So can We expand the LTP detection ?
Cyril Hrubis May 11, 2020, 9:37 a.m. UTC | #15
Hi!
> I had study the ltp execution framework and found that the LTP
> detection device is
> not suitable for device driver test at present.
> Like the UART test, I want have a auto-detect way to find the device
> needed to test
>  in the current running LtP machines.
> Now I have test the uart case in several sprdtream Soc
> these board  have different /dev/tty* device???and I nedd run in CI
> manual export device puzzled me to do the auto-test-job.
> moreover I wil  porting other device driver testcases in the future.
> So can We expand the LTP detection ?

Sorry I haven't managed to work on this before it was time for LTP
release. I will resume my work on this once LTP is released.

My generall idea is that the LTP framework will get a path to a
directory with scripts that would be evaluated at test runtime and will
return list of devices to test. These scripts will have to be supplied
by the user as managing lab hardware is out of scope of the LTP
framework.
Cixi Geng May 12, 2020, 2:32 a.m. UTC | #16
Cyril Hrubis <chrubis@suse.cz> 于2020年5月11日周一 下午5:37写道:
>
> Hi!
> > I had study the ltp execution framework and found that the LTP
> > detection device is
> > not suitable for device driver test at present.
> > Like the UART test, I want have a auto-detect way to find the device
> > needed to test
> >  in the current running LtP machines.
> > Now I have test the uart case in several sprdtream Soc
> > these board  have different /dev/tty* device???and I nedd run in CI
> > manual export device puzzled me to do the auto-test-job.
> > moreover I wil  porting other device driver testcases in the future.
> > So can We expand the LTP detection ?
>
> Sorry I haven't managed to work on this before it was time for LTP
> release. I will resume my work on this once LTP is released.
>
> My generall idea is that the LTP framework will get a path to a
> directory with scripts that would be evaluated at test runtime and will
> return list of devices to test. These scripts will have to be supplied
> by the user as managing lab hardware is out of scope of the LTP
> framework.
>
> --
> Cyril Hrubis
> chrubis@suse.cz

Thank you for your guidance,In the Next time, I will try to fork a
process in serialcheck.c
So it can be test Tx & Rx at one time.And then  to develop the
detection scripts if hava a time
 I'll keep you posted on the status.
diff mbox series

Patch

diff --git a/runtest/kernel_misc b/runtest/kernel_misc
index 7937c7bbf..a7f1d9b56 100644
--- a/runtest/kernel_misc
+++ b/runtest/kernel_misc
@@ -13,3 +13,8 @@  zram01 zram01.sh
 zram02 zram02.sh
 zram03 zram03
 umip_basic_test umip_basic_test
+uart01_9600 uart01 -b 9600
+uart01_19200 uart01 -b 19200
+uart01_38400 uart01 -b 38400
+uart01_57600 uart01 -b 57600
+uart01_115200 uart01 -b 115200
diff --git a/testcases/kernel/device-drivers/uart/.gitignore b/testcases/kernel/device-drivers/uart/.gitignore
new file mode 100644
index 000000000..9333e8db9
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/.gitignore
@@ -0,0 +1 @@ 
+uart01
diff --git a/testcases/kernel/device-drivers/uart/Makefile b/testcases/kernel/device-drivers/uart/Makefile
new file mode 100644
index 000000000..1c90e5cd6
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/Makefile
@@ -0,0 +1,4 @@ 
+
+top_srcdir	?= ../../../..
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/device-drivers/uart/uart01.c b/testcases/kernel/device-drivers/uart/uart01.c
new file mode 100644
index 000000000..4647c55e3
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/uart01.c
@@ -0,0 +1,522 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+   Copyright (c) 2014 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+   Copyright (c) 2020 Cyril Hrubis <chrubis@suse.cz>
+
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdint.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <linux/serial.h>
+
+#include "tst_test.h"
+
+static const char hex_asc[] = "0123456789abcdef";
+#define hex_asc_lo(x)	hex_asc[((x) & 0x0f)]
+#define hex_asc_hi(x)	hex_asc[((x) & 0xf0) >> 4]
+
+struct g_opt {
+	char *uart_dev;
+	char *file_trans;
+	int baud_rate;
+	unsigned int loops;
+	unsigned char hwflow;
+	unsigned char do_termios;
+#define MODE_TX_ONLY    (1 << 0)
+#define MODE_RX_ONLY    (1 << 1)
+#define MODE_DUPLEX     (MODE_TX_ONLY | MODE_RX_ONLY)
+	unsigned int mode;
+	unsigned char *cmp_buff;
+};
+
+static int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+	int i;
+
+	i = vsnprintf(buf, size, fmt, args);
+
+	if (i < (int)size)
+		return i;
+	if (size != 0)
+		return size - 1;
+	return 0;
+}
+
+static int scnprintf(char *buf, size_t size, const char *fmt, ...)
+{
+	va_list args;
+	int i;
+
+	va_start(args, fmt);
+	i = vscnprintf(buf, size, fmt, args);
+	va_end(args);
+
+	return i;
+}
+
+
+static void hex_dump_to_buffer(const void *buf, int len, int rowsize,
+		int groupsize, char *linebuf, int linebuflen, int ascii)
+{
+	const uint8_t *ptr = buf;
+	uint8_t ch;
+	int j, lx = 0;
+	int ascii_column;
+
+	if (rowsize != 16 && rowsize != 32)
+		rowsize = 16;
+
+	if (!len)
+		goto nil;
+	if (len > rowsize)      /* limit to one line at a time */
+		len = rowsize;
+	if ((len % groupsize) != 0)     /* no mixed size output */
+		groupsize = 1;
+
+	switch (groupsize) {
+	case 8: {
+		const uint64_t *ptr8 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%16.16llx", j ? " " : "",
+					(unsigned long long)*(ptr8 + j));
+		ascii_column = 17 * ngroups + 2;
+		break;
+		}
+
+	case 4: {
+		const uint32_t *ptr4 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%8.8x", j ? " " : "", *(ptr4 + j));
+		ascii_column = 9 * ngroups + 2;
+		break;
+		}
+
+	case 2: {
+		const uint16_t *ptr2 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%4.4x", j ? " " : "", *(ptr2 + j));
+		ascii_column = 5 * ngroups + 2;
+		break;
+		}
+
+	default:
+		for (j = 0; (j < len) && (lx + 3) <= linebuflen; j++) {
+			ch = ptr[j];
+			linebuf[lx++] = hex_asc_hi(ch);
+			linebuf[lx++] = hex_asc_lo(ch);
+			linebuf[lx++] = ' ';
+			if (j == 7)
+				linebuf[lx++] = ' ';
+		}
+		if (j)
+			lx--;
+
+		ascii_column = 3 * rowsize + 2 + 2;
+		break;
+	}
+	if (!ascii)
+		goto nil;
+
+	while (lx < (linebuflen - 1) && lx < (ascii_column - 1))
+		linebuf[lx++] = ' ';
+	for (j = 0; (j < len) && (lx + 2) < linebuflen; j++) {
+		ch = ptr[j];
+		linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.';
+	}
+nil:
+	linebuf[lx++] = '\0';
+}
+
+static void print_hex_dump(const void *buf, int len, int offset)
+{
+	const uint8_t *ptr = buf;
+	int i, linelen, remaining = len;
+	char linebuf[32 * 3 + 2 + 32 + 2 + 1];
+	int rowsize = 16;
+	int groupsize = 1;
+
+	if (rowsize != 16 && rowsize != 32)
+		rowsize = 16;
+
+	for (i = 0; i < len; i += rowsize) {
+		linelen = MIN(remaining, rowsize);
+		remaining -= rowsize;
+
+		hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
+				linebuf, sizeof(linebuf), 1);
+
+		printf("%.8x: %s\n", i + offset, linebuf);
+	}
+}
+
+static int stress_test_uart_once(struct g_opt *opts, int fd, unsigned char *data,
+		off_t data_len)
+{
+	unsigned char *cmp_data = opts->cmp_buff;
+	ssize_t size;
+	int wait_rx;
+	int wait_tx;
+	ssize_t progress_rx = 0;
+	ssize_t progress_tx = 0;
+	unsigned int reads = 0;
+	unsigned int writes = 0;
+
+	do {
+		struct pollfd pfd = {
+			.fd = fd,
+		};
+		int ret;
+
+		if (opts->mode & MODE_RX_ONLY && progress_rx < data_len) {
+			pfd.events |= POLLIN;
+			wait_rx = 1;
+		} else {
+			wait_rx = 0;
+		}
+
+		if (opts->mode & MODE_TX_ONLY && progress_tx < data_len) {
+			pfd.events |= POLLOUT;
+			wait_tx = 1;
+		} else {
+			wait_tx = 0;
+		}
+
+		ret = poll(&pfd, 1, 10 * 1000);
+		if (ret == 0) {
+			tst_res(TFAIL, "timeout, RX/TX: %zd/%zd\n", progress_rx, progress_tx);
+			break;
+		}
+		if (ret < 0) {
+			tst_res(TFAIL | TERRNO, "poll() failed");
+			return 1;
+		}
+
+		if (pfd.revents & POLLIN) {
+			size = read(fd, cmp_data + progress_rx, data_len - progress_rx);
+			if (size < 0) {
+				tst_res(TFAIL | TERRNO, "read() failed");
+				return 1;
+			}
+			reads++;
+			progress_rx += size;
+			if (progress_rx >= data_len)
+				wait_rx = 0;
+		}
+
+		if (pfd.revents & POLLOUT) {
+
+			size = write(fd, data + progress_tx, data_len - progress_tx);
+			if (size < 0) {
+				tst_res(TFAIL | TERRNO, "write() failed");
+				return 1;
+			}
+			writes++;
+			progress_tx += size;
+			if (progress_tx >= data_len)
+				wait_tx = 0;
+		}
+	} while (wait_rx || wait_tx);
+
+	tst_res(TINFO, "Needed %u reads %u writes ", reads, writes);
+
+	if (opts->mode & MODE_RX_ONLY) {
+		unsigned int i;
+		int found = 0;
+		unsigned int min_pos;
+		unsigned int max_pos;
+
+		if (!memcmp(data, cmp_data, data_len)) {
+			tst_res(TPASS, "RX passed");
+			return 0;
+		}
+
+		for (i = 0; i < data_len && !found; i++) {
+			if (data[i] != cmp_data[i]) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (!found) {
+			tst_res(TFAIL, "memcmp() didn't match but manual cmp did");
+			return 1;
+		}
+
+		max_pos = (i & ~0xfULL) + 16 * 3;
+		if (max_pos > data_len)
+			max_pos = data_len;
+
+		min_pos = i & ~0xfULL;
+		if (min_pos > 16 * 3)
+			min_pos -= 16 * 3;
+		else
+			min_pos = 0;
+
+		tst_res(TFAIL, "Oh oh, inconsistency at pos %d (0x%x)", i, i);
+
+		printf("\nOriginal sample:\n");
+		print_hex_dump(data + min_pos, max_pos - min_pos, min_pos);
+
+		printf("\nReceived sample:\n");
+		print_hex_dump(cmp_data + min_pos, max_pos - min_pos, min_pos);
+		return 1;
+	}
+
+	if (opts->mode & MODE_TX_ONLY)
+		tst_res(TPASS, "TX passed");
+
+	return 0;
+}
+
+static int stress_test_uart(struct g_opt *opts, int fd, unsigned char *data, off_t data_len)
+{
+	unsigned int loops = 0;
+	int status;
+
+	opts->cmp_buff = SAFE_MALLOC(data_len);
+	memset(opts->cmp_buff, 0, data_len);
+
+	do {
+		status = stress_test_uart_once(opts, fd, data, data_len);
+		memset(opts->cmp_buff, 0, data_len);
+	} while (++loops < opts->loops && !status);
+
+	free(opts->cmp_buff);
+
+	return status;
+}
+
+static int setup_uart(struct g_opt *opts, int open_mode, struct termios *old_term)
+{
+	struct termios new_term;
+	int fd;
+	int ret;
+
+	tst_res(TINFO, "Setting up %s speed %u hwflow=%u",
+	        opts->uart_dev, opts->baud_rate, opts->hwflow);
+
+	fd = SAFE_OPEN(opts->uart_dev, open_mode | O_NONBLOCK);
+
+	ret = tcgetattr(fd, old_term);
+	if (ret < 0)
+		tst_brk(TBROK, "tcgetattr() failed: %m\n");
+
+	new_term = *old_term;
+
+	/* or c_cflag |= BOTHER and c_ospeed for any speed */
+	ret = cfsetspeed(&new_term, opts->baud_rate);
+	if (ret < 0)
+		tst_brk(TBROK, "cfsetspeed(, %u) failed %m\n", opts->baud_rate);
+	cfmakeraw(&new_term);
+	new_term.c_cflag |= CREAD;
+	if (opts->hwflow)
+		new_term.c_cflag |= CRTSCTS;
+	else
+		new_term.c_cflag &= ~CRTSCTS;
+	new_term.c_cc[VMIN] = 64;
+	new_term.c_cc[VTIME] = 8;
+
+	ret = tcsetattr(fd, TCSANOW, &new_term);
+	if (ret < 0)
+		tst_brk(TBROK, "tcsetattr failed: %m\n");
+
+	if (opts->do_termios) {
+		ret = tcflush(fd, TCIFLUSH);
+		if (ret < 0)
+			tst_brk(TBROK, "tcflush failed: %m\n");
+	}
+
+	ret = fcntl(fd, F_SETFL, 0);
+	if (ret)
+		printf("Failed to remove nonblock mode\n");
+
+	return fd;
+}
+
+static void restore_uart(int fd, struct termios *old_term)
+{
+	int ret = tcsetattr(fd, TCSAFLUSH, old_term);
+	if (ret)
+		printf("tcsetattr() of old ones failed: %m\n");
+}
+
+static void print_counters(const char *prefix,
+                           struct serial_icounter_struct *old,
+                           struct serial_icounter_struct *new)
+{
+#define CNT(x) (new->x - old->x)
+	printf("%scts: %d dsr: %d rng: %d dcd: %d rx: %d tx: %d "
+		"frame %d ovr %d par: %d brk: %d buf_ovrr: %d\n", prefix,
+		CNT(cts), CNT(dsr), CNT(rng), CNT(dcd), CNT(rx),
+		CNT(tx), CNT(frame), CNT(overrun), CNT(parity),
+		CNT(brk), CNT(buf_overrun));
+#undef CNT
+}
+
+static struct g_opt opts = {
+	.baud_rate = 115200,
+	.loops = 1,
+	.do_termios = 1,
+};
+
+static char *uart_rx;
+static char *uart_tx;
+
+unsigned char *data;
+static long data_len;
+
+void run(void)
+{
+	struct serial_icounter_struct old_counters;
+	struct serial_icounter_struct new_counters;
+	int ret, fd_rx, fd_tx;
+	struct termios old_term_rx, old_term_tx;
+
+	struct g_opt opts_in = opts;
+	struct g_opt opts_out = opts;
+
+	opts_in.uart_dev = uart_rx;
+	opts_out.uart_dev = uart_tx;
+
+	opts_in.mode = MODE_RX_ONLY;
+	opts_out.mode = MODE_TX_ONLY;
+
+	fd_rx = setup_uart(&opts_in, O_RDONLY, &old_term_rx);
+
+	if (!strcmp(uart_rx, uart_tx))
+		fd_tx = SAFE_OPEN(uart_tx, O_WRONLY);
+	else
+		fd_tx = setup_uart(&opts_out, O_WRONLY, &old_term_tx);
+
+	if (!SAFE_FORK()) {
+		ioctl(fd_rx, TIOCGICOUNT, &old_counters);
+		stress_test_uart(&opts_in, fd_rx, data, data_len);
+		ret = ioctl(fd_rx, TIOCGICOUNT, &new_counters);
+		if (ret > 0)
+			print_counters("RX:", &old_counters, &new_counters);
+		exit(0);
+	}
+
+	if (!SAFE_FORK()) {
+		ioctl(fd_tx, TIOCGICOUNT, &old_counters);
+		stress_test_uart(&opts_out, fd_tx, data, data_len);
+		ret = ioctl(fd_tx, TIOCGICOUNT, &new_counters);
+		if (ret > 0)
+			print_counters("TX:", &old_counters, &new_counters);
+		exit(0);
+	}
+
+	SAFE_WAIT(NULL);
+	SAFE_WAIT(NULL);
+
+	restore_uart(fd_rx, &old_term_rx);
+
+	if (strcmp(uart_rx, uart_tx))
+		restore_uart(fd_tx, &old_term_tx);
+
+	close(fd_rx);
+	close(fd_tx);
+}
+
+static void map_file(const char *fname)
+{
+	struct stat st;
+	int fd;
+
+	fd = SAFE_OPEN(fname, O_RDONLY);
+
+	SAFE_FSTAT(fd, &st);
+
+	data_len = st.st_size;
+
+	data = SAFE_MMAP(NULL, data_len, PROT_READ,
+	                 MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0);
+
+	tst_res(TINFO, "Mapped file '%s' size %li bytes", fname, data_len);
+
+	SAFE_CLOSE(fd);
+}
+
+static void map_buffer(long buf_size)
+{
+	size_t i;
+
+	data_len = buf_size;
+
+	data = SAFE_MMAP(NULL, data_len, PROT_READ | PROT_WRITE,
+	                 MAP_ANONYMOUS | MAP_SHARED | MAP_LOCKED, -1, 0);
+
+	long *p = (void*)data;
+
+	srandom(time(NULL));
+
+	for (i = 0; i < data_len / sizeof(long); i++)
+		p[i] = random();
+
+	tst_res(TINFO, "Mapped anynymous memory size %li bytes", data_len);
+}
+
+static char *baud_rate;
+static char *hwflow;
+static char *fname;
+static char *buf_size;
+
+static void setup(void)
+{
+	long size = 1024;
+
+	if (baud_rate)
+		tst_parse_int(baud_rate, &(opts.baud_rate), 0, INT_MAX);
+
+	if (hwflow)
+		opts.hwflow = 1;
+
+	if (fname && buf_size)
+		tst_brk(TBROK, "Only one of -f and -s could be set!");
+
+	if (buf_size)
+		tst_parse_long(buf_size, &size, 0, LONG_MAX);
+
+	uart_rx = getenv("UART_RX");
+	uart_tx = getenv("UART_TX");
+
+	if (fname)
+		map_file(fname);
+	else
+		map_buffer(size);
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.test_all = run,
+	.options = (struct tst_option[]) {
+		{"b:", &baud_rate, "-b       Baud rate (9600, ...)"},
+		{"w",  &hwflow   , "-w       Enable hwflow (RTS/CTS)"},
+		{"f:",  &fname,    "-f       Binary file for transfers"},
+		{"s:",  &buf_size, "-s       Binary buffer size"},
+		{}
+	},
+	.needs_devices = (const char *const[]) {"UART_RX", "UART_TX", NULL},
+	.forks_child = 1,
+};