/* drivers/tty/smux_test.c * * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include "smux_private.h" #define DEBUG_BUFMAX 4096 #define RED_ZONE_SIZE 16 #define RED_ZONE_PRE_CH 0xAB #define RED_ZONE_POS_CH 0xBA #define SMUX_REMOTE_INACTIVITY_TIME_MS 50 #define SMUX_REMOTE_DELAY_TIME_MS 250 /** * Unit test assertion for logging test cases. * * @a lval * @b rval * @cmp comparison operator * * Assertion fails if (@a cmp @b) is not true which then * logs the function and line number where the error occurred * along with the values of @a and @b. * * Assumes that the following local variables exist: * @buf - buffer to write failure message to * @i - number of bytes written to buffer * @max - maximum size of the buffer * @failed - set to true if test fails */ #define UT_ASSERT_INT(a, cmp, b) \ { \ int a_tmp = (a); \ int b_tmp = (b); \ if (!((a_tmp)cmp(b_tmp))) { \ i += scnprintf(buf + i, max - i, \ "%s:%d Fail: " #a "(%d) " #cmp " " #b "(%d)\n", \ __func__, __LINE__, \ a_tmp, b_tmp); \ failed = 1; \ break; \ } \ } #define UT_ASSERT_PTR(a, cmp, b) \ { \ void *a_tmp = (a); \ void *b_tmp = (b); \ if (!((a_tmp)cmp(b_tmp))) { \ i += scnprintf(buf + i, max - i, \ "%s:%d Fail: " #a "(%p) " #cmp " " #b "(%p)\n", \ __func__, __LINE__, \ a_tmp, b_tmp); \ failed = 1; \ break; \ } \ } #define UT_ASSERT_UINT(a, cmp, b) \ { \ unsigned a_tmp = (a); \ unsigned b_tmp = (b); \ if (!((a_tmp)cmp(b_tmp))) { \ i += scnprintf(buf + i, max - i, \ "%s:%d Fail: " #a "(%u) " #cmp " " #b "(%u)\n", \ __func__, __LINE__, \ a_tmp, b_tmp); \ failed = 1; \ break; \ } \ } /** * In-range unit test assertion for test cases. * * @a lval * @minv Minimum value * @maxv Maximum value * * Assertion fails if @a is not on the exclusive range minv, maxv * ((@a < @minv) or (@a > @maxv)). In the failure case, the macro * logs the function and line number where the error occurred along * with the values of @a and @minv, @maxv. * * Assumes that the following local variables exist: * @buf - buffer to write failure message to * @i - number of bytes written to buffer * @max - maximum size of the buffer * @failed - set to true if test fails */ #define UT_ASSERT_INT_IN_RANGE(a, minv, maxv) \ { \ int a_tmp = (a); \ int minv_tmp = (minv); \ int maxv_tmp = (maxv); \ if (((a_tmp) < (minv_tmp)) || ((a_tmp) > (maxv_tmp))) { \ i += scnprintf(buf + i, max - i, \ "%s:%d Fail: " #a "(%d) < " #minv "(%d) or " \ #a "(%d) > " #maxv "(%d)\n", \ __func__, __LINE__, \ a_tmp, minv_tmp, a_tmp, maxv_tmp); \ failed = 1; \ break; \ } \ } static unsigned char test_array[] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233}; /* when 1, forces failure of get_rx_buffer_mock function */ static int get_rx_buffer_mock_fail; /* Used for mapping local to remote TIOCM signals */ struct tiocm_test_vector { uint32_t input; uint32_t set_old; uint32_t set_new; uint32_t clr_old; }; /** * Allocates a new buffer for SMUX for every call. */ static int get_rx_buffer(void *priv, void **pkt_priv, void **buffer, int size) { void *rx_buf; rx_buf = kmalloc(size, GFP_KERNEL); *pkt_priv = (void *)0x1234; *buffer = rx_buf; return 0; } /* Test vector for packet tests. */ struct test_vector { const char *data; const unsigned len; }; /* Mock object metadata for SMUX_READ_DONE event */ struct mock_read_event { struct list_head list; struct smux_meta_read meta; }; /* Mock object metadata for SMUX_WRITE_DONE event */ struct mock_write_event { struct list_head list; struct smux_meta_write meta; }; /* Mock object metadata for get_rx_buffer failure event */ struct mock_get_rx_buff_event { struct list_head list; int size; unsigned long jiffies; }; /* Mock object for all SMUX callback events */ struct smux_mock_callback { int cb_count; struct completion cb_completion; spinlock_t lock; /* status changes */ int event_connected; int event_disconnected; int event_disconnected_ssr; int event_low_wm; int event_high_wm; int event_rx_retry_high_wm; int event_rx_retry_low_wm; /* TIOCM changes */ int event_tiocm; struct smux_meta_tiocm tiocm_meta; /* read event data */ int event_read_done; int event_read_failed; struct list_head read_events; /* read retry data */ int get_rx_buff_retry_count; struct list_head get_rx_buff_retry_events; /* write event data */ int event_write_done; int event_write_failed; struct list_head write_events; }; static int get_rx_buffer_mock(void *priv, void **pkt_priv, void **buffer, int size); /** * Initialize mock callback data. Only call once. * * @cb Mock callback data */ static void mock_cb_data_init(struct smux_mock_callback *cb) { init_completion(&cb->cb_completion); spin_lock_init(&cb->lock); INIT_LIST_HEAD(&cb->read_events); INIT_LIST_HEAD(&cb->get_rx_buff_retry_events); INIT_LIST_HEAD(&cb->write_events); } /** * Reset mock callback data to default values. * * @cb Mock callback data * * All packets are freed and counters reset to zero. */ static void mock_cb_data_reset(struct smux_mock_callback *cb) { cb->cb_count = 0; INIT_COMPLETION(cb->cb_completion); cb->event_connected = 0; cb->event_disconnected = 0; cb->event_disconnected_ssr = 0; cb->event_low_wm = 0; cb->event_high_wm = 0; cb->event_rx_retry_high_wm = 0; cb->event_rx_retry_low_wm = 0; cb->event_tiocm = 0; cb->tiocm_meta.tiocm_old = 0; cb->tiocm_meta.tiocm_new = 0; cb->event_read_done = 0; cb->event_read_failed = 0; while (!list_empty(&cb->read_events)) { struct mock_read_event *meta; meta = list_first_entry(&cb->read_events, struct mock_read_event, list); kfree(meta->meta.buffer); list_del(&meta->list); kfree(meta); } cb->get_rx_buff_retry_count = 0; while (!list_empty(&cb->get_rx_buff_retry_events)) { struct mock_get_rx_buff_event *meta; meta = list_first_entry(&cb->get_rx_buff_retry_events, struct mock_get_rx_buff_event, list); list_del(&meta->list); kfree(meta); } cb->event_write_done = 0; cb->event_write_failed = 0; while (!list_empty(&cb->write_events)) { struct mock_write_event *meta; meta = list_first_entry(&cb->write_events, struct mock_write_event, list); list_del(&meta->list); kfree(meta); } } /** * Dump the values of the mock callback data for debug purposes. * * @cb Mock callback data * @buf Print buffer * @max Maximum number of characters to print * * @returns Number of characters added to buffer */ static int mock_cb_data_print(const struct smux_mock_callback *cb, char *buf, int max) { int i = 0; i += scnprintf(buf + i, max - i, "\tcb_count=%d\n" "\tcb_completion.done=%d\n" "\tevent_connected=%d\n" "\tevent_disconnected=%d\n" "\tevent_disconnected_ssr=%d\n" "\tevent_low_wm=%d\n" "\tevent_high_wm=%d\n" "\tevent_rx_retry_high_wm=%d\n" "\tevent_rx_retry_low_wm=%d\n" "\tevent_tiocm=%d\n" "\tevent_read_done=%d\n" "\tevent_read_failed=%d\n" "\tread_events empty=%d\n" "\tget_rx_retry=%d\n" "\tget_rx_retry_events empty=%d\n" "\tevent_write_done=%d\n" "\tevent_write_failed=%d\n" "\twrite_events empty=%d\n", cb->cb_count, cb->cb_completion.done, cb->event_connected, cb->event_disconnected, cb->event_disconnected_ssr, cb->event_low_wm, cb->event_high_wm, cb->event_rx_retry_high_wm, cb->event_rx_retry_low_wm, cb->event_tiocm, cb->event_read_done, cb->event_read_failed, list_empty(&cb->read_events), cb->get_rx_buff_retry_count, list_empty(&cb->get_rx_buff_retry_events), cb->event_write_done, cb->event_write_failed, list_empty(&cb->write_events) ); return i; } /** * Mock object event callback. Used to logs events for analysis in the unit * tests. */ static void smux_mock_cb(void *priv, int event, const void *metadata) { struct smux_mock_callback *cb_data_ptr; struct mock_write_event *write_event_meta; struct mock_read_event *read_event_meta; unsigned long flags; cb_data_ptr = (struct smux_mock_callback *)priv; if (cb_data_ptr == NULL) { pr_err("%s: invalid private data\n", __func__); return; } switch (event) { case SMUX_CONNECTED: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_connected; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_DISCONNECTED: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_disconnected; cb_data_ptr->event_disconnected_ssr = ((struct smux_meta_disconnected *)metadata)->is_ssr; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_READ_DONE: read_event_meta = kmalloc(sizeof(struct mock_read_event), GFP_KERNEL); spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_read_done; if (read_event_meta) { read_event_meta->meta = *(struct smux_meta_read *)metadata; list_add_tail(&read_event_meta->list, &cb_data_ptr->read_events); } spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_READ_FAIL: read_event_meta = kmalloc(sizeof(struct mock_read_event), GFP_KERNEL); spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_read_failed; if (read_event_meta) { if (metadata) read_event_meta->meta = *(struct smux_meta_read *)metadata; else memset(&read_event_meta->meta, 0x0, sizeof(struct smux_meta_read)); list_add_tail(&read_event_meta->list, &cb_data_ptr->read_events); } spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_WRITE_DONE: write_event_meta = kmalloc(sizeof(struct mock_write_event), GFP_KERNEL); spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_write_done; if (write_event_meta) { write_event_meta->meta = *(struct smux_meta_write *)metadata; list_add_tail(&write_event_meta->list, &cb_data_ptr->write_events); } spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_WRITE_FAIL: write_event_meta = kmalloc(sizeof(struct mock_write_event), GFP_KERNEL); spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_write_failed; if (write_event_meta) { write_event_meta->meta = *(struct smux_meta_write *)metadata; list_add_tail(&write_event_meta->list, &cb_data_ptr->write_events); } spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_LOW_WM_HIT: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_low_wm; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_HIGH_WM_HIT: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_high_wm; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_RX_RETRY_HIGH_WM_HIT: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_rx_retry_high_wm; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_RX_RETRY_LOW_WM_HIT: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_rx_retry_low_wm; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; case SMUX_TIOCM_UPDATE: spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->event_tiocm; cb_data_ptr->tiocm_meta = *(struct smux_meta_tiocm *)metadata; spin_unlock_irqrestore(&cb_data_ptr->lock, flags); break; default: pr_err("%s: unknown event %d\n", __func__, event); }; spin_lock_irqsave(&cb_data_ptr->lock, flags); ++cb_data_ptr->cb_count; complete(&cb_data_ptr->cb_completion); spin_unlock_irqrestore(&cb_data_ptr->lock, flags); } /** * Test Read/write usage. * * @buf Output buffer for failure/status messages * @max Size of @buf * @vectors Test vector data (must end with NULL item) * @name Name of the test case for failure messages * * Perform a sanity test consisting of opening a port, writing test packet(s), * reading the response(s), and closing the port. * * The port should already be configured to use either local or remote * loopback. */ static int smux_ut_basic_core(char *buf, int max, const struct test_vector *vectors, const char *name) { int i = 0; int failed = 0; static struct smux_mock_callback cb_data; static int cb_initialized; int ret; if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); while (!failed) { struct mock_write_event *write_event; struct mock_read_event *read_event; /* open port */ ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* write, read, and verify the test vector data */ for (; vectors->data != NULL; ++vectors) { const char *test_data = vectors->data; const unsigned test_len = vectors->len; unsigned long long start_t; unsigned long long end_t; unsigned long long val; unsigned long rem; i += scnprintf(buf + i, max - i, "Writing vector %p len %d: ", test_data, test_len); /* write data */ start_t = sched_clock(); msm_smux_write(SMUX_TEST_LCID, (void *)0xCAFEFACE, test_data, test_len); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); /* wait for write and echo'd read to complete */ INIT_COMPLETION(cb_data.cb_completion); if (cb_data.cb_count < 2) UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); end_t = sched_clock(); UT_ASSERT_INT(cb_data.cb_count, >=, 1); UT_ASSERT_INT(cb_data.event_write_done, ==, 1); UT_ASSERT_INT(list_empty(&cb_data.write_events), ==, 0); write_event = list_first_entry(&cb_data.write_events, struct mock_write_event, list); UT_ASSERT_PTR(write_event->meta.pkt_priv, ==, (void *)0xCAFEFACE); UT_ASSERT_PTR(write_event->meta.buffer, ==, (void *)test_data); UT_ASSERT_INT(write_event->meta.len, ==, test_len); /* verify read event */ UT_ASSERT_INT(cb_data.event_read_done, ==, 1); UT_ASSERT_INT(list_empty(&cb_data.read_events), ==, 0); read_event = list_first_entry(&cb_data.read_events, struct mock_read_event, list); UT_ASSERT_PTR(read_event->meta.pkt_priv, ==, (void *)0x1234); UT_ASSERT_PTR(read_event->meta.buffer, !=, NULL); if (read_event->meta.len != test_len || memcmp(read_event->meta.buffer, test_data, test_len)) { /* data mismatch */ char linebuff[80]; hex_dump_to_buffer(test_data, test_len, 16, 1, linebuff, sizeof(linebuff), 1); i += scnprintf(buf + i, max - i, "Failed\nExpected:\n%s\n\n", linebuff); hex_dump_to_buffer(read_event->meta.buffer, read_event->meta.len, 16, 1, linebuff, sizeof(linebuff), 1); i += scnprintf(buf + i, max - i, "Failed\nActual:\n%s\n", linebuff); failed = 1; break; } /* calculate throughput stats */ val = end_t - start_t; rem = do_div(val, 1000); i += scnprintf(buf + i, max - i, "OK - %u us", (unsigned int)val); val = 1000000000LL * 2 * test_len; rem = do_div(val, end_t - start_t); i += scnprintf(buf + i, max - i, " (%u kB/sec)\n", (unsigned int)val); mock_cb_data_reset(&cb_data); } /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", name); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } mock_cb_data_reset(&cb_data); return i; } /** * Verify Basic Local Loopback Support * * Perform a sanity test consisting of opening a port in local loopback * mode and writing a packet and reading the echo'd packet back. */ static int smux_ut_basic(char *buf, int max) { const struct test_vector test_data[] = { {"hello\0world\n", sizeof("hello\0world\n")}, {0, 0}, }; int i = 0; int failed = 0; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); while (!failed) { /* enable loopback mode */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); break; } if (failed) { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); } return i; } /** * Verify Basic Remote Loopback Support * * Perform a sanity test consisting of opening a port in remote loopback * mode and writing a packet and reading the echo'd packet back. */ static int smux_ut_remote_basic(char *buf, int max) { const struct test_vector test_data[] = { {"hello\0world\n", sizeof("hello\0world\n")}, {0, 0}, }; int i = 0; int failed = 0; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); while (!failed) { /* enable remote mode */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); break; } if (failed) { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); } return i; } /** * Verify Basic Subsystem Restart Support * * Run a basic loopback test followed by a subsystem restart and then another * loopback test. */ static int smux_ut_ssr_remote_basic(char *buf, int max) { const struct test_vector test_data[] = { {"hello\0world\n", sizeof("hello\0world\n")}, {0, 0}, }; int i = 0; int failed = 0; int retry_count = 0; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); while (!failed) { /* enable remote mode */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); subsystem_restart("external_modem"); do { msleep(500); ++retry_count; UT_ASSERT_INT(retry_count, <, 20); } while (!smux_remote_is_active() && !failed); i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); break; } if (failed) { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); } return i; } /** * Verify Subsystem Restart Support During Port Open */ static int smux_ut_ssr_remote_open(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int ret; int retry_count; int i = 0; int failed = 0; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); while (!failed) { ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); /* open port */ ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* restart modem */ subsystem_restart("external_modem"); /* verify SSR events */ UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 5*HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 1); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); /* wait for remote side to finish booting */ retry_count = 0; do { msleep(500); ++retry_count; UT_ASSERT_INT(retry_count, <, 20); } while (!smux_remote_is_active() && !failed); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } mock_cb_data_reset(&cb_data); return i; } /** * Verify get_rx_buffer callback retry doesn't livelock SSR * until all RX Bufffer Retries have timed out. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_ssr_remote_rx_buff_retry(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int i = 0; int failed = 0; int retry_count; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); pr_err("%s", buf); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); while (!failed) { /* open port for loopback */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer_mock); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* Queue up an RX buffer retry */ get_rx_buffer_mock_fail = 1; ret = msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); while (!cb_data.get_rx_buff_retry_count) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; mock_cb_data_reset(&cb_data); /* trigger SSR */ subsystem_restart("external_modem"); /* verify SSR completed */ retry_count = 0; while (cb_data.event_disconnected_ssr == 0) { (void)wait_for_completion_timeout( &cb_data.cb_completion, HZ); INIT_COMPLETION(cb_data.cb_completion); ++retry_count; UT_ASSERT_INT(retry_count, <, 10); } if (failed) break; UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 1); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); /* wait for remote side to finish booting */ retry_count = 0; do { msleep(500); ++retry_count; UT_ASSERT_INT(retry_count, <, 20); } while (!smux_remote_is_active() && !failed); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } mock_cb_data_reset(&cb_data); return i; } /** * Fill test pattern into provided buffer including an optional * redzone before and after the buffer. * * buf --------- * redzone * --------- <- returned pointer * data * --------- <- returned pointer + len * redzone * --------- * * @buf Pointer to the buffer of size len or len+2*RED_ZONE_SIZE (redzone) * @len Length of the *data* buffer (excluding the extra redzone buffers) * @redzone If true, adds redzone data * * @returns pointer to buffer (buf + RED_ZONE_SIZE if redzone enabled) */ static uint8_t *test_pattern_fill(char *buf, int len, int redzone) { char *buf_ptr; uint8_t ch; if (redzone) { memset(buf, RED_ZONE_PRE_CH, RED_ZONE_SIZE); buf += RED_ZONE_SIZE; memset(buf + len, RED_ZONE_POS_CH, RED_ZONE_SIZE); } for (ch = 0, buf_ptr = buf; len > 0; --len, ++ch) *buf_ptr++ = (char)ch; return buf; } /** * Verify test pattern generated by test_pattern_fill. * * @buf_ptr Pointer to buffer pointer * @len Length of the *data* buffer (excluding redzone bytes) * @redzone If true, verifies redzone and adjusts *buf_ptr * @errmsg Buffer for error message * @errmsg_max Size of error message buffer * * @returns 0 for success; length of error message otherwise */ static unsigned test_pattern_verify(char **buf_ptr, int len, int redzone, char *errmsg, int errmsg_max) { int n; int i = 0; char linebuff[80]; char *zone_ptr; if (redzone) { *buf_ptr -= RED_ZONE_SIZE; zone_ptr = *buf_ptr; /* verify prefix redzone */ for (n = 0; n < RED_ZONE_SIZE; ++n) { if (zone_ptr[n] != RED_ZONE_PRE_CH) { hex_dump_to_buffer(zone_ptr, RED_ZONE_SIZE, RED_ZONE_SIZE, 1, linebuff, sizeof(linebuff), 1); i += scnprintf(errmsg + i, errmsg_max - i, "Pre-redzone violation: %s\n", linebuff); break; } } /* verify postfix redzone */ zone_ptr = *buf_ptr + RED_ZONE_SIZE + len; for (n = 0; n < RED_ZONE_SIZE; ++n) { if (zone_ptr[n] != RED_ZONE_POS_CH) { hex_dump_to_buffer(zone_ptr, RED_ZONE_SIZE, RED_ZONE_SIZE, 1, linebuff, sizeof(linebuff), 1); i += scnprintf(errmsg + i, errmsg_max - i, "Post-redzone violation: %s\n", linebuff); break; } } } return i; } /** * Write a multiple packets in ascending size and verify packet is received * correctly. * * @buf Buffer for status message * @max Size of buffer * @name Name of the test for error reporting * * @returns Number of bytes written to @buf * * Requires that the port already be opened and loopback mode is * configured correctly (if required). */ static int smux_ut_loopback_big_pkt(char *buf, int max, const char *name) { struct test_vector test_data[] = { {0, 64}, {0, 128}, {0, 256}, {0, 512}, {0, 1024}, {0, 1500}, {0, 2048}, {0, 4096}, {0, 0}, }; int i = 0; int failed = 0; struct test_vector *tv; /* generate test data */ for (tv = test_data; tv->len > 0; ++tv) { tv->data = kmalloc(tv->len + 2 * RED_ZONE_SIZE, GFP_KERNEL); if (!tv->data) { i += scnprintf(buf + i, max - i, "%s: Unable to allocate %d bytes\n", __func__, tv->len); failed = 1; goto out; } tv->data = test_pattern_fill((uint8_t *)tv->data, tv->len, 1); } /* run test */ i += scnprintf(buf + i, max - i, "Running %s\n", name); while (!failed) { i += smux_ut_basic_core(buf + i, max - i, test_data, name); break; } out: if (failed) { pr_err("%s: Failed\n", name); i += scnprintf(buf + i, max - i, "\tFailed\n"); } for (tv = test_data; tv->len > 0; ++tv) { if (tv->data) { i += test_pattern_verify((char **)&tv->data, tv->len, 1, buf + i, max - i); kfree(tv->data); } } return i; } /** * Verify Large-packet Local Loopback Support. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf * * Open port in local loopback mode and write a multiple packets in ascending * size and verify packet is received correctly. */ static int smux_ut_local_big_pkt(char *buf, int max) { int i = 0; int ret; ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); if (ret == 0) { smux_byte_loopback = SMUX_TEST_LCID; i += smux_ut_loopback_big_pkt(buf, max, __func__); smux_byte_loopback = 0; } else { i += scnprintf(buf + i, max - i, "%s: Unable to set loopback mode\n", __func__); } return i; } /** * Verify Large-packet Remote Loopback Support. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf * * Open port in remote loopback mode and write a multiple packets in ascending * size and verify packet is received correctly. */ static int smux_ut_remote_big_pkt(char *buf, int max) { int i = 0; int ret; ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); if (ret == 0) { i += smux_ut_loopback_big_pkt(buf, max, __func__); } else { i += scnprintf(buf + i, max - i, "%s: Unable to set loopback mode\n", __func__); } return i; } /** * Run a large packet test for throughput metrics. * * Repeatedly send a packet for 100 iterations to get throughput metrics. */ static int smux_ut_remote_throughput(char *buf, int max) { struct test_vector test_data[] = { {0, 1500}, {0, 0}, }; int failed = 0; int i = 0; int loop = 0; struct test_vector *tv; int ret; /* generate test data */ for (tv = test_data; tv->len > 0; ++tv) { tv->data = kmalloc(tv->len, GFP_KERNEL); if (!tv->data) { i += scnprintf(buf + i, max - i, "%s: Unable to allocate %d bytes\n", __func__, tv->len); failed = 1; goto out; } test_pattern_fill((uint8_t *)tv->data, tv->len, 0); } /* run test */ i += scnprintf(buf + i, max - i, "Running %s\n", __func__); while (!failed && loop < 100) { ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); ++loop; } out: if (failed) { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); } for (tv = test_data; tv->len > 0; ++tv) kfree(tv->data); return i; } /** * Verify set and get operations for each TIOCM bit. * * @buf Buffer for status message * @max Size of buffer * @name Name of the test for error reporting * * @returns Number of bytes written to @buf */ static int smux_ut_tiocm(char *buf, int max, const char *name) { static struct smux_mock_callback cb_data; static int cb_initialized; static const struct tiocm_test_vector tiocm_vectors[] = { /* bit to set, set old, set new, clear old */ {TIOCM_DTR, TIOCM_DTR, TIOCM_DTR | TIOCM_DSR, TIOCM_DSR}, {TIOCM_RTS, TIOCM_RTS, TIOCM_RTS | TIOCM_CTS, TIOCM_CTS}, {TIOCM_RI, 0x0, TIOCM_RI, TIOCM_RI}, {TIOCM_CD, 0x0, TIOCM_CD, TIOCM_CD}, }; int i = 0; int failed = 0; int n; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", name); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); while (!failed) { /* open port */ ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* set and clear each TIOCM bit */ for (n = 0; n < ARRAY_SIZE(tiocm_vectors) && !failed; ++n) { /* set signal and verify */ ret = msm_smux_tiocm_set(SMUX_TEST_LCID, tiocm_vectors[n].input, 0x0); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_tiocm, ==, 1); UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_old, ==, tiocm_vectors[n].set_old); UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_new, ==, tiocm_vectors[n].set_new); mock_cb_data_reset(&cb_data); /* clear signal and verify */ ret = msm_smux_tiocm_set(SMUX_TEST_LCID, 0x0, tiocm_vectors[n].input); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_tiocm, ==, 1); UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_old, ==, tiocm_vectors[n].clr_old); UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_new, ==, 0x0); mock_cb_data_reset(&cb_data); } if (failed) break; /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", name); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } mock_cb_data_reset(&cb_data); return i; } /** * Verify TIOCM Status Bits for local loopback. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_local_tiocm(char *buf, int max) { int i = 0; int ret; ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); if (ret == 0) { smux_byte_loopback = SMUX_TEST_LCID; i += smux_ut_tiocm(buf, max, __func__); smux_byte_loopback = 0; } else { i += scnprintf(buf + i, max - i, "%s: Unable to set loopback mode\n", __func__); } return i; } /** * Verify TIOCM Status Bits for remote loopback. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_remote_tiocm(char *buf, int max) { int i = 0; int ret; ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); if (ret == 0) { i += smux_ut_tiocm(buf, max, __func__); } else { i += scnprintf(buf + i, max - i, "%s: Unable to set loopback mode\n", __func__); } return i; } /** * Verify High/Low Watermark notifications. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_local_wm(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int i = 0; int failed = 0; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); pr_err("%s", buf); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); smux_byte_loopback = SMUX_TEST_LCID; while (!failed) { /* open port for loopback with TX disabled */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK | SMUX_CH_OPTION_REMOTE_TX_STOP, 0); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* transmit 4 packets and verify high-watermark notification */ ret = 0; ret |= msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); ret |= msm_smux_write(SMUX_TEST_LCID, (void *)2, test_array, sizeof(test_array)); ret |= msm_smux_write(SMUX_TEST_LCID, (void *)3, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 0); UT_ASSERT_INT(cb_data.event_high_wm, ==, 0); ret = msm_smux_write(SMUX_TEST_LCID, (void *)4, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.event_high_wm, ==, 1); UT_ASSERT_INT(cb_data.event_low_wm, ==, 0); mock_cb_data_reset(&cb_data); /* exceed watermark and verify failure return value */ ret = msm_smux_write(SMUX_TEST_LCID, (void *)5, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, -EAGAIN); /* re-enable TX and verify low-watermark notification */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, 0, SMUX_CH_OPTION_REMOTE_TX_STOP); UT_ASSERT_INT(ret, ==, 0); while (cb_data.cb_count < 9) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; UT_ASSERT_INT(cb_data.event_high_wm, ==, 0); UT_ASSERT_INT(cb_data.event_low_wm, ==, 1); UT_ASSERT_INT(cb_data.event_write_done, ==, 4); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } smux_byte_loopback = 0; mock_cb_data_reset(&cb_data); return i; } /** * Verify smuxld_receive_buf regular and error processing. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_local_smuxld_receive_buf(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; struct mock_read_event *meta; int i = 0; int failed = 0; int ret; char data[] = {SMUX_UT_ECHO_REQ, SMUX_UT_ECHO_REQ, SMUX_UT_ECHO_REQ, }; char flags[] = {0x0, 0x1, 0x0,}; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); smux_byte_loopback = SMUX_TEST_LCID; while (!failed) { /* open port for loopback */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* * Verify RX error processing by sending 3 echo requests: * one OK, one fail, and a final OK * * The parsing framework should process the requests * and send us three BYTE command packets with * ECHO ACK FAIL and ECHO ACK OK characters. */ smuxld_receive_buf(0, data, flags, sizeof(data)); /* verify response characters */ do { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } while (cb_data.cb_count < 3); UT_ASSERT_INT(cb_data.cb_count, ==, 3); UT_ASSERT_INT(cb_data.event_read_done, ==, 3); meta = list_first_entry(&cb_data.read_events, struct mock_read_event, list); UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, SMUX_UT_ECHO_ACK_OK); list_del(&meta->list); meta = list_first_entry(&cb_data.read_events, struct mock_read_event, list); UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, SMUX_UT_ECHO_ACK_FAIL); list_del(&meta->list); meta = list_first_entry(&cb_data.read_events, struct mock_read_event, list); UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, SMUX_UT_ECHO_ACK_OK); list_del(&meta->list); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } smux_byte_loopback = 0; mock_cb_data_reset(&cb_data); return i; } /** * Allocates a new buffer or returns a failure based upon the * global @get_rx_buffer_mock_fail. */ static int get_rx_buffer_mock(void *priv, void **pkt_priv, void **buffer, int size) { void *rx_buf; unsigned long flags; struct smux_mock_callback *cb_ptr; cb_ptr = (struct smux_mock_callback *)priv; if (!cb_ptr) { pr_err("%s: no callback data\n", __func__); return -ENXIO; } if (get_rx_buffer_mock_fail) { /* force failure and log failure event */ struct mock_get_rx_buff_event *meta; meta = kmalloc(sizeof(struct mock_get_rx_buff_event), GFP_KERNEL); if (!meta) { pr_err("%s: unable to allocate metadata\n", __func__); return -ENOMEM; } INIT_LIST_HEAD(&meta->list); meta->size = size; meta->jiffies = jiffies; spin_lock_irqsave(&cb_ptr->lock, flags); ++cb_ptr->get_rx_buff_retry_count; list_add_tail(&meta->list, &cb_ptr->get_rx_buff_retry_events); ++cb_ptr->cb_count; complete(&cb_ptr->cb_completion); spin_unlock_irqrestore(&cb_ptr->lock, flags); return -EAGAIN; } else { rx_buf = kmalloc(size, GFP_KERNEL); *pkt_priv = (void *)0x1234; *buffer = rx_buf; return 0; } return 0; } /** * Verify get_rx_buffer callback retry. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_local_get_rx_buff_retry(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int i = 0; int failed = 0; char try_two[] = "try 2"; int ret; unsigned long start_j; struct mock_get_rx_buff_event *event; struct mock_read_event *read_event; int try; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); pr_err("%s", buf); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); smux_byte_loopback = SMUX_TEST_LCID; while (!failed) { /* open port for loopback */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK, SMUX_CH_OPTION_AUTO_REMOTE_TX_STOP); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer_mock); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* * Force get_rx_buffer failure for a single RX packet * * The get_rx_buffer calls should follow an exponential * back-off with a maximum timeout of 1024 ms after which we * will get a failure notification. * * Try Post Delay (ms) * 0 - * 1 1 * 2 2 * 3 4 * 4 8 * 5 16 * 6 32 * 7 64 * 8 128 * 9 256 * 10 512 * 11 1024 * 12 Fail * * All times are limited by the precision of the timer * framework, so ranges are used in the test * verification. */ get_rx_buffer_mock_fail = 1; start_j = jiffies; ret = msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_write(SMUX_TEST_LCID, (void *)2, try_two, sizeof(try_two)); UT_ASSERT_INT(ret, ==, 0); /* wait for RX failure event */ while (cb_data.event_read_failed == 0) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 2*HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; /* verify retry attempts */ UT_ASSERT_INT(cb_data.get_rx_buff_retry_count, ==, 12); event = list_first_entry(&cb_data.get_rx_buff_retry_events, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 0, 0 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 1, 1 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 2, 2 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 4, 4 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 8, 8 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 16, 16 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 32 - 20, 32 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 64 - 20, 64 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 128 - 20, 128 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 256 - 20, 256 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 512 - 20, 512 + 20); start_j = event->jiffies; event = list_first_entry(&event->list, struct mock_get_rx_buff_event, list); pr_err("%s: event->jiffies = %d (ms)\n", __func__, jiffies_to_msecs(event->jiffies - start_j)); UT_ASSERT_INT_IN_RANGE( jiffies_to_msecs(event->jiffies - start_j), 1024 - 20, 1024 + 20); mock_cb_data_reset(&cb_data); /* verify 2nd pending RX packet goes through */ get_rx_buffer_mock_fail = 0; INIT_COMPLETION(cb_data.cb_completion); if (cb_data.event_read_done == 0) UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.event_read_done, ==, 1); UT_ASSERT_INT(list_empty(&cb_data.read_events), ==, 0); read_event = list_first_entry(&cb_data.read_events, struct mock_read_event, list); UT_ASSERT_PTR(read_event->meta.pkt_priv, ==, (void *)0x1234); UT_ASSERT_PTR(read_event->meta.buffer, !=, NULL); UT_ASSERT_INT(0, ==, memcmp(read_event->meta.buffer, try_two, sizeof(try_two))); mock_cb_data_reset(&cb_data); /* Test maximum retry queue size */ get_rx_buffer_mock_fail = 1; for (try = 0; try < (SMUX_RX_RETRY_MAX_PKTS + 1); ++try) { mock_cb_data_reset(&cb_data); ret = msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); } /* should have 32 successful rx packets and 1 failed */ while (cb_data.event_read_failed == 0) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 2*HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; get_rx_buffer_mock_fail = 0; while (cb_data.event_read_done < SMUX_RX_RETRY_MAX_PKTS) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 2*HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; UT_ASSERT_INT(1, ==, cb_data.event_read_failed); UT_ASSERT_INT(SMUX_RX_RETRY_MAX_PKTS, ==, cb_data.event_read_done); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } smux_byte_loopback = 0; mock_cb_data_reset(&cb_data); return i; } /** * Verify get_rx_buffer callback retry for auto-rx flow control. * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_local_get_rx_buff_retry_auto(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int i = 0; int failed = 0; int ret; int try; int try_rx_retry_wm; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); pr_err("%s", buf); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); smux_byte_loopback = SMUX_TEST_LCID; while (!failed) { /* open port for loopback */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_LOCAL_LOOPBACK | SMUX_CH_OPTION_AUTO_REMOTE_TX_STOP, 0); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer_mock); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* Test high rx-retry watermark */ get_rx_buffer_mock_fail = 1; try_rx_retry_wm = 0; for (try = 0; try < SMUX_RX_RETRY_MAX_PKTS; ++try) { pr_err("%s: try %d\n", __func__, try); ret = msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); if (failed) break; if (!try_rx_retry_wm && cb_data.event_rx_retry_high_wm) { /* RX high watermark hit */ try_rx_retry_wm = try + 1; break; } while (cb_data.event_write_done <= try) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; } if (failed) break; /* RX retry high watermark should have been set */ UT_ASSERT_INT(cb_data.event_rx_retry_high_wm, ==, 1); UT_ASSERT_INT(try_rx_retry_wm, ==, SMUX_RX_WM_HIGH); /* * Disabled RX buffer allocation failure and wait for * the SMUX_RX_WM_HIGH count successful packets. */ get_rx_buffer_mock_fail = 0; while (cb_data.event_read_done < SMUX_RX_WM_HIGH) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 2*HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); } if (failed) break; UT_ASSERT_INT(0, ==, cb_data.event_read_failed); UT_ASSERT_INT(SMUX_RX_WM_HIGH, ==, cb_data.event_read_done); UT_ASSERT_INT(cb_data.event_rx_retry_low_wm, ==, 1); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_close(SMUX_TEST_LCID); } smux_byte_loopback = 0; mock_cb_data_reset(&cb_data); return i; } /** * Verify remote flow control (remote TX stop). * * @buf Buffer for status message * @max Size of buffer * * @returns Number of bytes written to @buf */ static int smux_ut_remote_tx_stop(char *buf, int max) { static struct smux_mock_callback cb_data; static int cb_initialized; int i = 0; int failed = 0; int ret; i += scnprintf(buf + i, max - i, "Running %s\n", __func__); pr_err("%s", buf); if (!cb_initialized) mock_cb_data_init(&cb_data); mock_cb_data_reset(&cb_data); while (!failed) { /* open port for remote loopback */ ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); UT_ASSERT_INT(ret, ==, 0); ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* send 1 packet and verify response */ ret = msm_smux_write(SMUX_TEST_LCID, (void *)1, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.event_write_done, ==, 1); INIT_COMPLETION(cb_data.cb_completion); if (!cb_data.event_read_done) { UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); } UT_ASSERT_INT(cb_data.event_read_done, ==, 1); mock_cb_data_reset(&cb_data); /* enable flow control */ UT_ASSERT_INT(smux_lch[SMUX_TEST_LCID].tx_flow_control, ==, 0); ret = msm_smux_set_ch_option(SMUX_TEST_LCID, SMUX_CH_OPTION_REMOTE_TX_STOP, 0); UT_ASSERT_INT(ret, ==, 0); /* wait for remote echo and clear our tx_flow control */ msleep(500); UT_ASSERT_INT(smux_lch[SMUX_TEST_LCID].tx_flow_control, ==, 1); smux_lch[SMUX_TEST_LCID].tx_flow_control = 0; /* Send 1 packet and verify no response */ ret = msm_smux_write(SMUX_TEST_LCID, (void *)2, test_array, sizeof(test_array)); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); INIT_COMPLETION(cb_data.cb_completion); UT_ASSERT_INT(cb_data.event_write_done, ==, 1); UT_ASSERT_INT(cb_data.event_read_done, ==, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, 1*HZ), ==, 0); UT_ASSERT_INT(cb_data.event_read_done, ==, 0); mock_cb_data_reset(&cb_data); /* disable flow control and verify response is received */ UT_ASSERT_INT(cb_data.event_read_done, ==, 0); ret = msm_smux_set_ch_option(SMUX_TEST_LCID, 0, SMUX_CH_OPTION_REMOTE_TX_STOP); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.event_read_done, ==, 1); mock_cb_data_reset(&cb_data); /* close port */ ret = msm_smux_close(SMUX_TEST_LCID); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); break; } if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); msm_smux_set_ch_option(SMUX_TEST_LCID, 0, SMUX_CH_OPTION_REMOTE_TX_STOP); msm_smux_close(SMUX_TEST_LCID); } mock_cb_data_reset(&cb_data); return i; } /** * Verify Remote-initiated wakeup test case. * * @buf Output buffer for failure/status messages * @max Size of @buf */ static int smux_ut_remote_initiated_wakeup(char *buf, int max) { int i = 0; int failed = 0; static struct smux_mock_callback cb_data; static int cb_initialized; int ret; if (!cb_initialized) mock_cb_data_init(&cb_data); smux_set_loopback_data_reply_delay(SMUX_REMOTE_DELAY_TIME_MS); mock_cb_data_reset(&cb_data); do { unsigned long start_j; unsigned transfer_time; unsigned lwakeups_start; unsigned rwakeups_start; unsigned lwakeups_end; unsigned rwakeups_end; unsigned lwakeup_delta; unsigned rwakeup_delta; /* open port */ ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, get_rx_buffer); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_connected, ==, 1); mock_cb_data_reset(&cb_data); /* do local wakeup test and send echo packet */ msleep(SMUX_REMOTE_INACTIVITY_TIME_MS); smux_get_wakeup_counts(&lwakeups_start, &rwakeups_start); msm_smux_write(SMUX_TEST_LCID, (void *)0x12345678, "Hello", 5); UT_ASSERT_INT(ret, ==, 0); UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, HZ), >, 0); UT_ASSERT_INT(cb_data.cb_count, ==, 1); UT_ASSERT_INT(cb_data.event_write_done, ==, 1); mock_cb_data_reset(&cb_data); /* verify local initiated wakeup */ smux_get_wakeup_counts(&lwakeups_end, &rwakeups_end); if (lwakeups_end > lwakeups_start) i += scnprintf(buf + i, max - i, "\tGood - have Apps-initiated wakeup\n"); else i += scnprintf(buf + i, max - i, "\tBad - no Apps-initiated wakeup\n"); /* verify remote wakeup and echo response */ smux_get_wakeup_counts(&lwakeups_start, &rwakeups_start); start_j = jiffies; INIT_COMPLETION(cb_data.cb_completion); if (!cb_data.event_read_done) UT_ASSERT_INT( (int)wait_for_completion_timeout( &cb_data.cb_completion, SMUX_REMOTE_DELAY_TIME_MS * 2), >, 0); transfer_time = (unsigned)jiffies_to_msecs(jiffies - start_j); UT_ASSERT_INT(cb_data.event_read_done, ==, 1); UT_ASSERT_INT_IN_RANGE(transfer_time, SMUX_REMOTE_DELAY_TIME_MS - SMUX_REMOTE_INACTIVITY_TIME_MS, SMUX_REMOTE_DELAY_TIME_MS + SMUX_REMOTE_INACTIVITY_TIME_MS); smux_get_wakeup_counts(&lwakeups_end, &rwakeups_end); lwakeup_delta = lwakeups_end - lwakeups_end; rwakeup_delta = rwakeups_end - rwakeups_end; if (rwakeup_delta && lwakeup_delta) { i += scnprintf(buf + i, max - i, "\tBoth local and remote wakeup - re-run test (transfer time %d ms)\n", transfer_time); failed = 1; break; } else if (lwakeup_delta) { i += scnprintf(buf + i, max - i, "\tLocal wakeup only (transfer time %d ms) - FAIL\n", transfer_time); failed = 1; break; } else { i += scnprintf(buf + i, max - i, "\tRemote wakeup verified (transfer time %d ms) - OK\n", transfer_time); } } while (0); if (!failed) { i += scnprintf(buf + i, max - i, "\tOK\n"); } else { pr_err("%s: Failed\n", __func__); i += scnprintf(buf + i, max - i, "\tFailed\n"); i += mock_cb_data_print(&cb_data, buf + i, max - i); } mock_cb_data_reset(&cb_data); msm_smux_close(SMUX_TEST_LCID); wait_for_completion_timeout(&cb_data.cb_completion, HZ); mock_cb_data_reset(&cb_data); smux_set_loopback_data_reply_delay(0); return i; } static char debug_buffer[DEBUG_BUFMAX]; static ssize_t debug_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int (*fill)(char *buf, int max) = file->private_data; int bsize; if (*ppos != 0) return 0; bsize = fill(debug_buffer, DEBUG_BUFMAX); return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); } static int debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static const struct file_operations debug_ops = { .read = debug_read, .open = debug_open, }; static void debug_create(const char *name, mode_t mode, struct dentry *dent, int (*fill)(char *buf, int max)) { debugfs_create_file(name, mode, dent, fill, &debug_ops); } static int __init smux_debugfs_init(void) { struct dentry *dent; dent = debugfs_create_dir("n_smux_test", 0); if (IS_ERR(dent)) return PTR_ERR(dent); /* * Add Unit Test entries. * * The idea with unit tests is that you can run all of them * from ADB shell by doing: * adb shell * cat ut* * * And if particular tests fail, you can then repeatedly run the failing * tests as you debug and resolve the failing test. */ debug_create("ut_local_basic", 0444, dent, smux_ut_basic); debug_create("ut_remote_basic", 0444, dent, smux_ut_remote_basic); debug_create("ut_local_big_pkt", 0444, dent, smux_ut_local_big_pkt); debug_create("ut_remote_big_pkt", 0444, dent, smux_ut_remote_big_pkt); debug_create("ut_local_tiocm", 0444, dent, smux_ut_local_tiocm); debug_create("ut_remote_tiocm", 0444, dent, smux_ut_remote_tiocm); debug_create("ut_local_wm", 0444, dent, smux_ut_local_wm); debug_create("ut_local_smuxld_receive_buf", 0444, dent, smux_ut_local_smuxld_receive_buf); debug_create("ut_local_get_rx_buff_retry", 0444, dent, smux_ut_local_get_rx_buff_retry); debug_create("ut_local_get_rx_buff_retry_auto", 0444, dent, smux_ut_local_get_rx_buff_retry_auto); debug_create("ut_ssr_remote_basic", 0444, dent, smux_ut_ssr_remote_basic); debug_create("ut_ssr_remote_open", 0444, dent, smux_ut_ssr_remote_open); debug_create("ut_ssr_remote_rx_buff_retry", 0444, dent, smux_ut_ssr_remote_rx_buff_retry); debug_create("ut_remote_tx_stop", 0444, dent, smux_ut_remote_tx_stop); debug_create("ut_remote_throughput", 0444, dent, smux_ut_remote_throughput); debug_create("ut_remote_initiated_wakeup", 0444, dent, smux_ut_remote_initiated_wakeup); return 0; } late_initcall(smux_debugfs_init);