android_kernel_xiaomi_sm8350/drivers/soc/qcom/sysmon-qmi.c
Raghavendra Rao Ananta 4fcd37a470 soc: qcom: Add snapshot of SSR, PIL, Sysmon, and PDR drivers
Add snapshot for Subsystem Restart (SSR), Peripheral Image
Loader framework (PIL), Secure PIL driver, System Monitor
(sysmon), and Protection Domain Restart (PDR) drivers from
msm-4.19 as of commit <61502d56cb5f9b1> (Merge "drivers:
llcc_perfmon: support for dual memory controller added").

Change-Id: Id06c338121486705dfd716567244650b4dd1742c
[rananta@codeaurora.org: Make changes to make the drivers modular]
Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
2019-08-14 12:52:37 -07:00

720 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.
*/
#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 <linux/soc/qcom/qmi.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_GET_FAILURE_REASON_IND_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, \
.array_type = NO_ARRAY, \
.tlv_type = 0x00, \
.offset = 0, \
.ei_array = NULL, \
},
struct sysmon_qmi_data {
const char *name;
int instance_id;
bool connected;
struct subsys_desc *desc;
struct qmi_handle clnt_handle;
struct notifier_block notifier;
void *notif_handle;
bool legacy_version;
struct sockaddr_qrtr ssctl;
struct list_head list;
};
static LIST_HEAD(sysmon_list);
static DEFINE_MUTEX(sysmon_list_lock);
static const int notif_map[SUBSYS_NOTIF_TYPE_COUNT] = {
[0 ... SUBSYS_NOTIF_TYPE_COUNT - 1] = SSCTL_SSR_EVENT_INVALID,
[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,
};
struct qmi_ssctl_shutdown_indication {
};
static struct qmi_elem_info qmi_ssctl_indication_ei[] = {
QMI_EOTI_DATA_TYPE
};
static void sysmon_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *data)
{
struct sysmon_qmi_data *qmi_data = container_of(qmi,
struct sysmon_qmi_data, clnt_handle);
struct subsys_desc *desc = qmi_data->desc;
pr_info("%s: Indication received from subsystem\n", qmi_data->name);
if (desc)
complete_shutdown_ack(desc);
else
pr_err("Failed to find subsystem: %s for indication\n",
qmi_data->name);
}
static struct qmi_msg_handler qmi_indication_handler[] = {
{
.type = QMI_INDICATION,
.msg_id = QMI_SSCTL_SHUTDOWN_READY_IND_V02,
.ei = qmi_ssctl_indication_ei,
.decoded_size = 0,
.fn = sysmon_ind_cb
},
{}
};
static bool is_ssctl_event(enum subsys_notif_type notif)
{
return notif_map[notif] != SSCTL_SSR_EVENT_INVALID;
}
static int ssctl_new_server(struct qmi_handle *qmi, struct qmi_service *svc)
{
struct sysmon_qmi_data *data = container_of(qmi,
struct sysmon_qmi_data, clnt_handle);
pr_info("Connection established between QMI handle and %s's SSCTL service\n"
, data->name);
data->ssctl.sq_family = AF_QIPCRTR;
data->ssctl.sq_node = svc->node;
data->ssctl.sq_port = svc->port;
data->connected = true;
return 0;
}
static void ssctl_del_server(struct qmi_handle *qmi, struct qmi_service *svc)
{
struct sysmon_qmi_data *data = container_of(qmi,
struct sysmon_qmi_data, clnt_handle);
pr_info("Connection lost between QMI handle and %s's SSCTL service\n"
, data->name);
data->connected = false;
}
static struct qmi_ops ssctl_ops = {
.new_server = ssctl_new_server,
.del_server = ssctl_del_server,
};
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 qmi_elem_info qmi_ssctl_subsys_event_req_msg_ei[] = {
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = 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),
.array_type = 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),
.array_type = 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),
.array_type = 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),
.array_type = 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 qmi_elem_info qmi_ssctl_subsys_event_resp_msg_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_ssctl_subsys_event_resp_msg,
resp),
.ei_array = 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 -EPROTO if the destination subsystem
* responds, but with something other than an acknowledgment.
*
* 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 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;
struct qmi_txn txn;
if (notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT ||
!is_ssctl_event(notif) || 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->instance_id < 0) {
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;
}
if (!data->connected)
return -EAGAIN;
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;
ret = qmi_txn_init(&data->clnt_handle, &txn,
qmi_ssctl_subsys_event_resp_msg_ei,
&resp);
if (ret < 0) {
pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n",
dest_ss, ret);
goto out;
}
ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn,
QMI_SSCTL_SUBSYS_EVENT_REQ_V02,
QMI_SSCTL_SUBSYS_EVENT_REQ_LENGTH,
qmi_ssctl_subsys_event_req_msg_ei,
&req);
if (ret < 0) {
pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n",
dest_ss, ret);
qmi_txn_cancel(&txn);
goto out;
}
ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
if (ret < 0) {
pr_err("SYSMON QMI qmi txn wait failed for client %s, 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("SYSMON QMI request failed 0x%x\n",
QMI_RESP_BIT_SHIFT(resp.resp.error));
ret = -EREMOTEIO;
}
out:
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 qmi_elem_info qmi_ssctl_shutdown_req_msg_ei[] = {
QMI_EOTI_DATA_TYPE
};
static struct qmi_elem_info qmi_ssctl_shutdown_resp_msg_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_ssctl_shutdown_resp_msg,
resp),
.ei_array = qmi_response_type_v01_ei,
},
QMI_EOTI_DATA_TYPE
};
static inline int wait_for_shutdown_ack(struct subsys_desc *desc)
{
int ret;
if (!desc)
return 0;
ret = wait_for_completion_timeout(&desc->shutdown_ack,
msecs_to_jiffies(10000));
if (!ret) {
pr_err("[%s]: Timed out waiting for shutdown ack\n",
desc->name);
return -ETIMEDOUT;
}
return ret;
}
/**
* 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 -EPROTO 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 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;
struct qmi_txn txn;
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->instance_id < 0) {
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;
}
if (!data->connected)
return -EAGAIN;
reinit_completion(&dest_desc->shutdown_ack);
ret = qmi_txn_init(&data->clnt_handle, &txn,
qmi_ssctl_shutdown_resp_msg_ei,
&resp);
if (ret < 0) {
pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n",
dest_ss, ret);
goto out;
}
ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn,
QMI_SSCTL_SHUTDOWN_REQ_V02,
QMI_SSCTL_EMPTY_MSG_LENGTH,
qmi_ssctl_shutdown_req_msg_ei,
&req);
if (ret < 0) {
pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n",
dest_ss, ret);
qmi_txn_cancel(&txn);
goto out;
}
ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
if (ret < 0) {
pr_err("SYSMON QMI txn wait failed to dest %s, ret - %d\n",
dest_ss, ret);
}
/* Check the response */
if (ret != -ETIMEDOUT && QMI_RESP_BIT_SHIFT(resp.resp.result) !=
QMI_RESULT_SUCCESS_V01) {
pr_err("SYSMON 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) {
ret = 0;
goto out;
} else if (shutdown_ack_ret < 0) {
pr_err("shutdown acknowledgment not received for %s\n",
data->name);
ret = shutdown_ack_ret;
}
out:
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 qmi_elem_info qmi_ssctl_get_failure_reason_req_msg_ei[] = {
QMI_EOTI_DATA_TYPE
};
static struct qmi_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),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(
struct qmi_ssctl_get_failure_reason_resp_msg,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = 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),
.array_type = 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),
.array_type = 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 -EPROTO 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 qmi_ssctl_get_failure_reason_resp_msg resp;
struct sysmon_qmi_data *data = NULL, *temp;
struct qmi_txn txn;
const char *dest_ss = dest_desc->name;
static 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->instance_id < 0) {
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;
}
if (!data->connected)
return -EAGAIN;
ret = qmi_txn_init(&data->clnt_handle, &txn,
qmi_ssctl_get_failure_reason_resp_msg_ei,
&resp);
if (ret < 0) {
pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n",
dest_ss, ret);
goto out;
}
ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn,
QMI_SSCTL_GET_FAILURE_REASON_REQ_V02,
QMI_SSCTL_EMPTY_MSG_LENGTH,
qmi_ssctl_get_failure_reason_req_msg_ei,
&req);
if (ret < 0) {
pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n",
dest_ss, ret);
qmi_txn_cancel(&txn);
goto out;
}
ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
if (ret < 0) {
pr_err("SYSMON QMI qmi txn wait failed to dest %s, 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("SYSMON 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 = -EPROTO;
goto out;
}
strlcpy(buf, resp.error_message, resp.error_message_len);
out:
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->desc = desc;
data->name = desc->name;
data->instance_id = desc->ssctl_instance_id;
data->legacy_version = false;
data->connected = false;
if (data->instance_id <= 0) {
pr_debug("SSCTL instance id not defined\n");
goto add_list;
}
rc = qmi_handle_init(&data->clnt_handle,
QMI_SSCTL_RESP_MSG_LENGTH, &ssctl_ops,
qmi_indication_handler);
if (rc < 0) {
pr_err("Sysmon QMI handle init failed rc:%d\n", rc);
kfree(data);
return rc;
}
qmi_add_lookup(&data->clnt_handle, SSCTL_SERVICE_ID,
SSCTL_VER_2, data->instance_id);
add_list:
init_completion(&desc->shutdown_ack);
mutex_lock(&sysmon_list_lock);
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);
}
mutex_unlock(&sysmon_list_lock);
if (data == NULL)
return;
if (data->instance_id > 0)
qmi_handle_release(&data->clnt_handle);
kfree(data);
}
EXPORT_SYMBOL(sysmon_notifier_unregister);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. System Monitor QMI driver");