| // 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 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]; |
| }; |
| |
| 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; |
| 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->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: |
| 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); |