blob: f4c7779f286d9cfdb86d9859e1caf3262bfa6689 [file] [log] [blame]
/*
* Copyright (c) 2014-2017, 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);
mutex_lock(&sysmon_lock);
/* 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);
mutex_unlock(&sysmon_lock);
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;
mutex_unlock(&sysmon_lock);
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);
mutex_unlock(&sysmon_lock);
}
static void sysmon_clnt_svc_exit(struct work_struct *work)
{
struct sysmon_qmi_data *data = container_of(work,
struct sysmon_qmi_data, svc_exit);
mutex_lock(&sysmon_lock);
qmi_handle_destroy(data->clnt_handle);
data->clnt_handle = NULL;
mutex_unlock(&sysmon_lock);
}
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 -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 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 -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 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 -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 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 = -EPROTO;
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);