blob: 130b65e806b8987268613aba45626af0a6bba3d3 [file] [log] [blame]
/* 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 <linux/debugfs.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/termios.h>
#include <linux/sched.h>
#include <linux/smux.h>
#include <mach/subsystem_restart.h>
#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;
int event_local_closed;
int event_remote_closed;
/* 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_local_closed = 0;
cb->event_remote_closed = 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_local_closed=%d\n"
"\tevent_remote_closed=%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_local_closed,
cb->event_remote_closed,
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;
case SMUX_LOCAL_CLOSED:
spin_lock_irqsave(&cb_data_ptr->lock, flags);
++cb_data_ptr->event_local_closed;
cb_data_ptr->event_disconnected_ssr =
((struct smux_meta_disconnected *)metadata)->is_ssr;
spin_unlock_irqrestore(&cb_data_ptr->lock, flags);
break;
case SMUX_REMOTE_CLOSED:
spin_lock_irqsave(&cb_data_ptr->lock, flags);
++cb_data_ptr->event_remote_closed;
cb_data_ptr->event_disconnected_ssr =
((struct smux_meta_disconnected *)metadata)->is_ssr;
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_data.cb_completion, 10*HZ),
>, 0);
INIT_COMPLETION(cb_data.cb_completion);
}
UT_ASSERT_INT(cb_data.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 1);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 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);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);
while (cb_data.cb_count < 3) {
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.cb_count, ==, 3);
UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0);
UT_ASSERT_INT(cb_data.event_local_closed, ==, 1);
UT_ASSERT_INT(cb_data.event_remote_closed, ==, 1);
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);