tty: n_smux: Add wakeup test case

Reproducing and isolating wakeup issues caused by incorrect UART
configuration and hardware issues currently requires analysis of the
internal logs which is often time consuming.

Add wakeup test case to allow teams to easily reproduce wakeup issues or
verify wakeup functionality.

CRs-Fixed: 493907
Change-Id: I614d6ee3eefc6563930c45564b76452e89f65dc8
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
diff --git a/drivers/tty/n_smux.c b/drivers/tty/n_smux.c
index 0348145..92f9505 100644
--- a/drivers/tty/n_smux.c
+++ b/drivers/tty/n_smux.c
@@ -1,6 +1,6 @@
 /* drivers/tty/n_smux.c
  *
- * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ * 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
@@ -259,6 +259,8 @@
 	unsigned powerdown_enabled;
 	unsigned power_ctl_remote_req_received;
 	struct list_head power_queue;
+	unsigned remote_initiated_wakeup_count;
+	unsigned local_initiated_wakeup_count;
 };
 
 
@@ -279,6 +281,7 @@
 	[SMUX_CMD_CLOSE_LCH] = "CLOSE",
 	[SMUX_CMD_STATUS] = "STATUS",
 	[SMUX_CMD_PWR_CTL] = "PWR",
+	[SMUX_CMD_DELAY] = "DELAY",
 	[SMUX_CMD_BYTE] = "Raw Byte",
 };
 
@@ -1908,6 +1911,7 @@
 		/* wakeup system */
 		SMUX_PWR("smux: %s: Power %d->%d\n", __func__,
 				smux.power_state, SMUX_PWR_ON);
+		smux.remote_initiated_wakeup_count++;
 		smux.power_state = SMUX_PWR_ON;
 		queue_work(smux_tx_wq, &smux_wakeup_work);
 		queue_work(smux_tx_wq, &smux_tx_work);
@@ -2163,6 +2167,62 @@
 }
 
 /**
+ * Sends a delay command to the remote side.
+ *
+ * @ms: Time in milliseconds for the remote side to delay
+ *
+ * This command defines the delay that the remote side will use
+ * to slow the response time for DATA commands.
+ */
+void smux_set_loopback_data_reply_delay(uint32_t ms)
+{
+	struct smux_lch_t *ch = &smux_lch[SMUX_TEST_LCID];
+	struct smux_pkt_t *pkt;
+
+	pkt = smux_alloc_pkt();
+	if (!pkt) {
+		pr_err("%s: unable to allocate packet\n", __func__);
+		return;
+	}
+
+	pkt->hdr.lcid = ch->lcid;
+	pkt->hdr.cmd = SMUX_CMD_DELAY;
+	pkt->hdr.flags = 0;
+	pkt->hdr.payload_len = sizeof(uint32_t);
+	pkt->hdr.pad_len = 0;
+
+	if (smux_alloc_pkt_payload(pkt)) {
+		pr_err("%s: unable to allocate payload\n", __func__);
+		smux_free_pkt(pkt);
+		return;
+	}
+	memcpy(pkt->payload, &ms, sizeof(uint32_t));
+
+	smux_tx_queue(pkt, ch, 1);
+}
+
+/**
+ * Retrieve wakeup counts.
+ *
+ * @local_cnt: Pointer to local wakeup count
+ * @remote_cnt: Pointer to remote wakeup count
+ */
+void smux_get_wakeup_counts(int *local_cnt, int *remote_cnt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&smux.tx_lock_lha2, flags);
+
+	if (local_cnt)
+		*local_cnt = smux.local_initiated_wakeup_count;
+
+	if (remote_cnt)
+		*remote_cnt = smux.remote_initiated_wakeup_count;
+
+	spin_unlock_irqrestore(&smux.tx_lock_lha2, flags);
+}
+
+/**
  * Add channel to transmit-ready list and trigger transmit worker.
  *
  * @ch Channel to add
@@ -2744,6 +2804,7 @@
 				SMUX_PWR("smux: %s: Power %d->%d\n", __func__,
 						smux.power_state,
 						SMUX_PWR_TURNING_ON);
+				smux.local_initiated_wakeup_count++;
 				smux.power_state = SMUX_PWR_TURNING_ON;
 				spin_unlock_irqrestore(&smux.tx_lock_lha2,
 						flags);
diff --git a/drivers/tty/smux_private.h b/drivers/tty/smux_private.h
index 8fdec86..b9a2e89 100644
--- a/drivers/tty/smux_private.h
+++ b/drivers/tty/smux_private.h
@@ -1,6 +1,6 @@
 /* drivers/tty/smux_private.h
  *
- * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ * 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
@@ -122,6 +122,7 @@
 	SMUX_CMD_CLOSE_LCH = 0x2,
 	SMUX_CMD_STATUS = 0x3,
 	SMUX_CMD_PWR_CTL = 0x4,
+	SMUX_CMD_DELAY = 0x5,
 
 	SMUX_CMD_BYTE, /* for internal usage */
 	SMUX_NUM_COMMANDS
@@ -181,6 +182,8 @@
 void smuxld_receive_buf(struct tty_struct *tty, const unsigned char *cp,
 			   char *fp, int count);
 bool smux_remote_is_active(void);
+void smux_set_loopback_data_reply_delay(uint32_t ms);
+void smux_get_wakeup_counts(int *local_cnt, int *remote_cnt);
 
 /* testing parameters */
 extern int smux_byte_loopback;
diff --git a/drivers/tty/smux_test.c b/drivers/tty/smux_test.c
index 27adfa0..dcf1111 100644
--- a/drivers/tty/smux_test.c
+++ b/drivers/tty/smux_test.c
@@ -2163,6 +2163,126 @@
 	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,
@@ -2238,6 +2358,8 @@
 			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;
 }