summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/sysmon-qmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/qcom/sysmon-qmi.c')
-rw-r--r--drivers/soc/qcom/sysmon-qmi.c732
1 files changed, 732 insertions, 0 deletions
diff --git a/drivers/soc/qcom/sysmon-qmi.c b/drivers/soc/qcom/sysmon-qmi.c
new file mode 100644
index 000000000000..7ef69b527ef8
--- /dev/null
+++ b/drivers/soc/qcom/sysmon-qmi.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2014-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) "sysmon-qmi: %s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <soc/qcom/subsystem_restart.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/msm_qmi_interface.h>
+#include <soc/qcom/sysmon.h>
+
+#define QMI_RESP_BIT_SHIFT(x) (x << 16)
+
+#define QMI_SSCTL_RESTART_REQ_V02 0x0020
+#define QMI_SSCTL_RESTART_RESP_V02 0x0020
+#define QMI_SSCTL_RESTART_READY_IND_V02 0x0020
+#define QMI_SSCTL_SHUTDOWN_REQ_V02 0x0021
+#define QMI_SSCTL_SHUTDOWN_RESP_V02 0x0021
+#define QMI_SSCTL_SHUTDOWN_READY_IND_V02 0x0021
+#define QMI_SSCTL_GET_FAILURE_REASON_REQ_V02 0x0022
+#define QMI_SSCTL_GET_FAILURE_REASON_RESP_V02 0x0022
+#define QMI_SSCTL_SUBSYS_EVENT_REQ_V02 0x0023
+#define QMI_SSCTL_SUBSYS_EVENT_RESP_V02 0x0023
+#define QMI_SSCTL_SUBSYS_EVENT_READY_IND_V02 0x0023
+
+#define QMI_SSCTL_ERROR_MSG_LENGTH 90
+#define QMI_SSCTL_SUBSYS_NAME_LENGTH 15
+#define QMI_SSCTL_SUBSYS_EVENT_REQ_LENGTH 40
+#define QMI_SSCTL_RESP_MSG_LENGTH 7
+#define QMI_SSCTL_EMPTY_MSG_LENGTH 0
+
+#define SSCTL_SERVICE_ID 0x2B
+#define SSCTL_VER_2 2
+#define SERVER_TIMEOUT 500
+#define SHUTDOWN_TIMEOUT 10000
+
+#define QMI_EOTI_DATA_TYPE \
+{ \
+ .data_type = QMI_EOTI, \
+ .elem_len = 0, \
+ .elem_size = 0, \
+ .is_array = NO_ARRAY, \
+ .tlv_type = 0x00, \
+ .offset = 0, \
+ .ei_array = NULL, \
+},
+
+struct sysmon_qmi_data {
+ const char *name;
+ int instance_id;
+ struct work_struct svc_arrive;
+ struct work_struct svc_exit;
+ struct work_struct svc_rcv_msg;
+ struct qmi_handle *clnt_handle;
+ struct notifier_block notifier;
+ void *notif_handle;
+ bool legacy_version;
+ struct completion server_connect;
+ struct completion ind_recv;
+ struct list_head list;
+};
+
+static struct workqueue_struct *sysmon_wq;
+
+static LIST_HEAD(sysmon_list);
+static DEFINE_MUTEX(sysmon_list_lock);
+static DEFINE_MUTEX(sysmon_lock);
+
+static void sysmon_clnt_recv_msg(struct work_struct *work);
+static void sysmon_clnt_svc_arrive(struct work_struct *work);
+static void sysmon_clnt_svc_exit(struct work_struct *work);
+
+static const int notif_map[SUBSYS_NOTIF_TYPE_COUNT] = {
+ [SUBSYS_BEFORE_POWERUP] = SSCTL_SSR_EVENT_BEFORE_POWERUP,
+ [SUBSYS_AFTER_POWERUP] = SSCTL_SSR_EVENT_AFTER_POWERUP,
+ [SUBSYS_BEFORE_SHUTDOWN] = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN,
+ [SUBSYS_AFTER_SHUTDOWN] = SSCTL_SSR_EVENT_AFTER_SHUTDOWN,
+};
+
+static void sysmon_ind_cb(struct qmi_handle *handle, unsigned int msg_id,
+ void *msg, unsigned int msg_len, void *ind_cb_priv)
+{
+ struct sysmon_qmi_data *data = NULL, *temp;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(temp, &sysmon_list, list)
+ if (!strcmp(temp->name, (char *)ind_cb_priv))
+ data = temp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (!data)
+ return;
+
+ pr_debug("%s: Indication received from subsystem\n", data->name);
+ complete(&data->ind_recv);
+}
+
+static int sysmon_svc_event_notify(struct notifier_block *this,
+ unsigned long code,
+ void *_cmd)
+{
+ struct sysmon_qmi_data *data = container_of(this,
+ struct sysmon_qmi_data, notifier);
+
+ switch (code) {
+ case QMI_SERVER_ARRIVE:
+ queue_work(sysmon_wq, &data->svc_arrive);
+ break;
+ case QMI_SERVER_EXIT:
+ queue_work(sysmon_wq, &data->svc_exit);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void sysmon_clnt_notify(struct qmi_handle *handle,
+ enum qmi_event_type event, void *notify_priv)
+{
+ struct sysmon_qmi_data *data = container_of(notify_priv,
+ struct sysmon_qmi_data, svc_arrive);
+
+ switch (event) {
+ case QMI_RECV_MSG:
+ schedule_work(&data->svc_rcv_msg);
+ break;
+ default:
+ break;
+ }
+}
+
+static void sysmon_clnt_svc_arrive(struct work_struct *work)
+{
+ int rc;
+ struct sysmon_qmi_data *data = container_of(work,
+ struct sysmon_qmi_data, svc_arrive);
+
+ /* Create a Local client port for QMI communication */
+ data->clnt_handle = qmi_handle_create(sysmon_clnt_notify, work);
+ if (!data->clnt_handle) {
+ pr_err("QMI client handle alloc failed for %s\n", data->name);
+ return;
+ }
+
+ rc = qmi_connect_to_service(data->clnt_handle, SSCTL_SERVICE_ID,
+ SSCTL_VER_2, data->instance_id);
+ if (rc < 0) {
+ pr_err("%s: Could not connect handle to service\n",
+ data->name);
+ qmi_handle_destroy(data->clnt_handle);
+ data->clnt_handle = NULL;
+ return;
+ }
+ pr_info("Connection established between QMI handle and %s's SSCTL service\n"
+ , data->name);
+
+ rc = qmi_register_ind_cb(data->clnt_handle, sysmon_ind_cb,
+ (void *)data->name);
+ if (rc < 0)
+ pr_warn("%s: Could not register the indication callback\n",
+ data->name);
+}
+
+static void sysmon_clnt_svc_exit(struct work_struct *work)
+{
+ struct sysmon_qmi_data *data = container_of(work,
+ struct sysmon_qmi_data, svc_exit);
+
+ qmi_handle_destroy(data->clnt_handle);
+ data->clnt_handle = NULL;
+}
+
+static void sysmon_clnt_recv_msg(struct work_struct *work)
+{
+ int ret;
+ struct sysmon_qmi_data *data = container_of(work,
+ struct sysmon_qmi_data, svc_rcv_msg);
+
+ do {
+ pr_debug("%s: Notified about a Receive event\n", data->name);
+ } while ((ret = qmi_recv_msg(data->clnt_handle)) == 0);
+
+ if (ret != -ENOMSG)
+ pr_err("%s: Error receiving message\n", data->name);
+}
+
+struct qmi_ssctl_subsys_event_req_msg {
+ uint8_t subsys_name_len;
+ char subsys_name[QMI_SSCTL_SUBSYS_NAME_LENGTH];
+ enum ssctl_ssr_event_enum_type event;
+ uint8_t evt_driven_valid;
+ enum ssctl_ssr_event_driven_enum_type evt_driven;
+};
+
+struct qmi_ssctl_subsys_event_resp_msg {
+ struct qmi_response_type_v01 resp;
+};
+
+static struct elem_info qmi_ssctl_subsys_event_req_msg_ei[] = {
+ {
+ .data_type = QMI_DATA_LEN,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x01,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg,
+ subsys_name_len),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_UNSIGNED_1_BYTE,
+ .elem_len = QMI_SSCTL_SUBSYS_NAME_LENGTH,
+ .elem_size = sizeof(char),
+ .is_array = VAR_LEN_ARRAY,
+ .tlv_type = 0x01,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg,
+ subsys_name),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_SIGNED_4_BYTE_ENUM,
+ .elem_len = 1,
+ .elem_size = sizeof(uint32_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg,
+ event),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_OPT_FLAG,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg,
+ evt_driven_valid),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_SIGNED_4_BYTE_ENUM,
+ .elem_len = 1,
+ .elem_size = sizeof(uint32_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg,
+ evt_driven),
+ .ei_array = NULL,
+ },
+ QMI_EOTI_DATA_TYPE
+};
+
+static struct elem_info qmi_ssctl_subsys_event_resp_msg_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct qmi_ssctl_subsys_event_resp_msg,
+ resp),
+ .ei_array = get_qmi_response_type_v01_ei(),
+ },
+ QMI_EOTI_DATA_TYPE
+};
+
+/**
+ * sysmon_send_event() - 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)
+ *
+ * Reverts to using legacy sysmon API (sysmon_send_event_no_qmi()) if
+ * client handle is not set.
+ *
+ * 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(struct subsys_desc *dest_desc,
+ struct subsys_desc *event_desc,
+ enum subsys_notif_type notif)
+{
+ struct qmi_ssctl_subsys_event_req_msg req;
+ struct msg_desc req_desc, resp_desc;
+ struct qmi_ssctl_subsys_event_resp_msg resp = { { 0, 0 } };
+ struct sysmon_qmi_data *data = NULL, *temp;
+ const char *event_ss = event_desc->name;
+ const char *dest_ss = dest_desc->name;
+ int ret;
+
+ if (notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT || event_ss == NULL
+ || dest_ss == NULL)
+ return -EINVAL;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(temp, &sysmon_list, list)
+ if (!strcmp(temp->name, dest_desc->name))
+ data = temp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (!data)
+ return -EINVAL;
+
+ if (!data->clnt_handle) {
+ pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n",
+ dest_ss);
+ ret = sysmon_send_event_no_qmi(dest_desc, event_desc, notif);
+ if (ret)
+ pr_debug("SSCTL_V0 implementation failed - %d\n", ret);
+
+ return ret;
+ }
+
+ snprintf(req.subsys_name, ARRAY_SIZE(req.subsys_name), "%s", event_ss);
+ req.subsys_name_len = strlen(req.subsys_name);
+ req.event = notif_map[notif];
+ req.evt_driven_valid = 1;
+ req.evt_driven = SSCTL_SSR_EVENT_FORCED;
+
+ req_desc.msg_id = QMI_SSCTL_SUBSYS_EVENT_REQ_V02;
+ req_desc.max_msg_len = QMI_SSCTL_SUBSYS_EVENT_REQ_LENGTH;
+ req_desc.ei_array = qmi_ssctl_subsys_event_req_msg_ei;
+
+ resp_desc.msg_id = QMI_SSCTL_SUBSYS_EVENT_RESP_V02;
+ resp_desc.max_msg_len = QMI_SSCTL_RESP_MSG_LENGTH;
+ resp_desc.ei_array = qmi_ssctl_subsys_event_resp_msg_ei;
+
+ mutex_lock(&sysmon_lock);
+ ret = qmi_send_req_wait(data->clnt_handle, &req_desc, &req,
+ sizeof(req), &resp_desc, &resp, sizeof(resp), SERVER_TIMEOUT);
+ if (ret < 0) {
+ pr_err("QMI send req to %s failed, ret - %d\n", dest_ss, ret);
+ goto out;
+ }
+
+ /* Check the response */
+ if (QMI_RESP_BIT_SHIFT(resp.resp.result) != QMI_RESULT_SUCCESS_V01) {
+ pr_debug("QMI request failed 0x%x\n",
+ QMI_RESP_BIT_SHIFT(resp.resp.error));
+ ret = -EREMOTEIO;
+ }
+out:
+ mutex_unlock(&sysmon_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_event);
+
+struct qmi_ssctl_shutdown_req_msg {
+};
+
+struct qmi_ssctl_shutdown_resp_msg {
+ struct qmi_response_type_v01 resp;
+};
+
+static struct elem_info qmi_ssctl_shutdown_req_msg_ei[] = {
+ QMI_EOTI_DATA_TYPE
+};
+
+static struct elem_info qmi_ssctl_shutdown_resp_msg_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct qmi_ssctl_shutdown_resp_msg,
+ resp),
+ .ei_array = get_qmi_response_type_v01_ei(),
+ },
+ QMI_EOTI_DATA_TYPE
+};
+
+/**
+ * sysmon_send_shutdown() - send shutdown command to a
+ * subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to send to
+ *
+ * Reverts to using legacy sysmon API (sysmon_send_shutdown_no_qmi()) if
+ * client handle is not set.
+ *
+ * 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_send_shutdown(struct subsys_desc *dest_desc)
+{
+ struct msg_desc req_desc, resp_desc;
+ struct qmi_ssctl_shutdown_resp_msg resp = { { 0, 0 } };
+ struct sysmon_qmi_data *data = NULL, *temp;
+ const char *dest_ss = dest_desc->name;
+ char req = 0;
+ int ret, shutdown_ack_ret;
+
+ if (dest_ss == NULL)
+ return -EINVAL;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(temp, &sysmon_list, list)
+ if (!strcmp(temp->name, dest_desc->name))
+ data = temp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (!data)
+ return -EINVAL;
+
+ if (!data->clnt_handle) {
+ pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n",
+ dest_ss);
+ ret = sysmon_send_shutdown_no_qmi(dest_desc);
+ if (ret)
+ pr_debug("SSCTL_V0 implementation failed - %d\n", ret);
+
+ return ret;
+ }
+
+ req_desc.msg_id = QMI_SSCTL_SHUTDOWN_REQ_V02;
+ req_desc.max_msg_len = QMI_SSCTL_EMPTY_MSG_LENGTH;
+ req_desc.ei_array = qmi_ssctl_shutdown_req_msg_ei;
+
+ resp_desc.msg_id = QMI_SSCTL_SHUTDOWN_RESP_V02;
+ resp_desc.max_msg_len = QMI_SSCTL_RESP_MSG_LENGTH;
+ resp_desc.ei_array = qmi_ssctl_shutdown_resp_msg_ei;
+
+ reinit_completion(&data->ind_recv);
+ mutex_lock(&sysmon_lock);
+ ret = qmi_send_req_wait(data->clnt_handle, &req_desc, &req,
+ sizeof(req), &resp_desc, &resp, sizeof(resp), SERVER_TIMEOUT);
+ if (ret < 0) {
+ pr_err("QMI send req to %s failed, ret - %d\n", dest_ss, ret);
+ goto out;
+ }
+
+ /* Check the response */
+ if (QMI_RESP_BIT_SHIFT(resp.resp.result) != QMI_RESULT_SUCCESS_V01) {
+ pr_err("QMI request failed 0x%x\n",
+ QMI_RESP_BIT_SHIFT(resp.resp.error));
+ ret = -EREMOTEIO;
+ goto out;
+ }
+
+ shutdown_ack_ret = wait_for_shutdown_ack(dest_desc);
+ if (shutdown_ack_ret < 0) {
+ pr_err("shutdown_ack SMP2P bit for %s not set\n", data->name);
+ if (!&data->ind_recv.done) {
+ pr_err("QMI shutdown indication not received\n");
+ ret = shutdown_ack_ret;
+ }
+ goto out;
+ } else if (shutdown_ack_ret > 0)
+ goto out;
+
+ if (!wait_for_completion_timeout(&data->ind_recv,
+ msecs_to_jiffies(SHUTDOWN_TIMEOUT))) {
+ pr_err("Timed out waiting for shutdown indication from %s\n",
+ data->name);
+ ret = -ETIMEDOUT;
+ }
+out:
+ mutex_unlock(&sysmon_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_shutdown);
+
+struct qmi_ssctl_get_failure_reason_req_msg {
+};
+
+struct qmi_ssctl_get_failure_reason_resp_msg {
+ struct qmi_response_type_v01 resp;
+ uint8_t error_message_valid;
+ uint32_t error_message_len;
+ char error_message[QMI_SSCTL_ERROR_MSG_LENGTH];
+};
+
+static struct elem_info qmi_ssctl_get_failure_reason_req_msg_ei[] = {
+ QMI_EOTI_DATA_TYPE
+};
+
+static struct elem_info qmi_ssctl_get_failure_reason_resp_msg_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(
+ struct qmi_ssctl_get_failure_reason_resp_msg,
+ resp),
+ .ei_array = get_qmi_response_type_v01_ei(),
+ },
+ {
+ .data_type = QMI_OPT_FLAG,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(
+ struct qmi_ssctl_get_failure_reason_resp_msg,
+ error_message_valid),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_DATA_LEN,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(
+ struct qmi_ssctl_get_failure_reason_resp_msg,
+ error_message_len),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_UNSIGNED_1_BYTE,
+ .elem_len = QMI_SSCTL_ERROR_MSG_LENGTH,
+ .elem_size = sizeof(char),
+ .is_array = VAR_LEN_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(
+ struct qmi_ssctl_get_failure_reason_resp_msg,
+ error_message),
+ .ei_array = NULL,
+ },
+ QMI_EOTI_DATA_TYPE
+};
+
+/**
+ * sysmon_get_reason() - Retrieve failure reason from a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to query
+ * @buf: Caller-allocated buffer for the returned NUL-terminated reason
+ * @len: Length of @buf
+ *
+ * Reverts to using legacy sysmon API (sysmon_get_reason_no_qmi()) if client
+ * handle is not set.
+ *
+ * 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(struct subsys_desc *dest_desc, char *buf, size_t len)
+{
+ struct msg_desc req_desc, resp_desc;
+ struct qmi_ssctl_get_failure_reason_resp_msg resp;
+ struct sysmon_qmi_data *data = NULL, *temp;
+ const char *dest_ss = dest_desc->name;
+ const char expect[] = "ssr:return:";
+ char req = 0;
+ int ret;
+
+ if (dest_ss == NULL || buf == NULL || len == 0)
+ return -EINVAL;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(temp, &sysmon_list, list)
+ if (!strcmp(temp->name, dest_desc->name))
+ data = temp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (!data)
+ return -EINVAL;
+
+ if (!data->clnt_handle) {
+ pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n",
+ dest_ss);
+ ret = sysmon_get_reason_no_qmi(dest_desc, buf, len);
+ if (ret)
+ pr_debug("SSCTL_V0 implementation failed - %d\n", ret);
+
+ return ret;
+ }
+
+ req_desc.msg_id = QMI_SSCTL_GET_FAILURE_REASON_REQ_V02;
+ req_desc.max_msg_len = QMI_SSCTL_EMPTY_MSG_LENGTH;
+ req_desc.ei_array = qmi_ssctl_get_failure_reason_req_msg_ei;
+
+ resp_desc.msg_id = QMI_SSCTL_GET_FAILURE_REASON_RESP_V02;
+ resp_desc.max_msg_len = QMI_SSCTL_ERROR_MSG_LENGTH;
+ resp_desc.ei_array = qmi_ssctl_get_failure_reason_resp_msg_ei;
+
+ mutex_lock(&sysmon_lock);
+ ret = qmi_send_req_wait(data->clnt_handle, &req_desc, &req,
+ sizeof(req), &resp_desc, &resp, sizeof(resp), SERVER_TIMEOUT);
+ if (ret < 0) {
+ pr_err("QMI send req to %s failed, ret - %d\n", dest_ss, ret);
+ goto out;
+ }
+
+ /* Check the response */
+ if (QMI_RESP_BIT_SHIFT(resp.resp.result) != QMI_RESULT_SUCCESS_V01) {
+ pr_err("QMI request failed 0x%x\n",
+ QMI_RESP_BIT_SHIFT(resp.resp.error));
+ ret = -EREMOTEIO;
+ goto out;
+ }
+
+ if (!strcmp(resp.error_message, expect)) {
+ pr_err("Unexpected response %s\n", resp.error_message);
+ ret = -ENOSYS;
+ goto out;
+ }
+ strlcpy(buf, resp.error_message, resp.error_message_len);
+out:
+ mutex_unlock(&sysmon_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_get_reason);
+
+/**
+ * sysmon_notifier_register() - Initialize sysmon data for a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem
+ *
+ * Returns 0 for success. If the subsystem does not support SSCTL v2, a
+ * value of 0 is returned after adding the subsystem entry to the sysmon_list.
+ * In addition, if the SSCTL v2 support exists, the notifier block to receive
+ * events from the SSCTL service on the subsystem is registered.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_notifier_register(struct subsys_desc *desc)
+{
+ struct sysmon_qmi_data *data;
+ int rc = 0;
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->name = desc->name;
+ data->instance_id = desc->ssctl_instance_id;
+ data->clnt_handle = NULL;
+ data->legacy_version = false;
+
+ mutex_lock(&sysmon_list_lock);
+ if (data->instance_id <= 0) {
+ pr_debug("SSCTL instance id not defined\n");
+ goto add_list;
+ }
+
+ if (sysmon_wq)
+ goto notif_register;
+
+ sysmon_wq = create_singlethread_workqueue("sysmon_wq");
+ if (!sysmon_wq) {
+ mutex_unlock(&sysmon_list_lock);
+ pr_err("Could not create workqueue\n");
+ kfree(data);
+ return -ENOMEM;
+ }
+
+notif_register:
+ data->notifier.notifier_call = sysmon_svc_event_notify;
+ init_completion(&data->ind_recv);
+
+ INIT_WORK(&data->svc_arrive, sysmon_clnt_svc_arrive);
+ INIT_WORK(&data->svc_exit, sysmon_clnt_svc_exit);
+ INIT_WORK(&data->svc_rcv_msg, sysmon_clnt_recv_msg);
+
+ rc = qmi_svc_event_notifier_register(SSCTL_SERVICE_ID, SSCTL_VER_2,
+ data->instance_id, &data->notifier);
+ if (rc < 0)
+ pr_err("Notifier register failed for %s\n", data->name);
+add_list:
+ INIT_LIST_HEAD(&data->list);
+ list_add_tail(&data->list, &sysmon_list);
+ mutex_unlock(&sysmon_list_lock);
+
+ return rc;
+}
+EXPORT_SYMBOL(sysmon_notifier_register);
+
+/**
+ * sysmon_notifier_unregister() - Cleanup the subsystem's sysmon data.
+ * @dest_desc: Subsystem descriptor of the subsystem
+ *
+ * If the subsystem does not support SSCTL v2, its entry is simply removed from
+ * the sysmon_list. In addition, if the SSCTL v2 support exists, the notifier
+ * block to receive events from the SSCTL service is unregistered.
+ */
+void sysmon_notifier_unregister(struct subsys_desc *desc)
+{
+ struct sysmon_qmi_data *data = NULL, *sysmon_data, *tmp;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry_safe(sysmon_data, tmp, &sysmon_list, list)
+ if (!strcmp(sysmon_data->name, desc->name)) {
+ data = sysmon_data;
+ list_del(&data->list);
+ }
+
+ if (data == NULL)
+ goto exit;
+
+ if (data->instance_id > 0)
+ qmi_svc_event_notifier_unregister(SSCTL_SERVICE_ID,
+ SSCTL_VER_2, data->instance_id, &data->notifier);
+
+ if (sysmon_wq && list_empty(&sysmon_list))
+ destroy_workqueue(sysmon_wq);
+exit:
+ mutex_unlock(&sysmon_list_lock);
+ kfree(data);
+}
+EXPORT_SYMBOL(sysmon_notifier_unregister);