msm: smd: Add SMSM state queue

To reduce the chance of missing SMSM state transitions,
push the SMSM state into a queue in the interrupt handler
and then pull the state for processing the in callback
workqueue.

Change-Id: Ib66bc15f7445369ea7ad2ca271a79ac10b8105a0
CRs-Fixed: 323725
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
diff --git a/arch/arm/mach-msm/smd.c b/arch/arm/mach-msm/smd.c
index 691dad2..b31550a 100644
--- a/arch/arm/mach-msm/smd.c
+++ b/arch/arm/mach-msm/smd.c
@@ -1,7 +1,7 @@
 /* arch/arm/mach-msm/smd.c
  *
  * Copyright (C) 2007 Google, Inc.
- * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved.
  * Author: Brian Swetland <swetland@google.com>
  *
  * This software is licensed under the terms of the GNU General Public
@@ -31,6 +31,7 @@
 #include <linux/ctype.h>
 #include <linux/remote_spinlock.h>
 #include <linux/uaccess.h>
+#include <linux/kfifo.h>
 #include <mach/msm_smd.h>
 #include <mach/msm_iomap.h>
 #include <mach/system.h>
@@ -61,6 +62,7 @@
 #define MODULE_NAME "msm_smd"
 #define SMEM_VERSION 0x000B
 #define SMD_VERSION 0x00020000
+#define SMSM_SNAPSHOT_CNT 64
 
 uint32_t SMSM_NUM_ENTRIES = 8;
 uint32_t SMSM_NUM_HOSTS = 3;
@@ -79,6 +81,7 @@
 };
 
 static struct smsm_shared_info smsm_info;
+struct kfifo smsm_snapshot_fifo;
 
 struct smsm_size_info_type {
 	uint32_t num_hosts;
@@ -239,6 +242,7 @@
 static LIST_HEAD(smd_ch_list_loopback);
 static irqreturn_t smsm_irq_handler(int irq, void *data);
 static void smd_fake_irq_handler(unsigned long arg);
+static void smsm_cb_snapshot(void);
 
 static void notify_smsm_cb_clients_worker(struct work_struct *work);
 static DECLARE_WORK(smsm_cb_work, notify_smsm_cb_clients_worker);
@@ -308,7 +312,7 @@
 		MSM_TRIG_A2DSPS_SMSM_INT;
 	}
 
-	schedule_work(&smsm_cb_work);
+	smsm_cb_snapshot();
 }
 
 static inline void notify_modem_smd(void)
@@ -1955,6 +1959,14 @@
 		SMSM_NUM_HOSTS = smsm_size_info->num_hosts;
 	}
 
+	i = kfifo_alloc(&smsm_snapshot_fifo,
+			sizeof(uint32_t) * SMSM_NUM_ENTRIES * SMSM_SNAPSHOT_CNT,
+			GFP_KERNEL);
+	if (i) {
+		pr_err("%s: SMSM state fifo alloc failed %d\n", __func__, i);
+		return i;
+	}
+
 	if (!smsm_info.state) {
 		smsm_info.state = smem_alloc2(ID_SHARED_STATE,
 					      SMSM_NUM_ENTRIES *
@@ -2024,6 +2036,31 @@
 }
 EXPORT_SYMBOL(smsm_reset_modem_cont);
 
+static void smsm_cb_snapshot(void)
+{
+	int n;
+	uint32_t new_state;
+	int ret;
+
+	ret = kfifo_avail(&smsm_snapshot_fifo);
+	if (ret < (SMSM_NUM_ENTRIES * 4)) {
+		pr_err("%s: SMSM snapshot full %d\n", __func__, ret);
+		return;
+	}
+
+	for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
+		new_state = __raw_readl(SMSM_STATE_ADDR(n));
+
+		ret = kfifo_in(&smsm_snapshot_fifo,
+				&new_state, sizeof(new_state));
+		if (ret != sizeof(new_state)) {
+			pr_err("%s: SMSM snapshot failure %d\n", __func__, ret);
+			return;
+		}
+	}
+	schedule_work(&smsm_cb_work);
+}
+
 static irqreturn_t smsm_irq_handler(int irq, void *data)
 {
 	unsigned long flags;
@@ -2039,7 +2076,9 @@
 				prev_smem_q6_apps_smsm = mux_val;
 		}
 
-		schedule_work(&smsm_cb_work);
+		spin_lock_irqsave(&smem_lock, flags);
+		smsm_cb_snapshot();
+		spin_unlock_irqrestore(&smem_lock, flags);
 		return IRQ_HANDLED;
 	}
 
@@ -2097,7 +2136,7 @@
 			notify_other_smsm(SMSM_APPS_STATE, (old_apps ^ apps));
 		}
 
-		schedule_work(&smsm_cb_work);
+		smsm_cb_snapshot();
 	}
 	spin_unlock_irqrestore(&smem_lock, flags);
 	return IRQ_HANDLED;
@@ -2212,35 +2251,41 @@
 	int n;
 	uint32_t new_state;
 	uint32_t state_changes;
+	int ret;
+	int snapshot_size = SMSM_NUM_ENTRIES * sizeof(uint32_t);
 
-	mutex_lock(&smsm_lock);
-
-	if (!smsm_states) {
-		/* smsm not yet initialized */
-		mutex_unlock(&smsm_lock);
+	if (!smd_initialized)
 		return;
-	}
 
-	for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
-		state_info = &smsm_states[n];
-		new_state = __raw_readl(SMSM_STATE_ADDR(n));
+	while (kfifo_len(&smsm_snapshot_fifo) >= snapshot_size) {
+		mutex_lock(&smsm_lock);
+		for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
+			state_info = &smsm_states[n];
 
-		if (new_state != state_info->last_value) {
-			state_changes = state_info->last_value ^ new_state;
-
-			list_for_each_entry(cb_info,
-				&state_info->callbacks, cb_list) {
-
-				if (cb_info->mask & state_changes)
-					cb_info->notify(cb_info->data,
-						state_info->last_value,
-						new_state);
+			ret = kfifo_out(&smsm_snapshot_fifo, &new_state,
+					sizeof(new_state));
+			if (ret != sizeof(new_state)) {
+				pr_err("%s: snapshot underflow %d\n",
+					__func__, ret);
+				mutex_unlock(&smsm_lock);
+				return;
 			}
-			state_info->last_value = new_state;
-		}
-	}
 
-	mutex_unlock(&smsm_lock);
+			state_changes = state_info->last_value ^ new_state;
+			if (state_changes) {
+				list_for_each_entry(cb_info,
+					&state_info->callbacks, cb_list) {
+
+					if (cb_info->mask & state_changes)
+						cb_info->notify(cb_info->data,
+							state_info->last_value,
+							new_state);
+				}
+				state_info->last_value = new_state;
+			}
+		}
+		mutex_unlock(&smsm_lock);
+	}
 }
 
 
diff --git a/arch/arm/mach-msm/smd_debug.c b/arch/arm/mach-msm/smd_debug.c
index d7602f2..6171c92 100644
--- a/arch/arm/mach-msm/smd_debug.c
+++ b/arch/arm/mach-msm/smd_debug.c
@@ -1,7 +1,7 @@
 /* arch/arm/mach-msm/smd_debug.c
  *
  * Copyright (C) 2007 Google, Inc.
- * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved.
  * Author: Brian Swetland <swetland@google.com>
  *
  * This software is licensed under the terms of the GNU General Public
@@ -18,6 +18,7 @@
 #include <linux/debugfs.h>
 #include <linux/list.h>
 #include <linux/ctype.h>
+#include <linux/jiffies.h>
 
 #include <mach/msm_iomap.h>
 
@@ -246,6 +247,7 @@
 	uint32_t new_state;
 };
 static struct SMSM_CB_DATA smsm_cb_data;
+static struct completion smsm_cb_completion;
 
 static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state)
 {
@@ -253,6 +255,7 @@
 	smsm_cb_data.old_state = old_state;
 	smsm_cb_data.new_state = new_state;
 	smsm_cb_data.data = data;
+	complete_all(&smsm_cb_completion);
 }
 
 #define UT_EQ_INT(a, b) \
@@ -265,6 +268,16 @@
 	} \
 	do {} while (0)
 
+#define UT_GT_INT(a, b) \
+	if ((a) <= (b)) { \
+		i += scnprintf(buf + i, max - i, \
+			"%s:%d " #a "(%d) > " #b "(%d)\n", \
+				__func__, __LINE__, \
+				a, b); \
+		break; \
+	} \
+	do {} while (0)
+
 #define SMSM_CB_TEST_INIT() \
 	do { \
 		smsm_cb_data.cb_count = 0; \
@@ -290,16 +303,21 @@
 
 		/* de-assert SMSM_SMD_INIT to trigger state update */
 		UT_EQ_INT(smsm_cb_data.cb_count, 0);
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 
 		UT_EQ_INT(smsm_cb_data.cb_count, 1);
-		UT_EQ_INT(smsm_cb_data.cb_count, 1);
 		UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, SMSM_SMDINIT);
 		UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, 0x0);
 		UT_EQ_INT((int)smsm_cb_data.data, 0x1234);
 
 		/* re-assert SMSM_SMD_INIT to trigger state update */
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 2);
 		UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, 0x0);
 		UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, SMSM_SMDINIT);
@@ -310,8 +328,11 @@
 		UT_EQ_INT(ret, 2);
 
 		/* make sure state change doesn't cause any more callbacks */
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 2);
 
 		i += scnprintf(buf + i, max - i, "Test %d - PASS\n", test_num);
@@ -329,28 +350,49 @@
 		UT_EQ_INT(ret, 1);
 
 		/* verify both callback bits work */
+		INIT_COMPLETION(smsm_cb_completion);
 		UT_EQ_INT(smsm_cb_data.cb_count, 0);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 1);
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 2);
 
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 3);
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 4);
 
 		/* deregister 1st callback */
 		ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT,
 				smsm_state_cb, (void *)0x1234);
 		UT_EQ_INT(ret, 1);
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 4);
 
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 5);
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 6);
 
 		/* deregister 2nd callback */
@@ -359,8 +401,11 @@
 		UT_EQ_INT(ret, 2);
 
 		/* make sure state change doesn't cause any more callbacks */
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
 		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 6);
 
 		i += scnprintf(buf + i, max - i, "Test %d - PASS\n", test_num);
@@ -378,12 +423,18 @@
 		UT_EQ_INT(ret, 0);
 
 		/* verify both callbacks work */
+		INIT_COMPLETION(smsm_cb_completion);
 		UT_EQ_INT(smsm_cb_data.cb_count, 0);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 1);
 		UT_EQ_INT((int)smsm_cb_data.data, 0x1234);
 
+		INIT_COMPLETION(smsm_cb_completion);
 		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
 		UT_EQ_INT(smsm_cb_data.cb_count, 2);
 		UT_EQ_INT((int)smsm_cb_data.data, 0x3456);
 
@@ -659,6 +710,8 @@
 	debug_create("version", 0444, dent, debug_read_smem_version);
 	debug_create("smsm_test", 0444, dent, debug_test_smsm);
 
+	init_completion(&smsm_cb_completion);
+
 	return 0;
 }