diff options
author | David Keitel <dkeitel@codeaurora.org> | 2016-03-15 22:24:38 -0700 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-23 21:24:23 -0700 |
commit | 4d12cd6ee18175a8436f331f01656f2fb53c3919 (patch) | |
tree | e5bb7b3cec7dc4c0e0394c18ed10f67fc01f25b5 /drivers/soc | |
parent | 48d195bfd6602746bb70c622cda396f0179b92bc (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>
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/qcom/Kconfig | 9 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/sysmon-glink.c | 480 |
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); |