// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014-2020, 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_EXT_ERROR_MSG_LENGTH_V02	256
#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 QMI_SSCTL_MAX_MSG_LENGTH		362

#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 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_device *subsys_dev = find_subsys_device(qmi_data->name);
	pr_info("%s: Indication received from subsystem\n", qmi_data->name);
	if (subsys_dev)
		complete_shutdown_ack(subsys_dev);
	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;
}

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
};

/**
 * 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;

	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];
	uint8_t ext_error_message_valid;
	uint32_t ext_error_message_len;
	char ext_error_message[QMI_SSCTL_EXT_ERROR_MSG_LENGTH_V02];
};

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,
	},
	{
		.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,
						ext_error_message_valid),
		.ei_array  = NULL,
	},
	{
		.data_type = QMI_DATA_LEN,
		.elem_len  = 1,
		.elem_size = sizeof(uint16_t),
		.array_type  = NO_ARRAY,
		.tlv_type  = 0x10,
		.offset    = offsetof(
			struct qmi_ssctl_get_failure_reason_resp_msg,
						ext_error_message_len),
		.ei_array  = NULL,
	},
	{
		.data_type = QMI_UNSIGNED_1_BYTE,
		.elem_len  = QMI_SSCTL_EXT_ERROR_MSG_LENGTH_V02,
		.elem_size = sizeof(char),
		.array_type  = VAR_LEN_ARRAY,
		.tlv_type  = 0x10,
		.offset    = offsetof(
			struct qmi_ssctl_get_failure_reason_resp_msg,
						ext_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 = { { 0 } };
	struct sysmon_qmi_data *data = NULL, *temp;
	struct qmi_txn txn;
	const char *dest_ss = dest_desc->name;
	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;
	}

	strlcpy(buf, resp.error_message, resp.error_message_len);
	return 0;
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->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_MAX_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:
	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);
