summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Keitel <dkeitel@codeaurora.org>2016-03-15 22:24:38 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:24:23 -0700
commit4d12cd6ee18175a8436f331f01656f2fb53c3919 (patch)
treee5bb7b3cec7dc4c0e0394c18ed10f67fc01f25b5
parent48d195bfd6602746bb70c622cda396f0179b92bc (diff)
soc: qcom: add snapshot of sysmon-glink driver
This is a snapshot of the sysmon glink library as of msm-3.18 commit d5809484 (Merge "msm: ipa: fix race condition when teardown pipe") Signed-off-by: David Keitel <dkeitel@codeaurora.org>
-rw-r--r--drivers/soc/qcom/Kconfig9
-rw-r--r--drivers/soc/qcom/Makefile2
-rw-r--r--drivers/soc/qcom/sysmon-glink.c480
3 files changed, 490 insertions, 1 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 0d48c9f736bd..ffb16c9714a9 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -229,6 +229,15 @@ config MSM_IPC_ROUTER_SMD_XPRT
this layer registers a transport with IPC Router and enable
message exchange.
+config MSM_SYSMON_GLINK_COMM
+ bool "MSM System Monitor communication support using GLINK transport"
+ depends on MSM_GLINK && MSM_SUBSYSTEM_RESTART
+ help
+ This option adds support for MSM System Monitor APIs using the GLINK
+ transport layer. The APIs provided may be used for notifying
+ subsystems within the SoC about other subsystems' power-up/down
+ state-changes.
+
config MSM_IPC_ROUTER_HSIC_XPRT
depends on USB_QCOM_IPC_BRIDGE
depends on IPC_ROUTER
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 5b81701da99f..1486f8eb3ed8 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -12,7 +12,7 @@ obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o
obj-$(CONFIG_MSM_GLINK_SMD_XPRT) += glink_smd_xprt.o
obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT)+= glink_smem_native_xprt.o
obj-$(CONFIG_MSM_SMEM_LOGGING) += smem_log.o
-obj-$(CONFIG_MSM_SYSMON_GLINK_COMM) += sysmon-glink.o sysmon-qmi.o
+obj-$(CONFIG_MSM_SYSMON_GLINK_COMM) += sysmon-glink.o sysmon-qmi.o
obj-$(CONFIG_ARCH_QCOM) += kryo-l2-accessors.o
obj-$(CONFIG_MSM_SMP2P) += smp2p.o smp2p_debug.o smp2p_sleepstate.o
obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_spinlock_test.o
diff --git a/drivers/soc/qcom/sysmon-glink.c b/drivers/soc/qcom/sysmon-glink.c
new file mode 100644
index 000000000000..b788973ed96a
--- /dev/null
+++ b/drivers/soc/qcom/sysmon-glink.c
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/completion.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+
+#include <soc/qcom/sysmon.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/glink.h>
+
+#define TX_BUF_SIZE 50
+#define RX_BUF_SIZE 500
+#define TIMEOUT_MS 500
+
+/**
+ * struct sysmon_subsys - Sysmon info structure for subsystem
+ * name: subsys_desc name
+ * edge: name of the G-Link edge.
+ * handle: glink_ssr channel used for this subsystem.
+ * rx_buf: Buffer used to store received message.
+ * chan_open: Set when GLINK_CONNECTED. Reset otherwise.
+ * event: Last stored glink state event.
+ * glink_handle: Notifier handle reference.
+ * resp_ready: Completion struct for event response.
+ */
+struct sysmon_subsys {
+ const char *name;
+ const char *edge;
+ void *handle;
+ struct glink_link_info *link_info;
+ char rx_buf[RX_BUF_SIZE];
+ bool chan_open;
+ unsigned event;
+ void *glink_handle;
+ int intent_count;
+ struct completion resp_ready;
+ struct mutex lock;
+ struct workqueue_struct *glink_event_wq;
+ struct work_struct work;
+ struct list_head list;
+};
+
+static const char *notif_name[SUBSYS_NOTIF_TYPE_COUNT] = {
+ [SUBSYS_BEFORE_SHUTDOWN] = "before_shutdown",
+ [SUBSYS_AFTER_SHUTDOWN] = "after_shutdown",
+ [SUBSYS_BEFORE_POWERUP] = "before_powerup",
+ [SUBSYS_AFTER_POWERUP] = "after_powerup",
+};
+
+static LIST_HEAD(sysmon_glink_list);
+static DEFINE_MUTEX(sysmon_glink_list_lock);
+
+static struct sysmon_subsys *_find_subsys(struct subsys_desc *desc)
+{
+ struct sysmon_subsys *ss;
+
+ if (desc == NULL)
+ return NULL;
+
+ mutex_lock(&sysmon_glink_list_lock);
+ list_for_each_entry(ss, &sysmon_glink_list, list) {
+ if (!strcmp(ss->name, desc->name)) {
+ mutex_unlock(&sysmon_glink_list_lock);
+ return ss;
+ }
+ }
+ mutex_unlock(&sysmon_glink_list_lock);
+
+ return NULL;
+}
+
+static int sysmon_send_msg(struct sysmon_subsys *ss, const char *tx_buf,
+ size_t len)
+{
+ int ret;
+ void *handle;
+
+ if (!ss->chan_open)
+ return -ENODEV;
+
+ if (!ss->handle)
+ return -EINVAL;
+
+ init_completion(&ss->resp_ready);
+ handle = ss->handle;
+
+ /* Register an intent to receive data */
+ if (!ss->intent_count) {
+ ret = glink_queue_rx_intent(handle, (void *)ss,
+ sizeof(ss->rx_buf));
+ if (ret) {
+ pr_err("Failed to register receive intent\n");
+ return ret;
+ }
+ ss->intent_count++;
+ }
+
+ pr_debug("Sending sysmon message: %s\n", tx_buf);
+ ret = glink_tx(handle, (void *)ss, (void *)tx_buf, len,
+ GLINK_TX_REQ_INTENT);
+ if (ret) {
+ pr_err("Failed to send sysmon message!\n");
+ return ret;
+ }
+
+ ret = wait_for_completion_timeout(&ss->resp_ready,
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ pr_err("Timed out waiting for response\n");
+ return -ETIMEDOUT;
+ }
+ pr_debug("Received response: %s\n", ss->rx_buf);
+ return ret;
+}
+
+/**
+ * sysmon_send_event_no_qmi() - Notify a subsystem of another's state change
+ * @dest_desc: Subsystem descriptor of the subsystem the notification
+ * should be sent to
+ * @event_desc: Subsystem descriptor 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_GLINK_COMM is not defined, always return success (0).
+ */
+int sysmon_send_event_no_qmi(struct subsys_desc *dest_desc,
+ struct subsys_desc *event_desc,
+ enum subsys_notif_type notif)
+{
+
+ char tx_buf[TX_BUF_SIZE];
+ int ret;
+ struct sysmon_subsys *ss = NULL;
+
+ ss = _find_subsys(dest_desc);
+ if (ss == NULL)
+ return -EINVAL;
+
+ if (event_desc == NULL || notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT
+ || notif_name[notif] == NULL)
+ return -EINVAL;
+
+ snprintf(tx_buf, sizeof(tx_buf), "ssr:%s:%s", event_desc->name,
+ notif_name[notif]);
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
+ if (ret < 0) {
+ mutex_unlock(&ss->lock);
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strcmp(ss->rx_buf, "ssr:ack")) {
+ mutex_unlock(&ss->lock);
+ pr_debug("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ goto out;
+ }
+ mutex_unlock(&ss->lock);
+out:
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_event_no_qmi);
+
+/**
+ * sysmon_send_shutdown_no_qmi() - send shutdown command to a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to send to
+ *
+ * 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_GLINK_COMM is not defined, always return success (0).
+ */
+int sysmon_send_shutdown_no_qmi(struct subsys_desc *dest_desc)
+{
+ struct sysmon_subsys *ss = NULL;
+ const char tx_buf[] = "system:shutdown";
+ const char expect[] = "system:ack";
+ int ret;
+
+ ss = _find_subsys(dest_desc);
+ if (ss == NULL)
+ return -EINVAL;
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, sizeof(tx_buf));
+ if (ret < 0) {
+ mutex_unlock(&ss->lock);
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strcmp(ss->rx_buf, expect)) {
+ mutex_unlock(&ss->lock);
+ pr_err("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ goto out;
+ }
+ mutex_unlock(&ss->lock);
+out:
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_shutdown_no_qmi);
+
+/**
+ * sysmon_get_reason_no_qmi() - Retrieve failure reason from a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to query
+ * @buf: Caller-allocated buffer for the returned NULL-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_GLINK_COMM is not defined, always return success (0).
+ */
+int sysmon_get_reason_no_qmi(struct subsys_desc *dest_desc,
+ char *buf, size_t len)
+{
+ struct sysmon_subsys *ss = NULL;
+ const char tx_buf[] = "ssr:retrieve:sfr";
+ const char expect[] = "ssr:return:";
+ size_t prefix_len = ARRAY_SIZE(expect) - 1;
+ int ret;
+
+ ss = _find_subsys(dest_desc);
+ if (ss == NULL || buf == NULL || len == 0)
+ return -EINVAL;
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, sizeof(tx_buf));
+ if (ret < 0) {
+ mutex_unlock(&ss->lock);
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strncmp(ss->rx_buf, expect, prefix_len)) {
+ mutex_unlock(&ss->lock);
+ pr_err("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ goto out;
+ }
+ strlcpy(buf, ss->rx_buf + prefix_len, len);
+ mutex_unlock(&ss->lock);
+out:
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_get_reason_no_qmi);
+
+static void glink_notify_rx(void *handle, const void *priv,
+ const void *pkt_priv, const void *ptr, size_t size)
+{
+ struct sysmon_subsys *ss = (struct sysmon_subsys *)priv;
+
+ if (!ss) {
+ pr_err("sysmon_subsys mapping failed\n");
+ return;
+ }
+
+ memset(ss->rx_buf, 0, sizeof(ss->rx_buf));
+ ss->intent_count--;
+ if (sizeof(ss->rx_buf) > size)
+ strlcpy(ss->rx_buf, ptr, size);
+ else
+ pr_warn("Invalid recv message size\n");
+ glink_rx_done(ss->handle, ptr, false);
+ complete(&ss->resp_ready);
+}
+
+static void glink_notify_tx_done(void *handle, const void *priv,
+ const void *pkt_priv, const void *ptr)
+{
+ struct sysmon_subsys *cb_data = (struct sysmon_subsys *)priv;
+
+ if (!cb_data)
+ pr_err("sysmon_subsys mapping failed\n");
+ else
+ pr_debug("tx_done notification!\n");
+}
+
+static void glink_notify_state(void *handle, const void *priv, unsigned event)
+{
+ struct sysmon_subsys *ss = (struct sysmon_subsys *)priv;
+
+ if (!ss) {
+ pr_err("sysmon_subsys mapping failed\n");
+ return;
+ }
+
+ mutex_lock(&ss->lock);
+ ss->event = event;
+ switch (event) {
+ case GLINK_CONNECTED:
+ ss->chan_open = true;
+ break;
+ case GLINK_REMOTE_DISCONNECTED:
+ ss->chan_open = false;
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&ss->lock);
+}
+
+static void glink_state_up_work_hdlr(struct work_struct *work)
+{
+ struct glink_open_config open_cfg;
+ struct sysmon_subsys *ss = container_of(work, struct sysmon_subsys,
+ work);
+ void *handle = NULL;
+
+ if (!ss) {
+ pr_err("Invalid sysmon_subsys struct parameter\n");
+ return;
+ }
+
+ memset(&open_cfg, 0, sizeof(struct glink_open_config));
+ open_cfg.priv = (void *)ss;
+ open_cfg.notify_rx = glink_notify_rx;
+ open_cfg.notify_tx_done = glink_notify_tx_done;
+ open_cfg.notify_state = glink_notify_state;
+ open_cfg.edge = ss->edge;
+ open_cfg.transport = "smd_trans";
+ open_cfg.name = "sys_mon";
+
+ handle = glink_open(&open_cfg);
+ if (IS_ERR_OR_NULL(handle)) {
+ pr_err("%s: %s: unable to open channel\n",
+ open_cfg.edge, open_cfg.name);
+ return;
+ }
+ ss->handle = handle;
+}
+
+static void glink_state_down_work_hdlr(struct work_struct *work)
+{
+ struct sysmon_subsys *ss = container_of(work, struct sysmon_subsys,
+ work);
+
+ if (ss->handle)
+ glink_close(ss->handle);
+ ss->handle = NULL;
+}
+
+static void sysmon_glink_cb(struct glink_link_state_cb_info *cb_info,
+ void *priv)
+{
+ struct sysmon_subsys *ss = (struct sysmon_subsys *)priv;
+
+ if (!cb_info || !ss) {
+ pr_err("Invalid parameters\n");
+ return;
+ }
+
+ mutex_lock(&ss->lock);
+ switch (cb_info->link_state) {
+ case GLINK_LINK_STATE_UP:
+ pr_debug("LINK UP %s\n", ss->edge);
+ INIT_WORK(&ss->work, glink_state_up_work_hdlr);
+ queue_work(ss->glink_event_wq, &ss->work);
+ break;
+ case GLINK_LINK_STATE_DOWN:
+ pr_debug("LINK DOWN %s\n", ss->edge);
+ INIT_WORK(&ss->work, glink_state_down_work_hdlr);
+ queue_work(ss->glink_event_wq, &ss->work);
+ break;
+ default:
+ pr_warn("Invalid event notification\n");
+ break;
+ }
+ mutex_unlock(&ss->lock);
+}
+
+int sysmon_glink_register(struct subsys_desc *desc)
+{
+ struct sysmon_subsys *ss;
+ struct glink_link_info *link_info;
+ int ret;
+
+ if (!desc)
+ return -EINVAL;
+
+ ss = kzalloc(sizeof(*ss), GFP_KERNEL);
+ if (!ss)
+ return -ENOMEM;
+
+ link_info = kzalloc(sizeof(struct glink_link_info), GFP_KERNEL);
+ if (!link_info) {
+ pr_err("Could not allocate link info structure\n");
+ kfree(ss);
+ return -ENOMEM;
+ }
+
+ ss->glink_event_wq = create_singlethread_workqueue(desc->name);
+ if (ss->glink_event_wq == NULL) {
+ ret = -ENOMEM;
+ goto err_wq;
+ }
+ mutex_init(&ss->lock);
+
+ ss->name = desc->name;
+ ss->handle = NULL;
+ ss->intent_count = 0;
+ ss->link_info = link_info;
+ ss->link_info->edge = ss->edge = desc->edge;
+ ss->link_info->transport = "smd_trans";
+ ss->link_info->glink_link_state_notif_cb = sysmon_glink_cb;
+
+ ss->glink_handle = glink_register_link_state_cb(ss->link_info,
+ (void *)ss);
+ if (IS_ERR_OR_NULL(ss->glink_handle)) {
+ pr_err("Could not register link state cb\n");
+ ret = PTR_ERR(ss->glink_handle);
+ goto err;
+ }
+
+ mutex_lock(&sysmon_glink_list_lock);
+ INIT_LIST_HEAD(&ss->list);
+ list_add_tail(&ss->list, &sysmon_glink_list);
+ mutex_unlock(&sysmon_glink_list_lock);
+ return 0;
+err:
+ destroy_workqueue(ss->glink_event_wq);
+err_wq:
+ kfree(link_info);
+ kfree(ss);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_glink_register);
+
+void sysmon_glink_unregister(struct subsys_desc *desc)
+{
+ struct sysmon_subsys *ss = NULL;
+
+ if (!desc)
+ return;
+
+ ss = _find_subsys(desc);
+ if (ss == NULL)
+ return;
+
+ list_del(&ss->list);
+ if (ss->handle)
+ glink_close(ss->handle);
+ destroy_workqueue(ss->glink_event_wq);
+ glink_unregister_link_state_cb(ss->glink_handle);
+ kfree(ss->link_info);
+ kfree(ss);
+}
+EXPORT_SYMBOL(sysmon_glink_unregister);