/* Copyright (c) 2014, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "system_health_monitor_v01.h" #define MODULE_NAME "system_health_monitor" #define SUBSYS_NAME_LEN 256 #define SSRESTART_STRLEN 256 enum { SHM_INFO_FLAG = 0x1, SHM_DEBUG_FLAG = 0x2, }; static int shm_debug_mask = SHM_INFO_FLAG; module_param_named(debug_mask, shm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); static int shm_default_timeout_ms = 2000; module_param_named(default_timeout_ms, shm_default_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); #define DEFAULT_SHM_RATELIMIT_INTERVAL (HZ / 5) #define DEFAULT_SHM_RATELIMIT_BURST 2 #define SHM_ILCTXT_NUM_PAGES 2 static void *shm_ilctxt; #define SHM_INFO_LOG(x...) do { \ if ((shm_debug_mask & SHM_INFO_FLAG) && shm_ilctxt) \ ipc_log_string(shm_ilctxt, x); \ } while (0) #define SHM_DEBUG(x...) do { \ if ((shm_debug_mask & SHM_DEBUG_FLAG) && shm_ilctxt) \ ipc_log_string(shm_ilctxt, x); \ } while (0) #define SHM_ERR(x...) do { \ if (shm_ilctxt) \ ipc_log_string(shm_ilctxt, x); \ pr_err(x); \ } while (0) struct class *system_health_monitor_classp; static dev_t system_health_monitor_dev; static struct cdev system_health_monitor_cdev; static struct device *system_health_monitor_devp; #define SYSTEM_HEALTH_MONITOR_IOCTL_MAGIC (0xC3) #define CHECK_SYSTEM_HEALTH_IOCTL \ _IOR(SYSTEM_HEALTH_MONITOR_IOCTL_MAGIC, 0, unsigned int) static struct workqueue_struct *shm_svc_workqueue; static void shm_svc_recv_msg(struct work_struct *work); static DECLARE_DELAYED_WORK(work_recv_msg, shm_svc_recv_msg); static struct qmi_handle *shm_svc_handle; struct disconnect_work { struct work_struct work; void *conn_h; }; static void shm_svc_disconnect_worker(struct work_struct *work); struct req_work { struct work_struct work; void *conn_h; void *req_h; unsigned int msg_id; void *req; }; static void shm_svc_req_worker(struct work_struct *work); /** * struct hma_info - Information about a Health Monitor Agent(HMA) * @list: List to chain up the hma to the hma_list. * @subsys_name: Name of the remote subsystem that hosts this HMA. * @ssrestart_string: String to restart the subsystem that hosts this HMA. * @conn_h: Opaque connection handle to the HMA. * @timeout: Timeout as registered by the HMA. * @check_count: Count of the health check attempts. * @report_count: Count of the health reports handled. * @reset_srcu: Sleepable RCU to protect the reset state. * @is_in_reset: Flag to identify if the remote subsystem is in reset. * @restart_nb: Notifier block to receive subsystem restart events. * @restart_nb_h: Handle to subsystem restart notifier block. * @rs: Rate-limit the health check. */ struct hma_info { struct list_head list; char subsys_name[SUBSYS_NAME_LEN]; char ssrestart_string[SSRESTART_STRLEN]; void *conn_h; uint32_t timeout; atomic_t check_count; atomic_t report_count; struct srcu_struct reset_srcu; atomic_t is_in_reset; struct notifier_block restart_nb; void *restart_nb_h; struct ratelimit_state rs; }; struct restart_work { struct delayed_work dwork; struct hma_info *hmap; void *conn_h; int check_count; }; static void shm_svc_restart_worker(struct work_struct *work); static DEFINE_MUTEX(hma_info_list_lock); static LIST_HEAD(hma_info_list); static struct msg_desc shm_svc_register_req_desc = { .max_msg_len = HMON_REGISTER_REQ_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_HEALTH_MON_REG_REQ_V01, .ei_array = hmon_register_req_msg_v01_ei, }; static struct msg_desc shm_svc_register_resp_desc = { .max_msg_len = HMON_REGISTER_RESP_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_HEALTH_MON_REG_RESP_V01, .ei_array = hmon_register_resp_msg_v01_ei, }; static struct msg_desc shm_svc_health_check_ind_desc = { .max_msg_len = HMON_HEALTH_CHECK_IND_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_HEALTH_MON_HEALTH_CHECK_IND_V01, .ei_array = hmon_health_check_ind_msg_v01_ei, }; static struct msg_desc shm_svc_health_check_complete_req_desc = { .max_msg_len = HMON_HEALTH_CHECK_COMPLETE_REQ_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_HEALTH_MON_HEALTH_CHECK_COMPLETE_REQ_V01, .ei_array = hmon_health_check_complete_req_msg_v01_ei, }; static struct msg_desc shm_svc_health_check_complete_resp_desc = { .max_msg_len = HMON_HEALTH_CHECK_COMPLETE_RESP_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_HEALTH_MON_HEALTH_CHECK_COMPLETE_RESP_V01, .ei_array = hmon_health_check_complete_resp_msg_v01_ei, }; /** * restart_notifier_cb() - Callback to handle SSR events * @this: Reference to the notifier block. * @code: Type of SSR event. * @data: Data that needs to be handled as part of SSR event. * * This function is used to identify if a subsystem which hosts an HMA * is already in reset, so that a duplicate subsystem restart is not * triggered. * * Return: 0 on success, standard Linux error codes on failure. */ static int restart_notifier_cb(struct notifier_block *this, unsigned long code, void *data) { struct hma_info *tmp_hma_info = container_of(this, struct hma_info, restart_nb); if (code == SUBSYS_BEFORE_SHUTDOWN) { atomic_set(&tmp_hma_info->is_in_reset, 1); synchronize_srcu(&tmp_hma_info->reset_srcu); SHM_INFO_LOG("%s: %s going to shutdown\n", __func__, tmp_hma_info->ssrestart_string); } else if (code == SUBSYS_AFTER_POWERUP) { atomic_set(&tmp_hma_info->is_in_reset, 0); SHM_INFO_LOG("%s: %s powered up\n", __func__, tmp_hma_info->ssrestart_string); } return 0; } /** * shm_svc_restart_worker() - Worker to restart a subsystem * @work: Reference to the work item being handled. * * This function restarts the subsystem which hosts an HMA. This function * checks the following before triggering a restart: * 1) Health check report is not received. * 2) The subsystem has not undergone a reset. * 3) The subsystem is not undergoing a reset. */ static void shm_svc_restart_worker(struct work_struct *work) { int rc; struct delayed_work *dwork = to_delayed_work(work); struct restart_work *rwp = container_of(dwork, struct restart_work, dwork); struct hma_info *tmp_hma_info = rwp->hmap; int rcu_id; if (rwp->check_count <= atomic_read(&tmp_hma_info->report_count)) { SHM_INFO_LOG("%s: No Action on Health Check Attempt %d to %s\n", __func__, rwp->check_count, tmp_hma_info->subsys_name); kfree(rwp); return; } if (!tmp_hma_info->conn_h || rwp->conn_h != tmp_hma_info->conn_h) { SHM_INFO_LOG( "%s: Connection to %s is reset. No further action\n", __func__, tmp_hma_info->subsys_name); kfree(rwp); return; } rcu_id = srcu_read_lock(&tmp_hma_info->reset_srcu); if (atomic_read(&tmp_hma_info->is_in_reset)) { SHM_INFO_LOG( "%s: %s is going thru restart. No further action\n", __func__, tmp_hma_info->subsys_name); srcu_read_unlock(&tmp_hma_info->reset_srcu, rcu_id); kfree(rwp); return; } SHM_ERR("%s: HMA in %s failed to respond in time. Restarting %s...\n", __func__, tmp_hma_info->subsys_name, tmp_hma_info->ssrestart_string); rc = subsystem_restart(tmp_hma_info->ssrestart_string); if (rc < 0) SHM_ERR("%s: Error %d restarting %s\n", __func__, rc, tmp_hma_info->ssrestart_string); srcu_read_unlock(&tmp_hma_info->reset_srcu, rcu_id); kfree(rwp); } /** * shm_send_health_check_ind() - Initiate a subsystem health check * @tmp_hma_info: Info about an HMA which resides in a subsystem. * * This function initiates a health check of a subsytem, which hosts the * HMA, by sending a health check QMI indication message. * * Return: 0 on success, standard Linux error codes on failure. */ static int shm_send_health_check_ind(struct hma_info *tmp_hma_info) { int rc; struct restart_work *rwp; if (!tmp_hma_info->conn_h) return 0; /* Rate limit the health check as configured by the subsystem */ if (!__ratelimit(&tmp_hma_info->rs)) return 0; rwp = kzalloc(sizeof(*rwp), GFP_KERNEL); if (!rwp) { SHM_ERR("%s: Error allocating restart work\n", __func__); return -ENOMEM; } INIT_DELAYED_WORK(&rwp->dwork, shm_svc_restart_worker); rwp->hmap = tmp_hma_info; rwp->conn_h = tmp_hma_info->conn_h; rc = qmi_send_ind(shm_svc_handle, tmp_hma_info->conn_h, &shm_svc_health_check_ind_desc, NULL, 0); if (rc < 0) { SHM_ERR("%s: Send Error %d to %s\n", __func__, rc, tmp_hma_info->subsys_name); kfree(rwp); return rc; } rwp->check_count = atomic_inc_return(&tmp_hma_info->check_count); queue_delayed_work(shm_svc_workqueue, &rwp->dwork, msecs_to_jiffies(tmp_hma_info->timeout)); return 0; } /** * kern_check_system_health() - Check the system health * * This function is used by the kernel drivers to initiate the * system health check. This function in turn triggers SHM to send * QMI message to all the HMAs connected to it. * * Return: 0 on success, standard Linux error codes on failure. */ int kern_check_system_health(void) { int rc; int final_rc = 0; struct hma_info *tmp_hma_info; mutex_lock(&hma_info_list_lock); list_for_each_entry(tmp_hma_info, &hma_info_list, list) { rc = shm_send_health_check_ind(tmp_hma_info); if (rc < 0) { SHM_ERR("%s by %s failed for %s - rc %d\n", __func__, current->comm, tmp_hma_info->subsys_name, rc); final_rc = rc; } } mutex_unlock(&hma_info_list_lock); return final_rc; } EXPORT_SYMBOL(kern_check_system_health); /** * shm_svc_connect_cb() - Callback to handle connect event from an HMA * @handle: QMI Service handle in which a connect event is received. * @conn_h: Opaque reference to the connection handle. * * Return: 0 on success, standard Linux error codes on failure. */ static int shm_svc_connect_cb(struct qmi_handle *handle, void *conn_h) { SHM_DEBUG("%s: conn_h %p\n", __func__, conn_h); return 0; } /** * shm_svc_disconnect_worker() - Worker to handle disconnect event from an HMA * @work: Reference to the work item. * * This function handles the disconnect event from an HMA in a deferred manner. */ static void shm_svc_disconnect_worker(struct work_struct *work) { struct hma_info *tmp_hma_info; struct disconnect_work *dwp = container_of(work, struct disconnect_work, work); mutex_lock(&hma_info_list_lock); list_for_each_entry(tmp_hma_info, &hma_info_list, list) { if (dwp->conn_h == tmp_hma_info->conn_h) { SHM_INFO_LOG("%s: conn_h %p to HMA in %s exited\n", __func__, dwp->conn_h, tmp_hma_info->subsys_name); tmp_hma_info->conn_h = NULL; atomic_set(&tmp_hma_info->report_count, atomic_read(&tmp_hma_info->check_count)); break; } } mutex_unlock(&hma_info_list_lock); kfree(dwp); } /** * shm_svc_disconnect_cb() - Callback to handle disconnect event from an HMA * @handle: QMI Service handle in which a disconnect event is received. * @conn_h: Opaque reference to the connection handle. * * Return: 0 on success, standard Linux error codes on failure. */ static int shm_svc_disconnect_cb(struct qmi_handle *handle, void *conn_h) { struct disconnect_work *dwp; dwp = kzalloc(sizeof(*dwp), GFP_ATOMIC); if (!dwp) { SHM_ERR("%s: Error allocating work item\n", __func__); return -ENOMEM; } INIT_WORK(&dwp->work, shm_svc_disconnect_worker); dwp->conn_h = conn_h; queue_work(shm_svc_workqueue, &dwp->work); return 0; } /** * shm_svc_req_desc_cb() - Callback to identify the request descriptor * @msg_id: Message ID of the QMI request. * @req_desc: Request Descriptor of the QMI request. * * Return: 0 on success, standard Linux error codes on failure. */ static int shm_svc_req_desc_cb(unsigned int msg_id, struct msg_desc **req_desc) { int rc; SHM_DEBUG("%s: called for msg_id %d\n", __func__, msg_id); switch (msg_id) { case QMI_HEALTH_MON_REG_REQ_V01: *req_desc = &shm_svc_register_req_desc; rc = sizeof(struct hmon_register_req_msg_v01); break; case QMI_HEALTH_MON_HEALTH_CHECK_COMPLETE_REQ_V01: *req_desc = &shm_svc_health_check_complete_req_desc; rc = sizeof(struct hmon_health_check_complete_req_msg_v01); break; default: SHM_ERR("%s: Invalid msg_id %d\n", __func__, msg_id); rc = -ENOTSUPP; } return rc; } /** * handle_health_mon_reg_req() - Handle the HMA register QMI request * @conn_h: Opaque reference to the connection handle to an HMA. * @req_h: Opaque reference to the request handle. * @buf: Pointer to the QMI request structure. * * This function handles the register request from an HMA. The request * contains the subsystem name which hosts the HMA and health check * timeout for the HMA. * * Return: 0 on success, standard Linux error codes on failure. */ static int handle_health_mon_reg_req(void *conn_h, void *req_h, void *buf) { int rc; struct hma_info *tmp_hma_info; struct hmon_register_req_msg_v01 *req = (struct hmon_register_req_msg_v01 *)buf; struct hmon_register_resp_msg_v01 resp; bool hma_info_found = false; if (!req->name_valid) { SHM_ERR("%s: host name invalid\n", __func__); goto send_reg_resp; } mutex_lock(&hma_info_list_lock); list_for_each_entry(tmp_hma_info, &hma_info_list, list) { if (!strcmp(tmp_hma_info->subsys_name, req->name) && !tmp_hma_info->conn_h) { tmp_hma_info->conn_h = conn_h; if (req->timeout_valid) tmp_hma_info->timeout = req->timeout; else tmp_hma_info->timeout = shm_default_timeout_ms; ratelimit_state_init(&tmp_hma_info->rs, DEFAULT_SHM_RATELIMIT_INTERVAL, DEFAULT_SHM_RATELIMIT_BURST); SHM_INFO_LOG("%s: from %s timeout_ms %d\n", __func__, req->name, tmp_hma_info->timeout); hma_info_found = true; } else if (!strcmp(tmp_hma_info->subsys_name, req->name)) { SHM_ERR("%s: Duplicate HMA from %s - cur %p, new %p\n", __func__, req->name, tmp_hma_info->conn_h, conn_h); } } mutex_unlock(&hma_info_list_lock); send_reg_resp: if (hma_info_found) { memset(&resp, 0, sizeof(resp)); } else { resp.resp.result = QMI_RESULT_FAILURE_V01; resp.resp.error = QMI_ERR_INVALID_ID_V01; } rc = qmi_send_resp(shm_svc_handle, conn_h, req_h, &shm_svc_register_resp_desc, &resp, sizeof(resp)); if (rc < 0) SHM_ERR("%s: send_resp failed to %s - rc %d\n", __func__, req->name, rc); return rc; } /** * handle_health_mon_health_check_complete_req() - Handle the HMA health report * @conn_h: Opaque reference to the connection handle to an HMA. * @req_h: Opaque reference to the request handle. * @buf: Pointer to the QMI request structure. * * This function handles health reports from an HMA. The health report is sent * in response to a health check QMI indication sent by SHM. * * Return: 0 on success, standard Linux error codes on failure. */ static int handle_health_mon_health_check_complete_req(void *conn_h, void *req_h, void *buf) { int rc; struct hma_info *tmp_hma_info; struct hmon_health_check_complete_req_msg_v01 *req = (struct hmon_health_check_complete_req_msg_v01 *)buf; struct hmon_health_check_complete_resp_msg_v01 resp; bool hma_info_found = false; if (!req->result_valid) { SHM_ERR("%s: Invalid result\n", __func__); goto send_resp; } mutex_lock(&hma_info_list_lock); list_for_each_entry(tmp_hma_info, &hma_info_list, list) { if (tmp_hma_info->conn_h != conn_h) continue; hma_info_found = true; if (req->result == HEALTH_MONITOR_CHECK_SUCCESS_V01) { atomic_inc(&tmp_hma_info->report_count); SHM_INFO_LOG("%s: %s Health Check Success\n", __func__, tmp_hma_info->subsys_name); } else { SHM_INFO_LOG("%s: %s Health Check Failure\n", __func__, tmp_hma_info->subsys_name); } } mutex_unlock(&hma_info_list_lock); send_resp: if (hma_info_found) { memset(&resp, 0, sizeof(resp)); } else { resp.resp.result = QMI_RESULT_FAILURE_V01; resp.resp.error = QMI_ERR_INVALID_ID_V01; } rc = qmi_send_resp(shm_svc_handle, conn_h, req_h, &shm_svc_health_check_complete_resp_desc, &resp, sizeof(resp)); if (rc < 0) SHM_ERR("%s: send_resp failed - rc %d\n", __func__, rc); return rc; } /** * shm_svc_req_worker() - Worker to handle QMI requests * @work: Reference to the work item. * * This function handles QMI requests from HMAs in a deferred manner. */ static void shm_svc_req_worker(struct work_struct *work) { struct req_work *rwp = container_of(work, struct req_work, work); switch (rwp->msg_id) { case QMI_HEALTH_MON_REG_REQ_V01: handle_health_mon_reg_req(rwp->conn_h, rwp->req_h, rwp->req); break; case QMI_HEALTH_MON_HEALTH_CHECK_COMPLETE_REQ_V01: handle_health_mon_health_check_complete_req(rwp->conn_h, rwp->req_h, rwp->req); break; default: SHM_ERR("%s: Invalid msg_id %d\n", __func__, rwp->msg_id); } kfree(rwp->req); kfree(rwp); } /** * shm_svc_req_cb() - Callback to notify about QMI requests from HMA * @handle; QMI Service handle in which the request is received. * @conn_h: Opaque reference to the connection handle to an HMA. * @req_h: Opaque reference to the request handle. * @msg_id: Message ID of the request. * @req: Pointer to the request structure. * * This function is called by kernel QMI Service Interface to notify the * incoming QMI request on the SHM service handle. * * Return: 0 on success, standard Linux error codes on failure. */ static int shm_svc_req_cb(struct qmi_handle *handle, void *conn_h, void *req_h, unsigned int msg_id, void *req) { struct req_work *rwp; void *req_buf; uint32_t req_sz = 0; rwp = kzalloc(sizeof(*rwp), GFP_KERNEL); if (!rwp) { SHM_ERR("%s: Error allocating work item\n", __func__); return -ENOMEM; } switch (msg_id) { case QMI_HEALTH_MON_REG_REQ_V01: req_sz = sizeof(struct hmon_register_req_msg_v01); break; case QMI_HEALTH_MON_HEALTH_CHECK_COMPLETE_REQ_V01: req_sz = sizeof(struct hmon_health_check_complete_req_msg_v01); break; default: SHM_ERR("%s: Invalid msg_id %d\n", __func__, msg_id); kfree(rwp); return -ENOTSUPP; } req_buf = kzalloc(req_sz, GFP_KERNEL); if (!req_buf) { SHM_ERR("%s: Error allocating request buffer\n", __func__); kfree(rwp); return -ENOMEM; } memcpy(req_buf, req, req_sz); INIT_WORK(&rwp->work, shm_svc_req_worker); rwp->conn_h = conn_h; rwp->req_h = req_h; rwp->msg_id = msg_id; rwp->req = req_buf; queue_work(shm_svc_workqueue, &rwp->work); return 0; } /** * shm_svc_recv_msg() - Worker to receive a QMI message * @work: Reference to the work item. * * This function handles any incoming QMI messages to the SHM QMI service. */ static void shm_svc_recv_msg(struct work_struct *work) { int rc; do { SHM_DEBUG("%s: Notified about a receive event\n", __func__); } while ((rc = qmi_recv_msg(shm_svc_handle)) == 0); if (rc != -ENOMSG) SHM_ERR("%s: Error %d receiving message\n", __func__, rc); } /** * shm_svc_notify() - Callback function to receive SHM QMI service events * @handle: QMI handle in which the event is received. * @event: Type of the QMI event. * @priv: Opaque reference to the private data as registered by the * service. */ static void shm_svc_notify(struct qmi_handle *handle, enum qmi_event_type event, void *priv) { switch (event) { case QMI_RECV_MSG: queue_delayed_work(shm_svc_workqueue, &work_recv_msg, 0); break; default: break; } } static struct qmi_svc_ops_options shm_svc_ops_options = { .version = 1, .service_id = HMON_SERVICE_ID_V01, .service_vers = HMON_SERVICE_VERS_V01, .service_ins = 0, .connect_cb = shm_svc_connect_cb, .disconnect_cb = shm_svc_disconnect_cb, .req_desc_cb = shm_svc_req_desc_cb, .req_cb = shm_svc_req_cb, }; static int system_health_monitor_open(struct inode *inode, struct file *file) { SHM_DEBUG("%s by %s\n", __func__, current->comm); return 0; } static int system_health_monitor_release(struct inode *inode, struct file *file) { SHM_DEBUG("%s by %s\n", __func__, current->comm); return 0; } static ssize_t system_health_monitor_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { SHM_ERR("%s by %s\n", __func__, current->comm); return -ENOTSUPP; } static ssize_t system_health_monitor_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { SHM_ERR("%s by %s\n", __func__, current->comm); return -ENOTSUPP; } static long system_health_monitor_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int rc; switch (cmd) { case CHECK_SYSTEM_HEALTH_IOCTL: SHM_INFO_LOG("%s by %s\n", __func__, current->comm); rc = kern_check_system_health(); break; default: SHM_ERR("%s: Invalid cmd %d by %s\n", __func__, cmd, current->comm); rc = -EINVAL; } return rc; } static const struct file_operations system_health_monitor_fops = { .owner = THIS_MODULE, .open = system_health_monitor_open, .release = system_health_monitor_release, .read = system_health_monitor_read, .write = system_health_monitor_write, .unlocked_ioctl = system_health_monitor_ioctl, .compat_ioctl = system_health_monitor_ioctl, }; /** * start_system_health_monitor_service() - Start the SHM QMI service * * This function registers the SHM QMI service, if it is not already * registered. */ static int start_system_health_monitor_service(void) { int rc; shm_svc_workqueue = create_singlethread_workqueue("shm_svc"); if (!shm_svc_workqueue) { SHM_ERR("%s: Error creating workqueue\n", __func__); return -EFAULT; } shm_svc_handle = qmi_handle_create(shm_svc_notify, NULL); if (!shm_svc_handle) { SHM_ERR("%s: Creating shm_svc_handle failed\n", __func__); rc = -ENOMEM; goto start_svc_error1; } rc = qmi_svc_register(shm_svc_handle, &shm_svc_ops_options); if (rc < 0) { SHM_ERR("%s: Registering shm svc failed - %d\n", __func__, rc); goto start_svc_error2; } return 0; start_svc_error2: qmi_handle_destroy(shm_svc_handle); start_svc_error1: destroy_workqueue(shm_svc_workqueue); return rc; } /** * parse_devicetree() - Parse the device tree for HMA information * @node: Pointer to the device tree node. * @hma: HMA information which needs to be extracted. * * This function parses the device tree, extracts the HMA information. * * Return: 0 on success, standard Linux error codes on failure. */ static int parse_devicetree(struct device_node *node, struct hma_info *hma) { char *key; const char *subsys_name; const char *ssrestart_string; key = "qcom,subsys-name"; subsys_name = of_get_property(node, key, NULL); if (!subsys_name) goto error; strlcpy(hma->subsys_name, subsys_name, SUBSYS_NAME_LEN); key = "qcom,ssrestart-string"; ssrestart_string = of_get_property(node, key, NULL); if (!ssrestart_string) goto error; strlcpy(hma->ssrestart_string, ssrestart_string, SSRESTART_STRLEN); return 0; error: SHM_ERR("%s: missing key: %s\n", __func__, key); return -ENODEV; } /** * system_health_monitor_probe() - Probe function to construct HMA info * @pdev: Platform device pointing to a device tree node. * * This function extracts the HMA information from the device tree, constructs * it and adds it to the global list. * * Return: 0 on success, standard Linux error codes on failure. */ static int system_health_monitor_probe(struct platform_device *pdev) { int rc; struct hma_info *hma, *tmp_hma; struct device_node *node; mutex_lock(&hma_info_list_lock); for_each_child_of_node(pdev->dev.of_node, node) { hma = kzalloc(sizeof(*hma), GFP_KERNEL); if (!hma) { SHM_ERR("%s: Error allocation hma_info\n", __func__); rc = -ENOMEM; goto probe_err; } rc = parse_devicetree(node, hma); if (rc) { SHM_ERR("%s Failed to parse Device Tree\n", __func__); kfree(hma); goto probe_err; } init_srcu_struct(&hma->reset_srcu); hma->restart_nb.notifier_call = restart_notifier_cb; hma->restart_nb_h = subsys_notif_register_notifier( hma->ssrestart_string, &hma->restart_nb); if (IS_ERR_OR_NULL(hma->restart_nb_h)) { cleanup_srcu_struct(&hma->reset_srcu); kfree(hma); rc = -EFAULT; SHM_ERR("%s: Error registering restart notif for %s\n", __func__, hma->ssrestart_string); goto probe_err; } list_add_tail(&hma->list, &hma_info_list); SHM_INFO_LOG("%s: Added HMA info for %s\n", __func__, hma->subsys_name); } rc = start_system_health_monitor_service(); if (rc) { SHM_ERR("%s Failed to start service %d\n", __func__, rc); goto probe_err; } mutex_unlock(&hma_info_list_lock); return 0; probe_err: list_for_each_entry_safe(hma, tmp_hma, &hma_info_list, list) { list_del(&hma->list); subsys_notif_unregister_notifier(hma->restart_nb_h, &hma->restart_nb); cleanup_srcu_struct(&hma->reset_srcu); kfree(hma); } mutex_unlock(&hma_info_list_lock); return rc; } static struct of_device_id system_health_monitor_match_table[] = { { .compatible = "qcom,system-health-monitor" }, {}, }; static struct platform_driver system_health_monitor_driver = { .probe = system_health_monitor_probe, .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, .of_match_table = system_health_monitor_match_table, }, }; /** * system_health_monitor_init() - Initialize the system health monitor module * * This functions registers a platform driver to probe for and extract the HMA * information. This function registers the character device interface to the * user-space. * * Return: 0 on success, standard Linux error codes on failure. */ static int __init system_health_monitor_init(void) { int rc; shm_ilctxt = ipc_log_context_create(SHM_ILCTXT_NUM_PAGES, "shm", 0); if (!shm_ilctxt) { SHM_ERR("%s: Unable to create SHM logging context\n", __func__); shm_debug_mask = 0; } rc = platform_driver_register(&system_health_monitor_driver); if (rc) { SHM_ERR("%s: system_health_monitor_driver register failed %d\n", __func__, rc); return rc; } rc = alloc_chrdev_region(&system_health_monitor_dev, 0, 1, "system_health_monitor"); if (rc < 0) { SHM_ERR("%s: alloc_chrdev_region() failed %d\n", __func__, rc); return rc; } system_health_monitor_classp = class_create(THIS_MODULE, "system_health_monitor"); if (IS_ERR_OR_NULL(system_health_monitor_classp)) { SHM_ERR("%s: class_create() failed\n", __func__); rc = -ENOMEM; goto init_error1; } cdev_init(&system_health_monitor_cdev, &system_health_monitor_fops); system_health_monitor_cdev.owner = THIS_MODULE; rc = cdev_add(&system_health_monitor_cdev, system_health_monitor_dev , 1); if (rc < 0) { SHM_ERR("%s: cdev_add() failed - rc %d\n", __func__, rc); goto init_error2; } system_health_monitor_devp = device_create(system_health_monitor_classp, NULL, system_health_monitor_dev, NULL, "system_health_monitor"); if (IS_ERR_OR_NULL(system_health_monitor_devp)) { SHM_ERR("%s: device_create() failed - rc %d\n", __func__, rc); rc = PTR_ERR(system_health_monitor_devp); goto init_error3; } SHM_INFO_LOG("%s: Complete\n", __func__); return 0; init_error3: cdev_del(&system_health_monitor_cdev); init_error2: class_destroy(system_health_monitor_classp); init_error1: unregister_chrdev_region(MAJOR(system_health_monitor_dev), 1); return rc; } module_init(system_health_monitor_init); MODULE_DESCRIPTION("System Health Monitor"); MODULE_LICENSE("GPL v2");