msm: sysmon: Introduce sysmon_get_reason() for querying failure reasons

The sysmon_get_reason() API queries a subsystem that has crashed for
the reason of the failure and returns it as string.  Common code between
this new API and sysmon_send_event() is refactored into sysmon_send_msg().

Change-Id: I1e4fda7794f493ee286346ade7b1e779a43d1e4c
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/arch/arm/mach-msm/sysmon.c b/arch/arm/mach-msm/sysmon.c
index 679393d..1305bd1 100644
--- a/arch/arm/mach-msm/sysmon.c
+++ b/arch/arm/mach-msm/sysmon.c
@@ -27,7 +27,8 @@
 #include "hsic_sysmon.h"
 #include "sysmon.h"
 
-#define MAX_MSG_LENGTH	50
+#define TX_BUF_SIZE	50
+#define RX_BUF_SIZE	500
 #define TIMEOUT_MS	5000
 
 enum transports {
@@ -40,7 +41,7 @@
 	struct smd_channel	*chan;
 	bool			chan_open;
 	struct completion	resp_ready;
-	char			rx_buf[MAX_MSG_LENGTH];
+	char			rx_buf[RX_BUF_SIZE];
 	enum transports		transport;
 };
 
@@ -60,7 +61,8 @@
 	[SUBSYS_AFTER_POWERUP]   = "after_powerup",
 };
 
-static int sysmon_send_smd(struct sysmon_subsys *ss, char *tx_buf, size_t len)
+static int sysmon_send_smd(struct sysmon_subsys *ss, const char *tx_buf,
+			   size_t len)
 {
 	int ret;
 
@@ -78,7 +80,8 @@
 	return 0;
 }
 
-static int sysmon_send_hsic(struct sysmon_subsys *ss, char *tx_buf, size_t len)
+static int sysmon_send_hsic(struct sysmon_subsys *ss, const char *tx_buf,
+			    size_t len)
 {
 	int ret;
 	size_t actual_len;
@@ -93,11 +96,46 @@
 	return ret;
 }
 
+static int sysmon_send_msg(struct sysmon_subsys *ss, const char *tx_buf,
+			   size_t len)
+{
+	int ret;
+
+	switch (ss->transport) {
+	case TRANSPORT_SMD:
+		ret = sysmon_send_smd(ss, tx_buf, len);
+		break;
+	case TRANSPORT_HSIC:
+		ret = sysmon_send_hsic(ss, tx_buf, len);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (!ret)
+		pr_debug("Received response: %s\n", ss->rx_buf);
+
+	return ret;
+}
+
+/**
+ * sysmon_send_event() - Notify a subsystem of another's state change
+ * @dest_ss:	ID of subsystem the notification should be sent to
+ * @event_ss:	String name of the subsystem that generated the notification
+ * @notif:	ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN)
+ *
+ * Returns 0 for success, -EINVAL for invalid destination or notification IDs,
+ * -ENODEV if the transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds, but with something other than an acknowledgement.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
 int sysmon_send_event(enum subsys_id dest_ss, const char *event_ss,
 		      enum subsys_notif_type notif)
 {
 	struct sysmon_subsys *ss = &subsys[dest_ss];
-	char tx_buf[MAX_MSG_LENGTH];
+	char tx_buf[TX_BUF_SIZE];
 	int ret;
 
 	if (dest_ss < 0 || dest_ss >= SYSMON_NUM_SS ||
@@ -109,24 +147,52 @@
 		 notif_name[notif]);
 
 	mutex_lock(&ss->lock);
-	switch (ss->transport) {
-	case TRANSPORT_SMD:
-		ret = sysmon_send_smd(ss, tx_buf, strlen(tx_buf));
-		break;
-	case TRANSPORT_HSIC:
-		ret = sysmon_send_hsic(ss, tx_buf, strlen(tx_buf));
-		break;
-	default:
-		ret = -EINVAL;
-	}
+	ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
 	if (ret)
 		goto out;
 
-	pr_debug("Received response: %s\n", ss->rx_buf);
 	if (strncmp(ss->rx_buf, "ssr:ack", ARRAY_SIZE(ss->rx_buf)))
 		ret = -ENOSYS;
-	else
-		ret = 0;
+out:
+	mutex_unlock(&ss->lock);
+	return ret;
+}
+
+/**
+ * sysmon_get_reason() - Retrieve failure reason from a subsystem.
+ * @dest_ss:	ID of subsystem to query
+ * @buf:	Caller-allocated buffer for the returned NUL-terminated reason
+ * @len:	Length of @buf
+ *
+ * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
+ * the SMD transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds with something unexpected.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_get_reason(enum subsys_id dest_ss, char *buf, size_t len)
+{
+	struct sysmon_subsys *ss = &subsys[dest_ss];
+	const char tx_buf[] = "ssr:retrieve:sfr";
+	const char expect[] = "ssr:return:";
+	size_t prefix_len = ARRAY_SIZE(expect) - 1;
+	int ret;
+
+	if (dest_ss < 0 || dest_ss >= SYSMON_NUM_SS ||
+	    buf == NULL || len == 0)
+		return -EINVAL;
+
+	mutex_lock(&ss->lock);
+	ret = sysmon_send_msg(ss, tx_buf, ARRAY_SIZE(tx_buf));
+	if (ret)
+		goto out;
+
+	if (strncmp(ss->rx_buf, expect, prefix_len)) {
+		ret = -ENOSYS;
+		goto out;
+	}
+	strlcpy(buf, ss->rx_buf + prefix_len, len);
 out:
 	mutex_unlock(&ss->lock);
 	return ret;
diff --git a/arch/arm/mach-msm/sysmon.h b/arch/arm/mach-msm/sysmon.h
index d014187..77c3329 100644
--- a/arch/arm/mach-msm/sysmon.h
+++ b/arch/arm/mach-msm/sysmon.h
@@ -34,22 +34,10 @@
 	SYSMON_NUM_SS
 };
 
-/**
- * sysmon_send_event() - Notify a subsystem of another's state change.
- * @dest_ss:	ID of subsystem the notification should be sent to.
- * @event_ss:	String name of the subsystem that generated the notification.
- * @notif:	ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN)
- *
- * Returns 0 for success, -EINVAL for invalid destination or notification IDs,
- * -ENODEV if the SMD channel is not open, -ETIMEDOUT if the destination
- * subsystem does not respond, and -ENOSYS if the destination subsystem
- * responds, but with something other than an acknowledgement.
- *
- * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
- */
 #ifdef CONFIG_MSM_SYSMON_COMM
 int sysmon_send_event(enum subsys_id dest_ss, const char *event_ss,
 		      enum subsys_notif_type notif);
+int sysmon_get_reason(enum subsys_id dest_ss, char *buf, size_t len);
 #else
 static inline int sysmon_send_event(enum subsys_id dest_ss,
 				    const char *event_ss,
@@ -57,6 +45,11 @@
 {
 	return 0;
 }
+static inline int sysmon_get_reason(enum subsys_id dest_ss, char *buf,
+				    size_t len)
+{
+	return 0;
+}
 #endif
 
 #endif