Message ID | 1605566782-38013-6-git-send-email-hemantk@codeaurora.org (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | userspace MHI client interface driver | expand |
On Mon, 16 Nov 2020 at 23:46, Hemant Kumar <hemantk@codeaurora.org> wrote: > > Loopback test opens the MHI device file node and writes > a data buffer to it. MHI UCI kernel space driver copies > the data and sends it to MHI uplink (Tx) LOOPBACK channel. > MHI device loops back the same data to MHI downlink (Rx) > LOOPBACK channel. This data is read by test application > and compared against the data sent. Test passes if data > buffer matches between Tx and Rx. Test application performs > open(), poll(), write(), read() and close() file operations. > > Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> > --- > Documentation/mhi/uci.rst | 32 + > tools/testing/selftests/Makefile | 1 + > tools/testing/selftests/drivers/.gitignore | 1 + > tools/testing/selftests/drivers/mhi/Makefile | 8 + > tools/testing/selftests/drivers/mhi/config | 2 + > .../testing/selftests/drivers/mhi/loopback_test.c | 802 +++++++++++++++++++++ > 6 files changed, 846 insertions(+) > create mode 100644 tools/testing/selftests/drivers/mhi/Makefile > create mode 100644 tools/testing/selftests/drivers/mhi/config > create mode 100644 tools/testing/selftests/drivers/mhi/loopback_test.c > > diff --git a/Documentation/mhi/uci.rst b/Documentation/mhi/uci.rst > index ce8740e..0a04afe 100644 > --- a/Documentation/mhi/uci.rst > +++ b/Documentation/mhi/uci.rst > @@ -79,6 +79,38 @@ MHI client driver performs read operation, same data gets looped back to MHI > host using LOOPBACK channel 1. LOOPBACK channel is used to verify data path > and data integrity between MHI Host and MHI device. > > +Loopback Test > +~~~~~~~~~~~~~ > + > +Loopback test application is used to verify data integrity between MHI host and > +MHI device over LOOPBACK channel. This also confirms that basic MHI data path > +is working properly. Test performs write() to send tx buffer to MHI device file > +node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops back > +transmit data to MHI Host. Test application receives data in receive buffer as > +part of read(). It verifies if tx buffer matches rx buffer. Test application > +performs poll() before making write() and read() system calls. Test passes if > +match is found. > + > +Test is present under tools/testing/selftests/drivers/mhi. It is compiled using > +following command in same dir:- > + > +make loopback_test > + > +Test is run using following command arguments:- > + > +loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i > +<number of iterations> > + > +Required argument: > +-c : loopback chardev node > + > +Optional argument: > +-b : transmit buffer size. If not present 1024 bytes size transmit buffer > + is sent. > +-i : Number of iterations to perform, If not present only one transmit buffer > + is sent. > +-l : Log level. If not present defaults to DBG_LVL_INFO. > + > Other Use Cases > --------------- > > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index d9c2835..084bc1e 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -10,6 +10,7 @@ TARGETS += core > TARGETS += cpufreq > TARGETS += cpu-hotplug > TARGETS += drivers/dma-buf > +TARGETS += drivers/mhi > TARGETS += efivarfs > TARGETS += exec > TARGETS += filesystems > diff --git a/tools/testing/selftests/drivers/.gitignore b/tools/testing/selftests/drivers/.gitignore > index ca74f2e..e4806d5 100644 > --- a/tools/testing/selftests/drivers/.gitignore > +++ b/tools/testing/selftests/drivers/.gitignore > @@ -1,2 +1,3 @@ > # SPDX-License-Identifier: GPL-2.0-only > /dma-buf/udmabuf > +/mhi/loopback_test > diff --git a/tools/testing/selftests/drivers/mhi/Makefile b/tools/testing/selftests/drivers/mhi/Makefile > new file mode 100644 > index 0000000..c06c925 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/Makefile > @@ -0,0 +1,8 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +CFLAGS += -I../../../../../usr/include/ -g -Wall > + > +LDLIBS = -lpthread > +TEST_GEN_PROGS := loopback_test > + > +include ../../lib.mk > + > diff --git a/tools/testing/selftests/drivers/mhi/config b/tools/testing/selftests/drivers/mhi/config > new file mode 100644 > index 0000000..471dc92 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/config > @@ -0,0 +1,2 @@ > +CONFIG_MHI_BUS=y > +CONFIG_MHi_UCI=y CONFIG_MHI_UCI > diff --git a/tools/testing/selftests/drivers/mhi/loopback_test.c b/tools/testing/selftests/drivers/mhi/loopback_test.c > new file mode 100644 > index 0000000..99b7712 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/loopback_test.c > @@ -0,0 +1,802 @@ > +// SPDX-License-Identifier: GPL-2.0 [...]
On Mon, Nov 16, 2020 at 02:46:22PM -0800, Hemant Kumar wrote: > Loopback test opens the MHI device file node and writes > a data buffer to it. MHI UCI kernel space driver copies > the data and sends it to MHI uplink (Tx) LOOPBACK channel. > MHI device loops back the same data to MHI downlink (Rx) > LOOPBACK channel. This data is read by test application > and compared against the data sent. Test passes if data > buffer matches between Tx and Rx. Test application performs > open(), poll(), write(), read() and close() file operations. > > Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> One nitpick below, with that addressed: Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> > --- > Documentation/mhi/uci.rst | 32 + > tools/testing/selftests/Makefile | 1 + > tools/testing/selftests/drivers/.gitignore | 1 + > tools/testing/selftests/drivers/mhi/Makefile | 8 + > tools/testing/selftests/drivers/mhi/config | 2 + > .../testing/selftests/drivers/mhi/loopback_test.c | 802 +++++++++++++++++++++ > 6 files changed, 846 insertions(+) > create mode 100644 tools/testing/selftests/drivers/mhi/Makefile > create mode 100644 tools/testing/selftests/drivers/mhi/config > create mode 100644 tools/testing/selftests/drivers/mhi/loopback_test.c > > diff --git a/Documentation/mhi/uci.rst b/Documentation/mhi/uci.rst > index ce8740e..0a04afe 100644 > --- a/Documentation/mhi/uci.rst > +++ b/Documentation/mhi/uci.rst > @@ -79,6 +79,38 @@ MHI client driver performs read operation, same data gets looped back to MHI > host using LOOPBACK channel 1. LOOPBACK channel is used to verify data path > and data integrity between MHI Host and MHI device. > > +Loopback Test > +~~~~~~~~~~~~~ > + > +Loopback test application is used to verify data integrity between MHI host and > +MHI device over LOOPBACK channel. This also confirms that basic MHI data path > +is working properly. Test performs write() to send tx buffer to MHI device file > +node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops back > +transmit data to MHI Host. Test application receives data in receive buffer as > +part of read(). It verifies if tx buffer matches rx buffer. Test application > +performs poll() before making write() and read() system calls. Test passes if > +match is found. > + > +Test is present under tools/testing/selftests/drivers/mhi. It is compiled using > +following command in same dir:- > + > +make loopback_test > + > +Test is run using following command arguments:- > + > +loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i > +<number of iterations> > + > +Required argument: > +-c : loopback chardev node > + > +Optional argument: > +-b : transmit buffer size. If not present 1024 bytes size transmit buffer > + is sent. > +-i : Number of iterations to perform, If not present only one transmit buffer > + is sent. > +-l : Log level. If not present defaults to DBG_LVL_INFO. > + > Other Use Cases > --------------- > > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index d9c2835..084bc1e 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -10,6 +10,7 @@ TARGETS += core > TARGETS += cpufreq > TARGETS += cpu-hotplug > TARGETS += drivers/dma-buf > +TARGETS += drivers/mhi > TARGETS += efivarfs > TARGETS += exec > TARGETS += filesystems > diff --git a/tools/testing/selftests/drivers/.gitignore b/tools/testing/selftests/drivers/.gitignore > index ca74f2e..e4806d5 100644 > --- a/tools/testing/selftests/drivers/.gitignore > +++ b/tools/testing/selftests/drivers/.gitignore > @@ -1,2 +1,3 @@ > # SPDX-License-Identifier: GPL-2.0-only > /dma-buf/udmabuf > +/mhi/loopback_test > diff --git a/tools/testing/selftests/drivers/mhi/Makefile b/tools/testing/selftests/drivers/mhi/Makefile > new file mode 100644 > index 0000000..c06c925 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/Makefile > @@ -0,0 +1,8 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +CFLAGS += -I../../../../../usr/include/ -g -Wall > + > +LDLIBS = -lpthread > +TEST_GEN_PROGS := loopback_test > + > +include ../../lib.mk > + > diff --git a/tools/testing/selftests/drivers/mhi/config b/tools/testing/selftests/drivers/mhi/config > new file mode 100644 > index 0000000..471dc92 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/config > @@ -0,0 +1,2 @@ > +CONFIG_MHI_BUS=y > +CONFIG_MHi_UCI=y > diff --git a/tools/testing/selftests/drivers/mhi/loopback_test.c b/tools/testing/selftests/drivers/mhi/loopback_test.c > new file mode 100644 > index 0000000..99b7712 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/loopback_test.c > @@ -0,0 +1,802 @@ > + [...] > +int main(int argc, char *argv[]) > +{ > + int ret = 0; > + > + loopback_test_set_defaults(); > + test_log(DBG_LVL_INFO, "MHI Loopback test App\n"); > + > + if (argc > 1) > + ret = loopback_test_parse(argc, argv); Effectively this functions does parse and run, so this should be called as, "loopback_test_parse_run" or pthread creation should be moved here. Thanks, Mani > + else > + usage(); > + > + return ret; > +} > -- > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project >
On 11/16/20 3:46 PM, Hemant Kumar wrote: > Loopback test opens the MHI device file node and writes > a data buffer to it. MHI UCI kernel space driver copies > the data and sends it to MHI uplink (Tx) LOOPBACK channel. > MHI device loops back the same data to MHI downlink (Rx) > LOOPBACK channel. This data is read by test application > and compared against the data sent. Test passes if data > buffer matches between Tx and Rx. Test application performs > open(), poll(), write(), read() and close() file operations. > > Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> > --- > Documentation/mhi/uci.rst | 32 + > tools/testing/selftests/Makefile | 1 + > tools/testing/selftests/drivers/.gitignore | 1 + > tools/testing/selftests/drivers/mhi/Makefile | 8 + > tools/testing/selftests/drivers/mhi/config | 2 + > .../testing/selftests/drivers/mhi/loopback_test.c | 802 +++++++++++++++++++++ > 6 files changed, 846 insertions(+) > create mode 100644 tools/testing/selftests/drivers/mhi/Makefile > create mode 100644 tools/testing/selftests/drivers/mhi/config > create mode 100644 tools/testing/selftests/drivers/mhi/loopback_test.c > > diff --git a/Documentation/mhi/uci.rst b/Documentation/mhi/uci.rst > index ce8740e..0a04afe 100644 > --- a/Documentation/mhi/uci.rst > +++ b/Documentation/mhi/uci.rst > @@ -79,6 +79,38 @@ MHI client driver performs read operation, same data gets looped back to MHI > host using LOOPBACK channel 1. LOOPBACK channel is used to verify data path > and data integrity between MHI Host and MHI device. > Nice. > +Loopback Test > +~~~~~~~~~~~~~ > + > +Loopback test application is used to verify data integrity between MHI host and > +MHI device over LOOPBACK channel. This also confirms that basic MHI data path > +is working properly. Test performs write() to send tx buffer to MHI device file > +node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops back > +transmit data to MHI Host. Test application receives data in receive buffer as > +part of read(). It verifies if tx buffer matches rx buffer. Test application > +performs poll() before making write() and read() system calls. Test passes if > +match is found. > + > +Test is present under tools/testing/selftests/drivers/mhi. It is compiled using > +following command in same dir:- > + > +make loopback_test > + > +Test is run using following command arguments:- > + > +loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i > +<number of iterations> > + > +Required argument: > +-c : loopback chardev node > + > +Optional argument: > +-b : transmit buffer size. If not present 1024 bytes size transmit buffer > + is sent. > +-i : Number of iterations to perform, If not present only one transmit buffer > + is sent. > +-l : Log level. If not present defaults to DBG_LVL_INFO. > + > Other Use Cases > --------------- > > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index d9c2835..084bc1e 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -10,6 +10,7 @@ TARGETS += core > TARGETS += cpufreq > TARGETS += cpu-hotplug > TARGETS += drivers/dma-buf > +TARGETS += drivers/mhi > TARGETS += efivarfs > TARGETS += exec > TARGETS += filesystems > diff --git a/tools/testing/selftests/drivers/.gitignore b/tools/testing/selftests/drivers/.gitignore > index ca74f2e..e4806d5 100644 > --- a/tools/testing/selftests/drivers/.gitignore > +++ b/tools/testing/selftests/drivers/.gitignore > @@ -1,2 +1,3 @@ > # SPDX-License-Identifier: GPL-2.0-only > /dma-buf/udmabuf > +/mhi/loopback_test > diff --git a/tools/testing/selftests/drivers/mhi/Makefile b/tools/testing/selftests/drivers/mhi/Makefile > new file mode 100644 > index 0000000..c06c925 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/Makefile > @@ -0,0 +1,8 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +CFLAGS += -I../../../../../usr/include/ -g -Wall > + > +LDLIBS = -lpthread > +TEST_GEN_PROGS := loopback_test > + > +include ../../lib.mk > + > diff --git a/tools/testing/selftests/drivers/mhi/config b/tools/testing/selftests/drivers/mhi/config > new file mode 100644 > index 0000000..471dc92 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/config > @@ -0,0 +1,2 @@ > +CONFIG_MHI_BUS=y > +CONFIG_MHi_UCI=y > diff --git a/tools/testing/selftests/drivers/mhi/loopback_test.c b/tools/testing/selftests/drivers/mhi/loopback_test.c > new file mode 100644 > index 0000000..99b7712 > --- /dev/null > +++ b/tools/testing/selftests/drivers/mhi/loopback_test.c > @@ -0,0 +1,802 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Loopback test application performs write() to send tx buffer to MHI device > + * file node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops > + * back transmit data to MHI Host. Test application receives data in receive > + * buffer as part of read(). It verifies if tx buffer matches rx buffer. Test > + * application performs poll() before making write() and read() system > + * calls. Test passes if match is found. > + * > + * Test is compiled using following command:- > + * > + * make loopback_test > + * > + * Test is run using following command arguments:- > + * > + * loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i > + * <number of iterations> > + * > + * Required argument: > + * -c : loopback chardev node > + * > + * Optional argument: > + * -b : transmit buffer size. If not present 1024 bytes size transmit buffer > + * is sent. > + * -i : Number of iterations to perform, If not present only one transmit buffer > + * is sent. > + * -l : Log level. If not present defaults to DBG_LVL_INFO. > + */ > + > +#include <errno.h> > +#include <fcntl.h> > +#include <getopt.h> > +#include <pthread.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/poll.h> > +#include <unistd.h> > + > +enum debug_level { > + DBG_LVL_VERBOSE, > + DBG_LVL_INFO, > + DBG_LVL_ERROR, > +}; > + > +enum test_status { > + TEST_STATUS_SUCCESS, > + TEST_STATUS_ERROR, > + TEST_STATUS_NO_DEV, > +}; > + Since you are running this test as part of kselftest, please use ksft errors nd returns. > +struct lb_test_ctx { > + char dev_node[256]; > + unsigned char *tx_buff; > + unsigned char *rx_buff; > + unsigned int rx_pkt_count; > + unsigned int tx_pkt_count; > + int iterations; > + bool iter_complete; > + bool comp_complete; > + bool test_complete; > + bool all_complete; > + unsigned long buff_size; > + long byte_recvd; > + long byte_sent; > +}; > + > +bool force_exit; > +char write_data = 'a'; > +int completed_iterations; > + > +struct lb_test_ctx test_ctxt; > +enum debug_level msg_lvl; > +struct pollfd read_fd; > +struct pollfd write_fd; > +enum test_status mhi_test_return_value; > +enum test_status tx_status; > +enum test_status rx_status; > +enum test_status cmp_rxtx_status; > + > +#define test_log(test_msg_lvl, format, ...) do { \ > + if (test_msg_lvl >= msg_lvl) \ > + fprintf(stderr, format, ##__VA_ARGS__); \ > +} while (0) > + > +static void loopback_test_sleep_ms(int ms) > +{ > + usleep(1000 * ms); > +} > + Have you run this as part of "make kselftest" run. How does this sleep work in that env.? > +static enum test_status loopback_test_open(struct pollfd *write_fd, > + struct pollfd *read_fd, char *dev_name) > +{ > + enum test_status ret_val = TEST_STATUS_SUCCESS; > + > + write_fd->fd = open(dev_name, O_SYNC | O_WRONLY); > + if (write_fd->fd < 0) { > + test_log(DBG_LVL_ERROR, "Error opening file for w: errno: %d\n", errno); > + ret_val = TEST_STATUS_ERROR; > + } else { > + test_log(DBG_LVL_INFO, "Opened %s, return code: %i.\n", > + dev_name, write_fd->fd); > + write_fd->events = POLLOUT; > + } > + > + read_fd->fd = open(dev_name, O_SYNC | O_RDONLY); > + if (read_fd->fd < 0) { > + test_log(DBG_LVL_ERROR, "Error opening file for r: errno: %d\n", errno); > + ret_val = TEST_STATUS_ERROR; > + } else { > + test_log(DBG_LVL_INFO, "Opened %s, return code: %i.\n", > + dev_name, read_fd->fd); > + read_fd->events = POLLIN; > + } > + > + return ret_val; > +} > + > +static enum test_status loopback_test_close(struct pollfd *write_fd, > + struct pollfd *read_fd) > +{ > + enum test_status ret_val = TEST_STATUS_SUCCESS; > + > + if (close(read_fd->fd) == 0) { > + test_log(DBG_LVL_INFO, "Closed device handle\n"); > + } else { > + test_log(DBG_LVL_ERROR, "Unable to close handle to device\n"); > + ret_val = TEST_STATUS_ERROR; > + } > + > + if (close(write_fd->fd) == 0) { > + test_log(DBG_LVL_INFO, "Closed device handle\n"); > + } else { > + test_log(DBG_LVL_ERROR, "Unable to close handle to device\n"); > + ret_val = TEST_STATUS_ERROR; > + } > + This should match the KSFT error codes > + return ret_val; > +} > + > +static enum test_status loopback_test_alloc_rx_tx_buff(void) > +{ > + test_ctxt.rx_buff = (unsigned char *)malloc(test_ctxt.buff_size); > + if (!test_ctxt.rx_buff) { > + test_log(DBG_LVL_ERROR, "Failed to allocate rx buff\n"); > + return TEST_STATUS_ERROR; > + } > + > + memset(test_ctxt.rx_buff, 0x00, sizeof(test_ctxt.buff_size)); > + test_ctxt.tx_buff = (unsigned char *)malloc(test_ctxt.buff_size); > + if (!test_ctxt.tx_buff) { > + test_log(DBG_LVL_ERROR, "Failed to allocate tx buff\n"); > + return TEST_STATUS_ERROR; > + } > + > + return TEST_STATUS_SUCCESS; > +} > + > +static enum test_status loopback_test_write(struct pollfd *write_fd) > +{ > + enum test_status ret_value = TEST_STATUS_SUCCESS; > + signed long temp_bytes_sent = 0; > + signed long total_bytes_sent = 0; > + > + test_log(DBG_LVL_VERBOSE, "About to write %lu\n", test_ctxt.buff_size); > + > + temp_bytes_sent = write(write_fd->fd, > + test_ctxt.tx_buff + test_ctxt.byte_sent, test_ctxt.buff_size); > + > + test_log(DBG_LVL_VERBOSE, "Returned %li\n", temp_bytes_sent); > + test_log(DBG_LVL_INFO, "Wrote packet with %li bytes of %02x\n", > + temp_bytes_sent, *(test_ctxt.tx_buff + test_ctxt.byte_sent)); > + > + if (temp_bytes_sent < 0) { > + /* write returned a negative value i.e. write failed */ > + test_log(DBG_LVL_ERROR, "write() failed: err code %d\n", errno); > + return TEST_STATUS_ERROR; > + } > + > + if ((unsigned long)temp_bytes_sent > test_ctxt.buff_size) { > + test_log(DBG_LVL_ERROR, "Wrote more bytes than intended\n"); > + return TEST_STATUS_ERROR; > + } > + > + if (temp_bytes_sent == 0) { > + loopback_test_sleep_ms(10); > + } else if (temp_bytes_sent > 0) { > + test_ctxt.tx_pkt_count++; > + test_ctxt.byte_sent += temp_bytes_sent; > + total_bytes_sent += temp_bytes_sent; > + test_log(DBG_LVL_INFO, "Total written packets %d\n", > + test_ctxt.tx_pkt_count); > + } > + > + return ret_value; > +} > + > +static void loopback_test_reset_ctxt(void) > +{ > + test_ctxt.byte_sent = 0; > + __sync_synchronize(); > + test_ctxt.byte_recvd = 0; > + test_ctxt.rx_pkt_count = 0; > + test_ctxt.tx_pkt_count = 0; > + test_ctxt.comp_complete = false; > + memset((void *)test_ctxt.rx_buff, 0x00, test_ctxt.buff_size); > +} > + > +static enum test_status loopback_test_time_out(int *time_count) > +{ > + enum test_status ret_val = TEST_STATUS_SUCCESS; > + > + loopback_test_sleep_ms(3); > + if ((++*time_count) > 100000) { > + test_log(DBG_LVL_ERROR, "Error: all bytes not received for this iteration\n"); > + ret_val = TEST_STATUS_ERROR; > + while (1) > + loopback_test_sleep_ms(1000); > + } > + > + return ret_val; > +} > + > +static enum test_status loopback_test_poll(struct pollfd *fd, > + bool poll_timeout) > +{ > + enum test_status ret_val = TEST_STATUS_SUCCESS; > + int poll_return = 0; > + int i = 0; > + int timeout = 1000; /* poll-wait 1 second */ > + int file_desc_type = -1; > + > + if (fd->events == POLLIN) { > + file_desc_type = 1; /* read */ > + test_log(DBG_LVL_VERBOSE, > + "Poll: opened read handle to device\n"); > + } else if (fd->events == POLLOUT) { > + file_desc_type = 0; /* write */ > + test_log(DBG_LVL_VERBOSE, > + "Poll: opened write handle to device\n"); > + } > + > + if (poll_timeout == false) > + timeout = -1; /* poll-wait forever */ > + > + while (ret_val == TEST_STATUS_SUCCESS) { > + if (force_exit == true) { > + ret_val = TEST_STATUS_ERROR; > + } else { > + test_log(DBG_LVL_VERBOSE, "%s Polling %i\n", > + file_desc_type ? "Read" : "Write", i); > + > + poll_return = poll(fd, 1, timeout); > + if (poll_return == -1) { > + test_log(DBG_LVL_ERROR, "%s Poll error - %d\n", > + file_desc_type ? "Read" : "Write", > + errno); > + return TEST_STATUS_ERROR; > + } else if (((file_desc_type == 1) && > + (fd->revents & POLLIN)) || > + ((file_desc_type == 0) && > + (fd->revents & POLLOUT))) { > + test_log(DBG_LVL_VERBOSE, > + "%s Poll Returned\n", > + file_desc_type ? "Read" : "Write"); > + return TEST_STATUS_SUCCESS; > + } else if (poll_timeout == true) { > + i++; > + poll_return = 0; > + if (i >= 250) { > + test_log(DBG_LVL_ERROR, > + "Poll: %s Timed out: 10s\n", > + file_desc_type ? "Read" : "Write"); > + return TEST_STATUS_ERROR; > + } > + } > + > + loopback_test_sleep_ms(10); > + } > + } > + > + return ret_val; > +} > + > +static void *loopback_test_rx(void *data) > +{ > + bool poll_timeout = true; > + unsigned long read_size = test_ctxt.buff_size; > + long temp_bytes_rec; > + enum test_status status; > + > + rx_status = TEST_STATUS_SUCCESS; > + > + while (force_exit == false) { > + if (test_ctxt.test_complete) { > + test_log(DBG_LVL_INFO, "Rx: exiting thread\n"); > + pthread_exit(&rx_status); > + return NULL; > + } > + > + while ((test_ctxt.byte_sent > test_ctxt.byte_recvd) && > + (force_exit == false)) { > + test_log(DBG_LVL_VERBOSE, > + "Rx: Bytes Sent: %li, Received %li\n", > + test_ctxt.byte_sent, test_ctxt.byte_recvd); > + > + temp_bytes_rec = 0; > + > + if (test_ctxt.comp_complete == false) { > + status = loopback_test_poll(&read_fd, > + poll_timeout); > + if (status == TEST_STATUS_ERROR) { > + if (test_ctxt.test_complete == true) > + rx_status = TEST_STATUS_SUCCESS; > + else > + rx_status = TEST_STATUS_ERROR; > + > + test_log(DBG_LVL_INFO, > + "Rx: exiting thread\n"); > + pthread_exit(&rx_status); > + return NULL; > + } > + > + test_log(DBG_LVL_VERBOSE, "Rx: before read\n"); > + temp_bytes_rec = read(read_fd.fd, > + test_ctxt.rx_buff + test_ctxt.byte_recvd, > + read_size); > + if (temp_bytes_rec < 0) { > + test_log(DBG_LVL_ERROR, > + "Rx: read() failed - err code %d\n", > + errno); > + rx_status = TEST_STATUS_ERROR; > + pthread_exit(&rx_status); > + return NULL; > + } > + > + if (temp_bytes_rec == 0) { > + loopback_test_sleep_ms(10); > + rx_status = TEST_STATUS_ERROR; > + pthread_exit(&rx_status); > + return NULL; > + } > + > + test_ctxt.rx_pkt_count++; > + test_log(DBG_LVL_INFO, > + "Rx: Read packet with %li bytes of %02x\n", > + temp_bytes_rec, > + *(test_ctxt.rx_buff + test_ctxt.byte_recvd)); > + test_ctxt.byte_recvd += temp_bytes_rec; > + test_log(DBG_LVL_INFO, > + "Rx: Total byte_recvd: %li\n", > + test_ctxt.byte_recvd); > + test_log(DBG_LVL_INFO, > + "Rx: Total read packets %d\n", > + test_ctxt.rx_pkt_count); > + test_log(DBG_LVL_INFO, > + "Rx: %lu byte buff_size\n", > + test_ctxt.buff_size); > + > + read_fd.revents = 0; > + } > + } > + } > + rx_status = TEST_STATUS_ERROR; > + pthread_exit(&rx_status); > + return NULL; > +} > + > +/* This single compare thread, will compare each byte of tx and rx Buff */ > +static void *loopback_test_compare_rxtx(void *data) > +{ > + int k; > + bool completion = true; > + long curr_ptr = 0; > + > + test_log(DBG_LVL_INFO, "CompareRxTxThread: Inside thread\n"); > + cmp_rxtx_status = TEST_STATUS_SUCCESS; > + > + for (;;) { > + /* if the test is completed exit thread */ > + if (test_ctxt.all_complete == true) { > + curr_ptr = 0; > + test_log(DBG_LVL_INFO, > + "CompareRxTx: exiting thread\n"); > + pthread_exit(&cmp_rxtx_status); > + return NULL; > + } > + > + /* Simply compares each byte in each Tx and Rx buffer */ > + while (curr_ptr < test_ctxt.byte_recvd) { > + if (*(test_ctxt.rx_buff + curr_ptr) != > + *(test_ctxt.tx_buff + curr_ptr)) { > + test_log(DBG_LVL_ERROR, > + "Mismatch Byte num %li\n", curr_ptr); > + test_log(DBG_LVL_ERROR, > + "Bytes: Sent 0x%x, Received 0x%x\n", > + *(test_ctxt.tx_buff + curr_ptr), > + *(test_ctxt.rx_buff + curr_ptr)); > + test_log(DBG_LVL_VERBOSE, "Rx buffer dump:\n"); > + for (k = 0; k < 1024; k++) > + test_log(DBG_LVL_VERBOSE, "%d:0x%x ", k, > + *(test_ctxt.rx_buff + k)); > + > + test_log(DBG_LVL_VERBOSE, "\n"); > + > + test_log(DBG_LVL_INFO, > + "CompareRxTx: finished iterations %i\n", > + completed_iterations); > + test_log(DBG_LVL_INFO, > + "CompareRxTx: Total pkts sent %u\n", > + test_ctxt.tx_pkt_count); > + > + curr_ptr = 0; > + force_exit = true; > + cmp_rxtx_status = TEST_STATUS_ERROR; > + pthread_exit(&cmp_rxtx_status); > + return NULL; > + } > + > + test_log(DBG_LVL_VERBOSE, > + "CompareRxTx: Bytes Sent 0x%x, Received 0x%x\n", > + *(test_ctxt.tx_buff + curr_ptr), > + *(test_ctxt.rx_buff + curr_ptr)); > + curr_ptr++; > + } > + > + /* > + * completion will start as true and change depending if the > + * iterations has been completed for all client threads > + */ > + if (curr_ptr >= test_ctxt.byte_sent) > + completion = (completion && test_ctxt.iter_complete); > + else > + completion = false; > + > + /* > + * Once all client threads have finished their iteration, reset > + * everything and allow the client thread to start their next > + * iteration > + */ > + if (completion) { > + test_ctxt.byte_sent = 0; > + __sync_synchronize(); > + test_ctxt.byte_recvd = 0; > + test_ctxt.iter_complete = false; > + test_ctxt.comp_complete = true; > + curr_ptr = 0; > + test_log(DBG_LVL_INFO, "CompareRxTx: TX and RX match\n"); > + } else { > + completion = true; > + } > + } > +} > + > +static void loopback_test_display_err_data(void) > +{ > + test_log(DBG_LVL_ERROR, "Wrote %lu bytes out of a %lu buff size\n", > + test_ctxt.byte_sent, test_ctxt.buff_size); > + test_log(DBG_LVL_ERROR, "Remaining bytes to write %lu\n", > + (test_ctxt.buff_size - test_ctxt.byte_sent)); > +} > + > +static enum test_status loopback_test_setup_write_packet(void) > +{ > + bool poll_timeout = true; > + > + if (loopback_test_poll(&write_fd, poll_timeout) == TEST_STATUS_ERROR) > + return TEST_STATUS_ERROR; > + > + test_log(DBG_LVL_VERBOSE, "revents: %d\n", write_fd.revents); > + > + if (write_fd.revents & POLLOUT) { > + memset(test_ctxt.tx_buff + test_ctxt.byte_sent, write_data, > + test_ctxt.buff_size); > + test_log(DBG_LVL_VERBOSE, "Write %02x of size %lu\n", write_data, > + test_ctxt.buff_size); > + > + if (loopback_test_write(&write_fd) != TEST_STATUS_SUCCESS) { > + loopback_test_sleep_ms(1000); > + return TEST_STATUS_ERROR; > + } > + > + test_log(DBG_LVL_INFO, "Total byte_sent %li\n", > + test_ctxt.byte_sent); > + write_fd.revents = 0; > + } > + > + return TEST_STATUS_SUCCESS; > +} > + > +static enum test_status loopback_test_wait_to_get_bytes_back(void) > +{ > + int time_count = 0; > + > + test_log(DBG_LVL_INFO, "Sent %li bytes, received %li bytes\n", > + test_ctxt.byte_sent, test_ctxt.byte_recvd); > + > + /* read thread will fill bytesRec and wait till it equals byte_sent */ > + while (test_ctxt.byte_recvd != test_ctxt.byte_sent) { > + if (loopback_test_time_out(&time_count) == TEST_STATUS_ERROR) > + return TEST_STATUS_ERROR; > + } Same here. > + > + return TEST_STATUS_SUCCESS; > +} Here too > + > +static void *loopback_test_tx(void *data) > +{ > + int i; > + int time_count = 0; > + int failed_iterations = 0; > + enum test_status status = TEST_STATUS_SUCCESS; > + > + tx_status = TEST_STATUS_SUCCESS; > + > + for (i = 0; i < test_ctxt.iterations; i++) { > + loopback_test_reset_ctxt(); > + > + test_ctxt.iter_complete = false; > + > + /* While the bytes sent is less than the buffsize */ > + while (((unsigned int)test_ctxt.byte_sent < test_ctxt.buff_size) > + && (status == TEST_STATUS_SUCCESS)) { > + if (force_exit == true) > + goto exit; > + > + if (status != TEST_STATUS_SUCCESS) { > + loopback_test_display_err_data(); > + tx_status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + status = loopback_test_setup_write_packet(); > + if (status == TEST_STATUS_ERROR) { > + test_log(DBG_LVL_INFO, > + "Failed after %d iterations\n", i); > + failed_iterations++; > + goto exit; > + } > + } > + > + if (loopback_test_wait_to_get_bytes_back() == > + TEST_STATUS_ERROR) { > + tx_status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + __sync_synchronize(); > + > + test_ctxt.iter_complete = true; > + > + while (!test_ctxt.comp_complete) { > + if (loopback_test_time_out(&time_count) == > + TEST_STATUS_ERROR) { > + tx_status = TEST_STATUS_ERROR; > + goto exit; > + } > + } > + time_count = 0; > + } > + > + if (failed_iterations > 0) > + test_log(DBG_LVL_INFO, "Amount of Failed Iterations: %d\n", > + failed_iterations); > + > +exit: > + loopback_test_reset_ctxt(); > + > + /* all of the iterations for this client thread have been completed */ > + test_ctxt.test_complete = true; > + > + /* close the opened file descriptors */ > + loopback_test_close(&write_fd, &read_fd); > + > + test_log(DBG_LVL_INFO, "mhi_test_loopback: exiting thread\n"); > + > + pthread_exit(&tx_status); > + > + return NULL; > +} > + > +/* > + * This function will be called when the user wishes to perform a loopback test. > + * In this function, a compare thread will be created to compare the tx buffer > + * and the rx Buffer. Device file node is opened for write and read. Tx thread > + * is created to send data and Rx thread to receive data. > + */ > +enum test_status loopback_test_thread_create(void) > +{ > + int i = 0; > + enum test_status status = TEST_STATUS_SUCCESS; > + enum test_status *write_thread_retval; > + enum test_status *read_thread_retval; > + enum test_status *compare_thread_retval; > + > + pthread_t compare_rx_tx_thread; > + pthread_t loopback_thread; > + pthread_t receive_thread; > + > + /* set all tests (including open/close) to not completed */ > + test_ctxt.all_complete = false; > + > + /* set current test to not completed */ > + test_ctxt.test_complete = false; > + > + if (loopback_test_alloc_rx_tx_buff() == TEST_STATUS_ERROR) { > + status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + /* Create thread for comparing the rx and tx buffers */ > + if (pthread_create(&compare_rx_tx_thread, NULL, > + loopback_test_compare_rxtx, NULL) != 0) { > + test_log(DBG_LVL_ERROR, "Error creating compare thread\n"); > + status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + test_log(DBG_LVL_INFO, "dev_node: %s\n", test_ctxt.dev_node); > + > + /* closing the device node will happen in the loopback thread */ > + status = loopback_test_open(&write_fd, &read_fd, test_ctxt.dev_node); > + if (status == TEST_STATUS_ERROR) { > + test_log(DBG_LVL_ERROR, "Failed to open device node\n"); > + goto exit; > + } > + > + /* Spawn the loopback tx thread */ > + if (pthread_create(&loopback_thread, NULL, loopback_test_tx, NULL) > + != 0) { > + test_log(DBG_LVL_ERROR, "Error creating tx thread"); > + status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + /* Create thread for receiving packets */ > + if (pthread_create(&receive_thread, NULL, loopback_test_rx, NULL) > + != 0) { > + test_log(DBG_LVL_ERROR, "Error creating rx thread\n"); > + status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + test_log(DBG_LVL_INFO, "Waiting for Tx and Rx threads to complete\n"); > + > + /* wait till all client and receive threads have finished */ > + pthread_join(loopback_thread, (void **)(&write_thread_retval)); > + pthread_join(receive_thread, (void **)(&read_thread_retval)); > + > + test_log(DBG_LVL_VERBOSE, "TX and Rx threads completed\n"); > + > + test_log(DBG_LVL_INFO, "Write thread status: %s\n", > + *write_thread_retval ? "Fail" : "Pass"); > + test_log(DBG_LVL_INFO, "Read thread status: %s\n", > + *read_thread_retval ? "Fail" : "Pass"); > + > + if ((*write_thread_retval == TEST_STATUS_ERROR) || > + (*read_thread_retval == TEST_STATUS_ERROR)) { > + test_log(DBG_LVL_ERROR, "Returned error, exiting\n"); > + status = TEST_STATUS_ERROR; > + goto exit; > + } > + > + test_log(DBG_LVL_INFO, "Thread %i of tests complete\n", i); > + test_ctxt.test_complete = false; > + > + test_log(DBG_LVL_INFO, "All loopback threads completed\n"); > + > + test_ctxt.all_complete = true; > + > + test_log(DBG_LVL_INFO, "Waiting for comp thread to exit\n"); > + > + pthread_join(compare_rx_tx_thread, (void **)(&compare_thread_retval)); > + if (*compare_thread_retval == TEST_STATUS_ERROR) { > + test_log(DBG_LVL_ERROR, "Compare thread returned error\n"); > + status = TEST_STATUS_ERROR; > + } > + > +exit: > + /* All threads have finished using the Tx and Rx Buffers, free them */ > + free(test_ctxt.tx_buff); > + test_ctxt.tx_buff = NULL; > + free(test_ctxt.rx_buff); > + test_ctxt.rx_buff = NULL; > + > + return status; > +} > + > +static void *loopback_test_run(void *data) > +{ > + enum test_status status = TEST_STATUS_SUCCESS; > + > + force_exit = false; > + srand(time(NULL)); > + > + status = loopback_test_thread_create(); > + > + pthread_exit(&status); > + > + return NULL; > +} > + > +static void loopback_test_set_defaults(void) > +{ > + msg_lvl = DBG_LVL_INFO; > + > + memset(&test_ctxt, 0, sizeof(test_ctxt)); > + test_ctxt.iterations = 1; > + test_ctxt.buff_size = 1024; > +} > + > +static void usage(void) > +{ > + test_log(DBG_LVL_INFO, "\nUsage:\n"); > + test_log(DBG_LVL_INFO, "-c: Device node\n"); > + test_log(DBG_LVL_INFO, "-b: Buffer size (default: 1024 bytes)\n"); > + test_log(DBG_LVL_INFO, "-i: Number of iterations\n"); > + test_log(DBG_LVL_INFO, "-l: log level, default - 1\n"); > + test_log(DBG_LVL_INFO, "log level : 0 -verbose, 1 -info , 2 -err\n"); > +} > + > +static int loopback_test_parse(int argc, char *argv[]) > +{ > + int i = 0; > + int command = 0; > + int indexptr = 0; > + char *optstr = "-:c:b:i:l:"; > + int *return_value = NULL; > + int status = -EINVAL; > + pthread_t run_test; > + > + optind = 1; > + > + static const struct option optlongstr[] = { > + { "device node", required_argument, NULL, 'c'}, > + {0, 0, 0, 0} > + }; > + > + test_log(DBG_LVL_INFO, "\nStarting MHI loopback tests\n"); > + > + test_log(DBG_LVL_VERBOSE, "argc: %d\n", argc); > + for (i = 0; i < argc; i++) > + test_log(DBG_LVL_VERBOSE, "argv[%i] = %s; ", i, argv[i]); > + > + while ((command = getopt_long(argc, (char **)argv, optstr, optlongstr, > + &indexptr)) && (command != -1)) { > + switch (command) { > + case 'c': > + sprintf(test_ctxt.dev_node, "%s", optarg); > + test_log(DBG_LVL_INFO, "%s\n", test_ctxt.dev_node); > + break; > + case 'b': > + test_ctxt.buff_size = atoi(optarg); > + test_log(DBG_LVL_INFO, "%lu\n", test_ctxt.buff_size); > + break; > + case 'i': > + test_ctxt.iterations = atoi(optarg); > + test_log(DBG_LVL_INFO, "%u\n", test_ctxt.iterations); > + break; > + case 'l': > + msg_lvl = atoi(optarg); > + test_log(DBG_LVL_INFO, "log level: %d\n", atoi(optarg)); > + break; > + default: > + test_log(DBG_LVL_ERROR, "Invalid Option: %d\n", > + command); > + usage(); > + return TEST_STATUS_ERROR; > + } > + } > + > + if (pthread_create(&run_test, NULL, loopback_test_run, NULL) != 0) { > + test_log(DBG_LVL_ERROR, "Error creating run_mhi_test\n"); > + } else { > + pthread_join(run_test, (void **)(&return_value)); > + > + if (*return_value != TEST_STATUS_SUCCESS) { > + test_log(DBG_LVL_ERROR, "Test Failed\n"); > + } else { > + test_log(DBG_LVL_INFO, "Test Passed\n"); > + status = 0; > + } > + } > + > + return status; > +} > + > +int main(int argc, char *argv[]) > +{ > + int ret = 0; > + > + loopback_test_set_defaults(); > + test_log(DBG_LVL_INFO, "MHI Loopback test App\n"); > + > + if (argc > 1) > + ret = loopback_test_parse(argc, argv); > + else > + usage(); > + > + return ret; > +} > Are there any cases where this test can't run and have to - those cases need to be skips. thanks, -- Shuah
Hi Mani, On 11/19/20 10:10 PM, Manivannan Sadhasivam wrote: > On Mon, Nov 16, 2020 at 02:46:22PM -0800, Hemant Kumar wrote: >> Loopback test opens the MHI device file node and writes >> a data buffer to it. MHI UCI kernel space driver copies >> the data and sends it to MHI uplink (Tx) LOOPBACK channel. >> MHI device loops back the same data to MHI downlink (Rx) >> LOOPBACK channel. This data is read by test application >> and compared against the data sent. Test passes if data >> buffer matches between Tx and Rx. Test application performs >> open(), poll(), write(), read() and close() file operations. >> >> Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> > > One nitpick below, with that addressed: > > Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> [..] > > Effectively this functions does parse and run, so this should be called > as, "loopback_test_parse_run" or pthread creation should be moved here. Done.
Hi Shuah, On 11/20/20 7:26 AM, Shuah Khan wrote: > On 11/16/20 3:46 PM, Hemant Kumar wrote: >> Loopback test opens the MHI device file node and writes >> a data buffer to it. MHI UCI kernel space driver copies >> the data and sends it to MHI uplink (Tx) LOOPBACK channel. >> MHI device loops back the same data to MHI downlink (Rx) >> LOOPBACK channel. This data is read by test application >> and compared against the data sent. Test passes if data >> buffer matches between Tx and Rx. Test application performs >> open(), poll(), write(), read() and close() file operations. >> >> Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> >> --- >> Documentation/mhi/uci.rst | 32 + >> tools/testing/selftests/Makefile | 1 + >> tools/testing/selftests/drivers/.gitignore | 1 + >> tools/testing/selftests/drivers/mhi/Makefile | 8 + >> tools/testing/selftests/drivers/mhi/config | 2 + >> .../testing/selftests/drivers/mhi/loopback_test.c | 802 >> +++++++++++++++++++++ >> 6 files changed, 846 insertions(+) >> create mode 100644 tools/testing/selftests/drivers/mhi/Makefile >> create mode 100644 tools/testing/selftests/drivers/mhi/config >> create mode 100644 tools/testing/selftests/drivers/mhi/loopback_test.c >> >> diff --git a/Documentation/mhi/uci.rst b/Documentation/mhi/uci.rst >> index ce8740e..0a04afe 100644 >> --- a/Documentation/mhi/uci.rst >> +++ b/Documentation/mhi/uci.rst >> @@ -79,6 +79,38 @@ MHI client driver performs read operation, same >> data gets looped back to MHI >> host using LOOPBACK channel 1. LOOPBACK channel is used to verify >> data path >> and data integrity between MHI Host and MHI device. > > Nice. [..] >> + >> +enum debug_level { >> + DBG_LVL_VERBOSE, >> + DBG_LVL_INFO, >> + DBG_LVL_ERROR, >> +}; >> + >> +enum test_status { >> + TEST_STATUS_SUCCESS, >> + TEST_STATUS_ERROR, >> + TEST_STATUS_NO_DEV, >> +}; >> + > > Since you are running this test as part of kselftest, please use > ksft errors nd returns. Are you suggesting to use following macros instead of test_status enum ? #define KSFT_PASS 0 #define KSFT_FAIL 1 > >> +struct lb_test_ctx { >> + char dev_node[256]; >> + unsigned char *tx_buff; >> + unsigned char *rx_buff; >> + unsigned int rx_pkt_count; >> + unsigned int tx_pkt_count; >> + int iterations; >> + bool iter_complete; >> + bool comp_complete; >> + bool test_complete; >> + bool all_complete; >> + unsigned long buff_size; >> + long byte_recvd; >> + long byte_sent; >> +}; >> + >> +bool force_exit; >> +char write_data = 'a'; >> +int completed_iterations; >> + >> +struct lb_test_ctx test_ctxt; >> +enum debug_level msg_lvl; >> +struct pollfd read_fd; >> +struct pollfd write_fd; >> +enum test_status mhi_test_return_value; >> +enum test_status tx_status; >> +enum test_status rx_status; >> +enum test_status cmp_rxtx_status; >> + >> +#define test_log(test_msg_lvl, format, ...) do { \ >> + if (test_msg_lvl >= msg_lvl) \ >> + fprintf(stderr, format, ##__VA_ARGS__); \ >> +} while (0) >> + >> +static void loopback_test_sleep_ms(int ms) >> +{ >> + usleep(1000 * ms); >> +} >> + > > Have you run this as part of "make kselftest" run. How does this > sleep work in that env.? Looks like kselftest runs this test application by directly executing the binary, but this test application requires a valid mhi device file node as a required parameter. So considering that requirement, is this test application qualifies to run using kselftest ? Without a valid device file node test would fail. Is there an option to run this test as standalone test which can only be run when a mhi device file node is present ? Having said that i tested this driver by directly executing it using the test binary which is compiled using make loopback_test under mhi dir. > Are there any cases where this test can't run and have to - those > cases need to be skips. Yes, as this test application can not run by itself it needs a valid mhi device file node to write and test reads the same device node to get the data back. So test can not be run without having a MHI device connected over a transport (in my testing MHI device is connected over PCIe). Could you please suggest an option to use this test application as a standalone test instead of being part of kselftest? > > thanks, > -- Shuah Thanks, Hemant
diff --git a/Documentation/mhi/uci.rst b/Documentation/mhi/uci.rst index ce8740e..0a04afe 100644 --- a/Documentation/mhi/uci.rst +++ b/Documentation/mhi/uci.rst @@ -79,6 +79,38 @@ MHI client driver performs read operation, same data gets looped back to MHI host using LOOPBACK channel 1. LOOPBACK channel is used to verify data path and data integrity between MHI Host and MHI device. +Loopback Test +~~~~~~~~~~~~~ + +Loopback test application is used to verify data integrity between MHI host and +MHI device over LOOPBACK channel. This also confirms that basic MHI data path +is working properly. Test performs write() to send tx buffer to MHI device file +node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops back +transmit data to MHI Host. Test application receives data in receive buffer as +part of read(). It verifies if tx buffer matches rx buffer. Test application +performs poll() before making write() and read() system calls. Test passes if +match is found. + +Test is present under tools/testing/selftests/drivers/mhi. It is compiled using +following command in same dir:- + +make loopback_test + +Test is run using following command arguments:- + +loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i +<number of iterations> + +Required argument: +-c : loopback chardev node + +Optional argument: +-b : transmit buffer size. If not present 1024 bytes size transmit buffer + is sent. +-i : Number of iterations to perform, If not present only one transmit buffer + is sent. +-l : Log level. If not present defaults to DBG_LVL_INFO. + Other Use Cases --------------- diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index d9c2835..084bc1e 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -10,6 +10,7 @@ TARGETS += core TARGETS += cpufreq TARGETS += cpu-hotplug TARGETS += drivers/dma-buf +TARGETS += drivers/mhi TARGETS += efivarfs TARGETS += exec TARGETS += filesystems diff --git a/tools/testing/selftests/drivers/.gitignore b/tools/testing/selftests/drivers/.gitignore index ca74f2e..e4806d5 100644 --- a/tools/testing/selftests/drivers/.gitignore +++ b/tools/testing/selftests/drivers/.gitignore @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only /dma-buf/udmabuf +/mhi/loopback_test diff --git a/tools/testing/selftests/drivers/mhi/Makefile b/tools/testing/selftests/drivers/mhi/Makefile new file mode 100644 index 0000000..c06c925 --- /dev/null +++ b/tools/testing/selftests/drivers/mhi/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS += -I../../../../../usr/include/ -g -Wall + +LDLIBS = -lpthread +TEST_GEN_PROGS := loopback_test + +include ../../lib.mk + diff --git a/tools/testing/selftests/drivers/mhi/config b/tools/testing/selftests/drivers/mhi/config new file mode 100644 index 0000000..471dc92 --- /dev/null +++ b/tools/testing/selftests/drivers/mhi/config @@ -0,0 +1,2 @@ +CONFIG_MHI_BUS=y +CONFIG_MHi_UCI=y diff --git a/tools/testing/selftests/drivers/mhi/loopback_test.c b/tools/testing/selftests/drivers/mhi/loopback_test.c new file mode 100644 index 0000000..99b7712 --- /dev/null +++ b/tools/testing/selftests/drivers/mhi/loopback_test.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loopback test application performs write() to send tx buffer to MHI device + * file node for LOOPBACK uplink channel. MHI LOOPBACK downlink channel loops + * back transmit data to MHI Host. Test application receives data in receive + * buffer as part of read(). It verifies if tx buffer matches rx buffer. Test + * application performs poll() before making write() and read() system + * calls. Test passes if match is found. + * + * Test is compiled using following command:- + * + * make loopback_test + * + * Test is run using following command arguments:- + * + * loopback_test -c <device_node> -b <transmit buffer size> -l <log level> -i + * <number of iterations> + * + * Required argument: + * -c : loopback chardev node + * + * Optional argument: + * -b : transmit buffer size. If not present 1024 bytes size transmit buffer + * is sent. + * -i : Number of iterations to perform, If not present only one transmit buffer + * is sent. + * -l : Log level. If not present defaults to DBG_LVL_INFO. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/poll.h> +#include <unistd.h> + +enum debug_level { + DBG_LVL_VERBOSE, + DBG_LVL_INFO, + DBG_LVL_ERROR, +}; + +enum test_status { + TEST_STATUS_SUCCESS, + TEST_STATUS_ERROR, + TEST_STATUS_NO_DEV, +}; + +struct lb_test_ctx { + char dev_node[256]; + unsigned char *tx_buff; + unsigned char *rx_buff; + unsigned int rx_pkt_count; + unsigned int tx_pkt_count; + int iterations; + bool iter_complete; + bool comp_complete; + bool test_complete; + bool all_complete; + unsigned long buff_size; + long byte_recvd; + long byte_sent; +}; + +bool force_exit; +char write_data = 'a'; +int completed_iterations; + +struct lb_test_ctx test_ctxt; +enum debug_level msg_lvl; +struct pollfd read_fd; +struct pollfd write_fd; +enum test_status mhi_test_return_value; +enum test_status tx_status; +enum test_status rx_status; +enum test_status cmp_rxtx_status; + +#define test_log(test_msg_lvl, format, ...) do { \ + if (test_msg_lvl >= msg_lvl) \ + fprintf(stderr, format, ##__VA_ARGS__); \ +} while (0) + +static void loopback_test_sleep_ms(int ms) +{ + usleep(1000 * ms); +} + +static enum test_status loopback_test_open(struct pollfd *write_fd, + struct pollfd *read_fd, char *dev_name) +{ + enum test_status ret_val = TEST_STATUS_SUCCESS; + + write_fd->fd = open(dev_name, O_SYNC | O_WRONLY); + if (write_fd->fd < 0) { + test_log(DBG_LVL_ERROR, "Error opening file for w: errno: %d\n", errno); + ret_val = TEST_STATUS_ERROR; + } else { + test_log(DBG_LVL_INFO, "Opened %s, return code: %i.\n", + dev_name, write_fd->fd); + write_fd->events = POLLOUT; + } + + read_fd->fd = open(dev_name, O_SYNC | O_RDONLY); + if (read_fd->fd < 0) { + test_log(DBG_LVL_ERROR, "Error opening file for r: errno: %d\n", errno); + ret_val = TEST_STATUS_ERROR; + } else { + test_log(DBG_LVL_INFO, "Opened %s, return code: %i.\n", + dev_name, read_fd->fd); + read_fd->events = POLLIN; + } + + return ret_val; +} + +static enum test_status loopback_test_close(struct pollfd *write_fd, + struct pollfd *read_fd) +{ + enum test_status ret_val = TEST_STATUS_SUCCESS; + + if (close(read_fd->fd) == 0) { + test_log(DBG_LVL_INFO, "Closed device handle\n"); + } else { + test_log(DBG_LVL_ERROR, "Unable to close handle to device\n"); + ret_val = TEST_STATUS_ERROR; + } + + if (close(write_fd->fd) == 0) { + test_log(DBG_LVL_INFO, "Closed device handle\n"); + } else { + test_log(DBG_LVL_ERROR, "Unable to close handle to device\n"); + ret_val = TEST_STATUS_ERROR; + } + + return ret_val; +} + +static enum test_status loopback_test_alloc_rx_tx_buff(void) +{ + test_ctxt.rx_buff = (unsigned char *)malloc(test_ctxt.buff_size); + if (!test_ctxt.rx_buff) { + test_log(DBG_LVL_ERROR, "Failed to allocate rx buff\n"); + return TEST_STATUS_ERROR; + } + + memset(test_ctxt.rx_buff, 0x00, sizeof(test_ctxt.buff_size)); + test_ctxt.tx_buff = (unsigned char *)malloc(test_ctxt.buff_size); + if (!test_ctxt.tx_buff) { + test_log(DBG_LVL_ERROR, "Failed to allocate tx buff\n"); + return TEST_STATUS_ERROR; + } + + return TEST_STATUS_SUCCESS; +} + +static enum test_status loopback_test_write(struct pollfd *write_fd) +{ + enum test_status ret_value = TEST_STATUS_SUCCESS; + signed long temp_bytes_sent = 0; + signed long total_bytes_sent = 0; + + test_log(DBG_LVL_VERBOSE, "About to write %lu\n", test_ctxt.buff_size); + + temp_bytes_sent = write(write_fd->fd, + test_ctxt.tx_buff + test_ctxt.byte_sent, test_ctxt.buff_size); + + test_log(DBG_LVL_VERBOSE, "Returned %li\n", temp_bytes_sent); + test_log(DBG_LVL_INFO, "Wrote packet with %li bytes of %02x\n", + temp_bytes_sent, *(test_ctxt.tx_buff + test_ctxt.byte_sent)); + + if (temp_bytes_sent < 0) { + /* write returned a negative value i.e. write failed */ + test_log(DBG_LVL_ERROR, "write() failed: err code %d\n", errno); + return TEST_STATUS_ERROR; + } + + if ((unsigned long)temp_bytes_sent > test_ctxt.buff_size) { + test_log(DBG_LVL_ERROR, "Wrote more bytes than intended\n"); + return TEST_STATUS_ERROR; + } + + if (temp_bytes_sent == 0) { + loopback_test_sleep_ms(10); + } else if (temp_bytes_sent > 0) { + test_ctxt.tx_pkt_count++; + test_ctxt.byte_sent += temp_bytes_sent; + total_bytes_sent += temp_bytes_sent; + test_log(DBG_LVL_INFO, "Total written packets %d\n", + test_ctxt.tx_pkt_count); + } + + return ret_value; +} + +static void loopback_test_reset_ctxt(void) +{ + test_ctxt.byte_sent = 0; + __sync_synchronize(); + test_ctxt.byte_recvd = 0; + test_ctxt.rx_pkt_count = 0; + test_ctxt.tx_pkt_count = 0; + test_ctxt.comp_complete = false; + memset((void *)test_ctxt.rx_buff, 0x00, test_ctxt.buff_size); +} + +static enum test_status loopback_test_time_out(int *time_count) +{ + enum test_status ret_val = TEST_STATUS_SUCCESS; + + loopback_test_sleep_ms(3); + if ((++*time_count) > 100000) { + test_log(DBG_LVL_ERROR, "Error: all bytes not received for this iteration\n"); + ret_val = TEST_STATUS_ERROR; + while (1) + loopback_test_sleep_ms(1000); + } + + return ret_val; +} + +static enum test_status loopback_test_poll(struct pollfd *fd, + bool poll_timeout) +{ + enum test_status ret_val = TEST_STATUS_SUCCESS; + int poll_return = 0; + int i = 0; + int timeout = 1000; /* poll-wait 1 second */ + int file_desc_type = -1; + + if (fd->events == POLLIN) { + file_desc_type = 1; /* read */ + test_log(DBG_LVL_VERBOSE, + "Poll: opened read handle to device\n"); + } else if (fd->events == POLLOUT) { + file_desc_type = 0; /* write */ + test_log(DBG_LVL_VERBOSE, + "Poll: opened write handle to device\n"); + } + + if (poll_timeout == false) + timeout = -1; /* poll-wait forever */ + + while (ret_val == TEST_STATUS_SUCCESS) { + if (force_exit == true) { + ret_val = TEST_STATUS_ERROR; + } else { + test_log(DBG_LVL_VERBOSE, "%s Polling %i\n", + file_desc_type ? "Read" : "Write", i); + + poll_return = poll(fd, 1, timeout); + if (poll_return == -1) { + test_log(DBG_LVL_ERROR, "%s Poll error - %d\n", + file_desc_type ? "Read" : "Write", + errno); + return TEST_STATUS_ERROR; + } else if (((file_desc_type == 1) && + (fd->revents & POLLIN)) || + ((file_desc_type == 0) && + (fd->revents & POLLOUT))) { + test_log(DBG_LVL_VERBOSE, + "%s Poll Returned\n", + file_desc_type ? "Read" : "Write"); + return TEST_STATUS_SUCCESS; + } else if (poll_timeout == true) { + i++; + poll_return = 0; + if (i >= 250) { + test_log(DBG_LVL_ERROR, + "Poll: %s Timed out: 10s\n", + file_desc_type ? "Read" : "Write"); + return TEST_STATUS_ERROR; + } + } + + loopback_test_sleep_ms(10); + } + } + + return ret_val; +} + +static void *loopback_test_rx(void *data) +{ + bool poll_timeout = true; + unsigned long read_size = test_ctxt.buff_size; + long temp_bytes_rec; + enum test_status status; + + rx_status = TEST_STATUS_SUCCESS; + + while (force_exit == false) { + if (test_ctxt.test_complete) { + test_log(DBG_LVL_INFO, "Rx: exiting thread\n"); + pthread_exit(&rx_status); + return NULL; + } + + while ((test_ctxt.byte_sent > test_ctxt.byte_recvd) && + (force_exit == false)) { + test_log(DBG_LVL_VERBOSE, + "Rx: Bytes Sent: %li, Received %li\n", + test_ctxt.byte_sent, test_ctxt.byte_recvd); + + temp_bytes_rec = 0; + + if (test_ctxt.comp_complete == false) { + status = loopback_test_poll(&read_fd, + poll_timeout); + if (status == TEST_STATUS_ERROR) { + if (test_ctxt.test_complete == true) + rx_status = TEST_STATUS_SUCCESS; + else + rx_status = TEST_STATUS_ERROR; + + test_log(DBG_LVL_INFO, + "Rx: exiting thread\n"); + pthread_exit(&rx_status); + return NULL; + } + + test_log(DBG_LVL_VERBOSE, "Rx: before read\n"); + temp_bytes_rec = read(read_fd.fd, + test_ctxt.rx_buff + test_ctxt.byte_recvd, + read_size); + if (temp_bytes_rec < 0) { + test_log(DBG_LVL_ERROR, + "Rx: read() failed - err code %d\n", + errno); + rx_status = TEST_STATUS_ERROR; + pthread_exit(&rx_status); + return NULL; + } + + if (temp_bytes_rec == 0) { + loopback_test_sleep_ms(10); + rx_status = TEST_STATUS_ERROR; + pthread_exit(&rx_status); + return NULL; + } + + test_ctxt.rx_pkt_count++; + test_log(DBG_LVL_INFO, + "Rx: Read packet with %li bytes of %02x\n", + temp_bytes_rec, + *(test_ctxt.rx_buff + test_ctxt.byte_recvd)); + test_ctxt.byte_recvd += temp_bytes_rec; + test_log(DBG_LVL_INFO, + "Rx: Total byte_recvd: %li\n", + test_ctxt.byte_recvd); + test_log(DBG_LVL_INFO, + "Rx: Total read packets %d\n", + test_ctxt.rx_pkt_count); + test_log(DBG_LVL_INFO, + "Rx: %lu byte buff_size\n", + test_ctxt.buff_size); + + read_fd.revents = 0; + } + } + } + rx_status = TEST_STATUS_ERROR; + pthread_exit(&rx_status); + return NULL; +} + +/* This single compare thread, will compare each byte of tx and rx Buff */ +static void *loopback_test_compare_rxtx(void *data) +{ + int k; + bool completion = true; + long curr_ptr = 0; + + test_log(DBG_LVL_INFO, "CompareRxTxThread: Inside thread\n"); + cmp_rxtx_status = TEST_STATUS_SUCCESS; + + for (;;) { + /* if the test is completed exit thread */ + if (test_ctxt.all_complete == true) { + curr_ptr = 0; + test_log(DBG_LVL_INFO, + "CompareRxTx: exiting thread\n"); + pthread_exit(&cmp_rxtx_status); + return NULL; + } + + /* Simply compares each byte in each Tx and Rx buffer */ + while (curr_ptr < test_ctxt.byte_recvd) { + if (*(test_ctxt.rx_buff + curr_ptr) != + *(test_ctxt.tx_buff + curr_ptr)) { + test_log(DBG_LVL_ERROR, + "Mismatch Byte num %li\n", curr_ptr); + test_log(DBG_LVL_ERROR, + "Bytes: Sent 0x%x, Received 0x%x\n", + *(test_ctxt.tx_buff + curr_ptr), + *(test_ctxt.rx_buff + curr_ptr)); + test_log(DBG_LVL_VERBOSE, "Rx buffer dump:\n"); + for (k = 0; k < 1024; k++) + test_log(DBG_LVL_VERBOSE, "%d:0x%x ", k, + *(test_ctxt.rx_buff + k)); + + test_log(DBG_LVL_VERBOSE, "\n"); + + test_log(DBG_LVL_INFO, + "CompareRxTx: finished iterations %i\n", + completed_iterations); + test_log(DBG_LVL_INFO, + "CompareRxTx: Total pkts sent %u\n", + test_ctxt.tx_pkt_count); + + curr_ptr = 0; + force_exit = true; + cmp_rxtx_status = TEST_STATUS_ERROR; + pthread_exit(&cmp_rxtx_status); + return NULL; + } + + test_log(DBG_LVL_VERBOSE, + "CompareRxTx: Bytes Sent 0x%x, Received 0x%x\n", + *(test_ctxt.tx_buff + curr_ptr), + *(test_ctxt.rx_buff + curr_ptr)); + curr_ptr++; + } + + /* + * completion will start as true and change depending if the + * iterations has been completed for all client threads + */ + if (curr_ptr >= test_ctxt.byte_sent) + completion = (completion && test_ctxt.iter_complete); + else + completion = false; + + /* + * Once all client threads have finished their iteration, reset + * everything and allow the client thread to start their next + * iteration + */ + if (completion) { + test_ctxt.byte_sent = 0; + __sync_synchronize(); + test_ctxt.byte_recvd = 0; + test_ctxt.iter_complete = false; + test_ctxt.comp_complete = true; + curr_ptr = 0; + test_log(DBG_LVL_INFO, "CompareRxTx: TX and RX match\n"); + } else { + completion = true; + } + } +} + +static void loopback_test_display_err_data(void) +{ + test_log(DBG_LVL_ERROR, "Wrote %lu bytes out of a %lu buff size\n", + test_ctxt.byte_sent, test_ctxt.buff_size); + test_log(DBG_LVL_ERROR, "Remaining bytes to write %lu\n", + (test_ctxt.buff_size - test_ctxt.byte_sent)); +} + +static enum test_status loopback_test_setup_write_packet(void) +{ + bool poll_timeout = true; + + if (loopback_test_poll(&write_fd, poll_timeout) == TEST_STATUS_ERROR) + return TEST_STATUS_ERROR; + + test_log(DBG_LVL_VERBOSE, "revents: %d\n", write_fd.revents); + + if (write_fd.revents & POLLOUT) { + memset(test_ctxt.tx_buff + test_ctxt.byte_sent, write_data, + test_ctxt.buff_size); + test_log(DBG_LVL_VERBOSE, "Write %02x of size %lu\n", write_data, + test_ctxt.buff_size); + + if (loopback_test_write(&write_fd) != TEST_STATUS_SUCCESS) { + loopback_test_sleep_ms(1000); + return TEST_STATUS_ERROR; + } + + test_log(DBG_LVL_INFO, "Total byte_sent %li\n", + test_ctxt.byte_sent); + write_fd.revents = 0; + } + + return TEST_STATUS_SUCCESS; +} + +static enum test_status loopback_test_wait_to_get_bytes_back(void) +{ + int time_count = 0; + + test_log(DBG_LVL_INFO, "Sent %li bytes, received %li bytes\n", + test_ctxt.byte_sent, test_ctxt.byte_recvd); + + /* read thread will fill bytesRec and wait till it equals byte_sent */ + while (test_ctxt.byte_recvd != test_ctxt.byte_sent) { + if (loopback_test_time_out(&time_count) == TEST_STATUS_ERROR) + return TEST_STATUS_ERROR; + } + + return TEST_STATUS_SUCCESS; +} + +static void *loopback_test_tx(void *data) +{ + int i; + int time_count = 0; + int failed_iterations = 0; + enum test_status status = TEST_STATUS_SUCCESS; + + tx_status = TEST_STATUS_SUCCESS; + + for (i = 0; i < test_ctxt.iterations; i++) { + loopback_test_reset_ctxt(); + + test_ctxt.iter_complete = false; + + /* While the bytes sent is less than the buffsize */ + while (((unsigned int)test_ctxt.byte_sent < test_ctxt.buff_size) + && (status == TEST_STATUS_SUCCESS)) { + if (force_exit == true) + goto exit; + + if (status != TEST_STATUS_SUCCESS) { + loopback_test_display_err_data(); + tx_status = TEST_STATUS_ERROR; + goto exit; + } + + status = loopback_test_setup_write_packet(); + if (status == TEST_STATUS_ERROR) { + test_log(DBG_LVL_INFO, + "Failed after %d iterations\n", i); + failed_iterations++; + goto exit; + } + } + + if (loopback_test_wait_to_get_bytes_back() == + TEST_STATUS_ERROR) { + tx_status = TEST_STATUS_ERROR; + goto exit; + } + + __sync_synchronize(); + + test_ctxt.iter_complete = true; + + while (!test_ctxt.comp_complete) { + if (loopback_test_time_out(&time_count) == + TEST_STATUS_ERROR) { + tx_status = TEST_STATUS_ERROR; + goto exit; + } + } + time_count = 0; + } + + if (failed_iterations > 0) + test_log(DBG_LVL_INFO, "Amount of Failed Iterations: %d\n", + failed_iterations); + +exit: + loopback_test_reset_ctxt(); + + /* all of the iterations for this client thread have been completed */ + test_ctxt.test_complete = true; + + /* close the opened file descriptors */ + loopback_test_close(&write_fd, &read_fd); + + test_log(DBG_LVL_INFO, "mhi_test_loopback: exiting thread\n"); + + pthread_exit(&tx_status); + + return NULL; +} + +/* + * This function will be called when the user wishes to perform a loopback test. + * In this function, a compare thread will be created to compare the tx buffer + * and the rx Buffer. Device file node is opened for write and read. Tx thread + * is created to send data and Rx thread to receive data. + */ +enum test_status loopback_test_thread_create(void) +{ + int i = 0; + enum test_status status = TEST_STATUS_SUCCESS; + enum test_status *write_thread_retval; + enum test_status *read_thread_retval; + enum test_status *compare_thread_retval; + + pthread_t compare_rx_tx_thread; + pthread_t loopback_thread; + pthread_t receive_thread; + + /* set all tests (including open/close) to not completed */ + test_ctxt.all_complete = false; + + /* set current test to not completed */ + test_ctxt.test_complete = false; + + if (loopback_test_alloc_rx_tx_buff() == TEST_STATUS_ERROR) { + status = TEST_STATUS_ERROR; + goto exit; + } + + /* Create thread for comparing the rx and tx buffers */ + if (pthread_create(&compare_rx_tx_thread, NULL, + loopback_test_compare_rxtx, NULL) != 0) { + test_log(DBG_LVL_ERROR, "Error creating compare thread\n"); + status = TEST_STATUS_ERROR; + goto exit; + } + + test_log(DBG_LVL_INFO, "dev_node: %s\n", test_ctxt.dev_node); + + /* closing the device node will happen in the loopback thread */ + status = loopback_test_open(&write_fd, &read_fd, test_ctxt.dev_node); + if (status == TEST_STATUS_ERROR) { + test_log(DBG_LVL_ERROR, "Failed to open device node\n"); + goto exit; + } + + /* Spawn the loopback tx thread */ + if (pthread_create(&loopback_thread, NULL, loopback_test_tx, NULL) + != 0) { + test_log(DBG_LVL_ERROR, "Error creating tx thread"); + status = TEST_STATUS_ERROR; + goto exit; + } + + /* Create thread for receiving packets */ + if (pthread_create(&receive_thread, NULL, loopback_test_rx, NULL) + != 0) { + test_log(DBG_LVL_ERROR, "Error creating rx thread\n"); + status = TEST_STATUS_ERROR; + goto exit; + } + + test_log(DBG_LVL_INFO, "Waiting for Tx and Rx threads to complete\n"); + + /* wait till all client and receive threads have finished */ + pthread_join(loopback_thread, (void **)(&write_thread_retval)); + pthread_join(receive_thread, (void **)(&read_thread_retval)); + + test_log(DBG_LVL_VERBOSE, "TX and Rx threads completed\n"); + + test_log(DBG_LVL_INFO, "Write thread status: %s\n", + *write_thread_retval ? "Fail" : "Pass"); + test_log(DBG_LVL_INFO, "Read thread status: %s\n", + *read_thread_retval ? "Fail" : "Pass"); + + if ((*write_thread_retval == TEST_STATUS_ERROR) || + (*read_thread_retval == TEST_STATUS_ERROR)) { + test_log(DBG_LVL_ERROR, "Returned error, exiting\n"); + status = TEST_STATUS_ERROR; + goto exit; + } + + test_log(DBG_LVL_INFO, "Thread %i of tests complete\n", i); + test_ctxt.test_complete = false; + + test_log(DBG_LVL_INFO, "All loopback threads completed\n"); + + test_ctxt.all_complete = true; + + test_log(DBG_LVL_INFO, "Waiting for comp thread to exit\n"); + + pthread_join(compare_rx_tx_thread, (void **)(&compare_thread_retval)); + if (*compare_thread_retval == TEST_STATUS_ERROR) { + test_log(DBG_LVL_ERROR, "Compare thread returned error\n"); + status = TEST_STATUS_ERROR; + } + +exit: + /* All threads have finished using the Tx and Rx Buffers, free them */ + free(test_ctxt.tx_buff); + test_ctxt.tx_buff = NULL; + free(test_ctxt.rx_buff); + test_ctxt.rx_buff = NULL; + + return status; +} + +static void *loopback_test_run(void *data) +{ + enum test_status status = TEST_STATUS_SUCCESS; + + force_exit = false; + srand(time(NULL)); + + status = loopback_test_thread_create(); + + pthread_exit(&status); + + return NULL; +} + +static void loopback_test_set_defaults(void) +{ + msg_lvl = DBG_LVL_INFO; + + memset(&test_ctxt, 0, sizeof(test_ctxt)); + test_ctxt.iterations = 1; + test_ctxt.buff_size = 1024; +} + +static void usage(void) +{ + test_log(DBG_LVL_INFO, "\nUsage:\n"); + test_log(DBG_LVL_INFO, "-c: Device node\n"); + test_log(DBG_LVL_INFO, "-b: Buffer size (default: 1024 bytes)\n"); + test_log(DBG_LVL_INFO, "-i: Number of iterations\n"); + test_log(DBG_LVL_INFO, "-l: log level, default - 1\n"); + test_log(DBG_LVL_INFO, "log level : 0 -verbose, 1 -info , 2 -err\n"); +} + +static int loopback_test_parse(int argc, char *argv[]) +{ + int i = 0; + int command = 0; + int indexptr = 0; + char *optstr = "-:c:b:i:l:"; + int *return_value = NULL; + int status = -EINVAL; + pthread_t run_test; + + optind = 1; + + static const struct option optlongstr[] = { + { "device node", required_argument, NULL, 'c'}, + {0, 0, 0, 0} + }; + + test_log(DBG_LVL_INFO, "\nStarting MHI loopback tests\n"); + + test_log(DBG_LVL_VERBOSE, "argc: %d\n", argc); + for (i = 0; i < argc; i++) + test_log(DBG_LVL_VERBOSE, "argv[%i] = %s; ", i, argv[i]); + + while ((command = getopt_long(argc, (char **)argv, optstr, optlongstr, + &indexptr)) && (command != -1)) { + switch (command) { + case 'c': + sprintf(test_ctxt.dev_node, "%s", optarg); + test_log(DBG_LVL_INFO, "%s\n", test_ctxt.dev_node); + break; + case 'b': + test_ctxt.buff_size = atoi(optarg); + test_log(DBG_LVL_INFO, "%lu\n", test_ctxt.buff_size); + break; + case 'i': + test_ctxt.iterations = atoi(optarg); + test_log(DBG_LVL_INFO, "%u\n", test_ctxt.iterations); + break; + case 'l': + msg_lvl = atoi(optarg); + test_log(DBG_LVL_INFO, "log level: %d\n", atoi(optarg)); + break; + default: + test_log(DBG_LVL_ERROR, "Invalid Option: %d\n", + command); + usage(); + return TEST_STATUS_ERROR; + } + } + + if (pthread_create(&run_test, NULL, loopback_test_run, NULL) != 0) { + test_log(DBG_LVL_ERROR, "Error creating run_mhi_test\n"); + } else { + pthread_join(run_test, (void **)(&return_value)); + + if (*return_value != TEST_STATUS_SUCCESS) { + test_log(DBG_LVL_ERROR, "Test Failed\n"); + } else { + test_log(DBG_LVL_INFO, "Test Passed\n"); + status = 0; + } + } + + return status; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + + loopback_test_set_defaults(); + test_log(DBG_LVL_INFO, "MHI Loopback test App\n"); + + if (argc > 1) + ret = loopback_test_parse(argc, argv); + else + usage(); + + return ret; +}
Loopback test opens the MHI device file node and writes a data buffer to it. MHI UCI kernel space driver copies the data and sends it to MHI uplink (Tx) LOOPBACK channel. MHI device loops back the same data to MHI downlink (Rx) LOOPBACK channel. This data is read by test application and compared against the data sent. Test passes if data buffer matches between Tx and Rx. Test application performs open(), poll(), write(), read() and close() file operations. Signed-off-by: Hemant Kumar <hemantk@codeaurora.org> --- Documentation/mhi/uci.rst | 32 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drivers/.gitignore | 1 + tools/testing/selftests/drivers/mhi/Makefile | 8 + tools/testing/selftests/drivers/mhi/config | 2 + .../testing/selftests/drivers/mhi/loopback_test.c | 802 +++++++++++++++++++++ 6 files changed, 846 insertions(+) create mode 100644 tools/testing/selftests/drivers/mhi/Makefile create mode 100644 tools/testing/selftests/drivers/mhi/config create mode 100644 tools/testing/selftests/drivers/mhi/loopback_test.c