/* Copyright (c) 2017-2020, 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(msg) "bgcom_dev:" msg #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bgcom.h" #include "linux/bgcom_interface.h" #include "bgcom_interface.h" #include #include #include #include #include #include #include #include #include "peripheral-loader.h" #include "../../misc/qseecom_kernel.h" #include "pil_bg_intf.h" #define BGCOM "bg_com_dev" #define SECURE_APP "bgapp" #define BGDAEMON_LDO09_LPM_VTG 0 #define BGDAEMON_LDO09_NPM_VTG 10000 #define BGDAEMON_LDO03_LPM_VTG 0 #define BGDAEMON_LDO03_NPM_VTG 10000 #define MPPS_DOWN_EVENT_TO_BG_TIMEOUT 3000 #define ADSP_DOWN_EVENT_TO_BG_TIMEOUT 3000 enum { SSR_DOMAIN_BG, SSR_DOMAIN_MODEM, SSR_DOMAIN_ADSP, SSR_DOMAIN_MAX, }; enum ldo_task { ENABLE_LDO03, ENABLE_LDO09, DISABLE_LDO03, DISABLE_LDO09 }; struct bgdaemon_regulator { struct regulator *regldo03; struct regulator *regldo09; }; struct bgdaemon_priv { struct bgdaemon_regulator rgltr; enum ldo_task ldo_action; void *pil_h; bool pending_bg_twm_wear_load; struct workqueue_struct *bgdaemon_wq; struct work_struct bgdaemon_load_twm_bg_work; bool bg_twm_wear_load; struct qseecom_handle *qseecom_handle; int app_status; unsigned long attrs; u32 cmd_status; struct device *platform_dev; void *twm_data_buff; }; struct bg_event { enum bg_event_type e_type; }; struct service_info { const char name[32]; int domain_id; void *handle; struct notifier_block *nb; }; static char *ssr_domains[] = { "bg-wear", "modem", "adsp", }; static struct bgdaemon_priv *dev; static unsigned int bgreset_gpio; static DEFINE_MUTEX(bg_char_mutex); static struct cdev bg_cdev; static struct class *bg_class; struct device *dev_ret; static dev_t bg_dev; static int device_open; static void *handle; static bool twm_exit; static bool bg_app_running; static struct bgcom_open_config_type config_type; static DECLARE_COMPLETION(bg_modem_down_wait); static DECLARE_COMPLETION(bg_adsp_down_wait); /** * send_uevent(): send events to user space * pce : ssr event handle value * Return: 0 on success, standard Linux error code on error * * It adds pce value to event and broadcasts to user space. */ static int send_uevent(struct bg_event *pce) { char event_string[32]; char *envp[2] = { event_string, NULL }; snprintf(event_string, ARRAY_SIZE(event_string), "BG_EVENT=%d", pce->e_type); return kobject_uevent_env(&dev_ret->kobj, KOBJ_CHANGE, envp); } static void bgcom_load_twm_bg_work(struct work_struct *work) { if (dev->pil_h) { pr_err("bg-wear is already loaded\n"); subsystem_put(dev->pil_h); dev->pil_h = NULL; } else { dev->bg_twm_wear_load = true; dev->pil_h = subsystem_get_with_fwname("bg-wear", "bg-twm"); if (!dev->pil_h) pr_err("failed to load bg-twm\n"); } } static int bgdaemon_configure_regulators(bool state) { int retval; if (state == true) { retval = regulator_enable(dev->rgltr.regldo03); if (retval) pr_err("Failed to enable LDO-03 regulator:%d\n", retval); retval = regulator_enable(dev->rgltr.regldo09); if (retval) pr_err("Failed to enable LDO-09 regulator:%d\n", retval); } if (state == false) { retval = regulator_disable(dev->rgltr.regldo03); if (retval) pr_err("Failed to disable LDO-03 regulator:%d\n", retval); retval = regulator_disable(dev->rgltr.regldo09); if (retval) pr_err("Failed to disable LDO-09 regulator:%d\n", retval); } return retval; } static int bgdaemon_init_regulators(struct device *pdev) { int rc; struct regulator *reg03; struct regulator *reg09; reg03 = regulator_get(pdev, "ssr-reg1"); if (IS_ERR_OR_NULL(reg03)) { rc = PTR_ERR(reg03); pr_err("Unable to get regulator for LDO-03\n"); goto err_ret; } reg09 = regulator_get(pdev, "ssr-reg2"); if (IS_ERR_OR_NULL(reg09)) { rc = PTR_ERR(reg09); pr_err("Unable to get regulator for LDO-09\n"); goto err_ret; } dev->rgltr.regldo03 = reg03; dev->rgltr.regldo09 = reg09; return 0; err_ret: return rc; } static int bgdaemon_ldowork(enum ldo_task do_action) { int ret; switch (do_action) { case ENABLE_LDO03: ret = regulator_set_load(dev->rgltr.regldo03, BGDAEMON_LDO03_NPM_VTG); if (ret < 0) { pr_err("Failed to request LDO-03 voltage:%d\n", ret); goto err_ret; } break; case ENABLE_LDO09: ret = regulator_set_load(dev->rgltr.regldo09, BGDAEMON_LDO09_NPM_VTG); if (ret < 0) { pr_err("Failed to request LDO-09 voltage:%d\n", ret); goto err_ret; } break; case DISABLE_LDO03: ret = regulator_set_load(dev->rgltr.regldo03, BGDAEMON_LDO03_LPM_VTG); if (ret < 0) { pr_err("Failed to disable LDO-03:%d\n", ret); goto err_ret; } break; case DISABLE_LDO09: ret = regulator_set_load(dev->rgltr.regldo09, BGDAEMON_LDO09_LPM_VTG); if (ret < 0) { pr_err("Failed to disable LDO-09:%d\n", ret); goto err_ret; } break; default: ret = -EINVAL; } err_ret: return ret; } static int bgcom_char_open(struct inode *inode, struct file *file) { int ret; mutex_lock(&bg_char_mutex); if (device_open == 1) { pr_err("device is already open\n"); mutex_unlock(&bg_char_mutex); return -EBUSY; } device_open++; handle = bgcom_open(&config_type); mutex_unlock(&bg_char_mutex); if (IS_ERR(handle)) { device_open = 0; ret = PTR_ERR(handle); handle = NULL; return ret; } return 0; } static int bgchar_read_cmd(struct bg_ui_data *fui_obj_msg, int type) { void *read_buf; int ret; void __user *result = (void *) (uintptr_t)fui_obj_msg->result; read_buf = kmalloc_array(fui_obj_msg->num_of_words, sizeof(uint32_t), GFP_KERNEL); if (read_buf == NULL) return -ENOMEM; switch (type) { case REG_READ: ret = bgcom_reg_read(handle, fui_obj_msg->cmd, fui_obj_msg->num_of_words, read_buf); break; case AHB_READ: ret = bgcom_ahb_read(handle, fui_obj_msg->bg_address, fui_obj_msg->num_of_words, read_buf); break; } if (!ret && copy_to_user(result, read_buf, fui_obj_msg->num_of_words * sizeof(uint32_t))) { pr_err("copy to user failed\n"); ret = -EFAULT; } kfree(read_buf); return ret; } static int bgchar_write_cmd(struct bg_ui_data *fui_obj_msg, int type) { void *write_buf; int ret = -EINVAL; void __user *write = (void *) (uintptr_t)fui_obj_msg->write; write_buf = kmalloc_array(fui_obj_msg->num_of_words, sizeof(uint32_t), GFP_KERNEL); if (write_buf == NULL) return -ENOMEM; write_buf = memdup_user(write, fui_obj_msg->num_of_words * sizeof(uint32_t)); if (IS_ERR(write_buf)) { ret = PTR_ERR(write_buf); kfree(write_buf); return ret; } switch (type) { case REG_WRITE: ret = bgcom_reg_write(handle, fui_obj_msg->cmd, fui_obj_msg->num_of_words, write_buf); break; case AHB_WRITE: ret = bgcom_ahb_write(handle, fui_obj_msg->bg_address, fui_obj_msg->num_of_words, write_buf); break; } kfree(write_buf); return ret; } int bg_soft_reset(void) { pr_debug("do BG reset using gpio %d\n", bgreset_gpio); if (!gpio_is_valid(bgreset_gpio)) { pr_err("gpio %d is not valid\n", bgreset_gpio); return -ENXIO; } if (gpio_direction_output(bgreset_gpio, 1)) pr_err("gpio %d direction not set\n", bgreset_gpio); /* Sleep for 50ms for hardware to detect signal as high */ msleep(50); gpio_set_value(bgreset_gpio, 0); /* Sleep for 50ms for hardware to detect signal as high */ msleep(50); gpio_set_value(bgreset_gpio, 1); return 0; } EXPORT_SYMBOL(bg_soft_reset); static int modem_down2_bg(void) { complete(&bg_modem_down_wait); return 0; } static int adsp_down2_bg(void) { complete(&bg_adsp_down_wait); return 0; } /** * load_bg_tzapp() - Called to load TZ app. * * Return: 0 on success. Error code on failure. */ static int load_bg_tzapp(void) { int rc; /* return success if already loaded */ if (dev->qseecom_handle && !dev->app_status) return 0; /* Load the APP */ rc = qseecom_start_app(&dev->qseecom_handle, SECURE_APP, SZ_4K); if (rc < 0) { pr_err("BG TZ app load failure\n"); dev->app_status = RESULT_FAILURE; return -EIO; } dev->app_status = RESULT_SUCCESS; return 0; } /** * get_cmd_rsp_buffers() - Function sets cmd & rsp buffer pointers and * aligns buffer lengths * @hdl: index of qseecom_handle * @cmd: req buffer - set to qseecom_handle.sbuf * @cmd_len: ptr to req buffer len * @rsp: rsp buffer - set to qseecom_handle.sbuf + offset * @rsp_len: ptr to rsp buffer len * * Return: Success always . */ static int get_cmd_rsp_buffers(struct qseecom_handle *handle, void **cmd, uint32_t *cmd_len, void **rsp, uint32_t *rsp_len) { *cmd = handle->sbuf; if (*cmd_len & QSEECOM_ALIGN_MASK) *cmd_len = QSEECOM_ALIGN(*cmd_len); *rsp = handle->sbuf + *cmd_len; if (*rsp_len & QSEECOM_ALIGN_MASK) *rsp_len = QSEECOM_ALIGN(*rsp_len); return 0; } /** * tzapp_comm() - Function called to communicate with TZ APP. * @req: struct containing command and parameters. * * Return: 0 on success. Error code on failure. */ static long tzapp_comm(struct tzapp_bg_req *req) { struct tzapp_bg_req *bg_tz_req; struct tzapp_bg_rsp *bg_tz_rsp; int rc, req_len, rsp_len; /* Fill command structure */ req_len = sizeof(struct tzapp_bg_req); rsp_len = sizeof(struct tzapp_bg_rsp); rc = get_cmd_rsp_buffers(dev->qseecom_handle, (void **)&bg_tz_req, &req_len, (void **)&bg_tz_rsp, &rsp_len); if (rc) goto end; bg_tz_req->tzapp_bg_cmd = req->tzapp_bg_cmd; bg_tz_req->address_fw = req->address_fw; bg_tz_req->size_fw = req->size_fw; rc = qseecom_send_command(dev->qseecom_handle, (void *)bg_tz_req, req_len, (void *)bg_tz_rsp, rsp_len); pr_debug("BGAPP qseecom returned with value 0x%x and status 0x%x\n", rc, bg_tz_rsp->status); if (rc || bg_tz_rsp->status) dev->cmd_status = bg_tz_rsp->status; else dev->cmd_status = 0; end: return rc; } /** * tzapp_twm_data() - Called to read user data saved by BG in TWM * Allocate region from dynamic memory and pass this region to * tz to dump data content from BG. * * Return: 0 on success. Error code on failure. */ static int tzapp_twm_data(uint32_t num_words, void *read_buf) { struct tzapp_bg_req bg_tz_req; phys_addr_t start_addr; void *region; void *buf; int ret = 0; struct device dma_dev = {0}; size_t data_len = num_words * 4; arch_setup_dma_ops(&dma_dev, 0, 0, NULL, 0); dev->attrs = 0; dev->attrs |= DMA_ATTR_SKIP_ZEROING; dev->attrs |= DMA_ATTR_STRONGLY_ORDERED; region = dma_alloc_attrs(dev->platform_dev, data_len, &start_addr, GFP_KERNEL, dev->attrs); if (region == NULL) { pr_debug( "Failure to allocate twm data region of len %zx\n", data_len); return -ENOMEM; } bg_tz_req.tzapp_bg_cmd = BGPIL_TWM_DATA; bg_tz_req.address_fw = start_addr; bg_tz_req.size_fw = data_len; ret = tzapp_comm(&bg_tz_req); if (ret || dev->cmd_status) { pr_debug("%s: BG twm data collection failed\n", __func__); dev->cmd_status; goto error; } buf = dma_remap(dev->platform_dev, region, start_addr, data_len, dev->attrs); if (!buf) { pr_debug("%s: Remap failed\n", __func__); ret = -EFAULT; goto error; } memcpy(read_buf, buf, data_len); ret = 0; error: dma_free_attrs(dev->platform_dev, data_len, region, start_addr, dev->attrs); return ret; } /** * twm_data_fetch() - Called to fetch data saved by BG in TWM * * Return: 0 on success. Error code on failure. */ static int twm_data_fetch(struct bg_ui_data *fui_obj_msg) { int ret; dev->twm_data_buff = kmalloc_array(fui_obj_msg->num_of_words, sizeof(uint32_t), GFP_KERNEL); if (dev->twm_data_buff == NULL) return -ENOMEM; ret = tzapp_twm_data(fui_obj_msg->num_of_words, dev->twm_data_buff); return ret; } /** * twm_data_read() - Called to read user data saved by BG in TWM * * Return: 0 on success. Error code on failure. */ static int twm_data_read(struct bg_ui_data *fui_obj_msg) { int ret = 0; void __user *result = (void *) (uintptr_t)fui_obj_msg->buffer; if (dev->twm_data_buff == NULL) { pr_err("Empty user buffer\n"); return -EFAULT; } if (copy_to_user(result, dev->twm_data_buff, fui_obj_msg->num_of_words * sizeof(uint32_t))) { pr_err("copy to user failed\n"); ret = -EFAULT; } kfree(dev->twm_data_buff); dev->twm_data_buff == NULL; return ret; } static long bg_com_ioctl(struct file *filp, unsigned int ui_bgcom_cmd, unsigned long arg) { int ret; struct bg_ui_data ui_obj_msg; switch (ui_bgcom_cmd) { case REG_READ: case AHB_READ: if (copy_from_user(&ui_obj_msg, (void __user *) arg, sizeof(ui_obj_msg))) { pr_err("The copy from user failed\n"); ret = -EFAULT; } ret = bgchar_read_cmd(&ui_obj_msg, ui_bgcom_cmd); if (ret < 0) pr_err("bgchar_read_cmd failed\n"); break; case AHB_WRITE: case REG_WRITE: if (copy_from_user(&ui_obj_msg, (void __user *) arg, sizeof(ui_obj_msg))) { pr_err("The copy from user failed\n"); ret = -EFAULT; } ret = bgchar_write_cmd(&ui_obj_msg, ui_bgcom_cmd); if (ret < 0) pr_err("bgchar_write_cmd failed\n"); break; case SET_SPI_FREE: ret = bgcom_set_spi_state(BGCOM_SPI_FREE); break; case SET_SPI_BUSY: ret = bgcom_set_spi_state(BGCOM_SPI_BUSY); break; case BG_SOFT_RESET: ret = bg_soft_reset(); break; case BG_MODEM_DOWN2_BG_DONE: ret = modem_down2_bg(); break; case BG_ADSP_DOWN2_BG_DONE: ret = adsp_down2_bg(); case BG_TWM_EXIT: twm_exit = true; ret = 0; break; case BG_APP_RUNNING: bg_app_running = true; ret = 0; break; case BG_WEAR_LOAD: ret = 0; if (dev->pil_h) { pr_err("bg-wear is already loaded\n"); ret = -EFAULT; break; } dev->bg_twm_wear_load = false; dev->pil_h = subsystem_get_with_fwname("bg-wear", "bg-wear"); if (!dev->pil_h) { pr_err("failed to load bg-wear\n"); ret = -EFAULT; } break; case BG_WEAR_TWM_LOAD: dev->pending_bg_twm_wear_load = true; queue_work(dev->bgdaemon_wq, &dev->bgdaemon_load_twm_bg_work); ret = 0; break; case BG_WEAR_UNLOAD: if (dev->pil_h) { subsystem_put(dev->pil_h); dev->pil_h = NULL; bg_soft_reset(); } ret = 0; break; case BG_FETCH_TWM_DATA: ret = load_bg_tzapp(); if (ret) { pr_err("%s: BG TZ app load failure\n", __func__); } else { pr_debug("bgapp loaded\n"); if (copy_from_user(&ui_obj_msg, (void __user *) arg, sizeof(ui_obj_msg))) { pr_err("The copy from user failed\n"); ret = -EFAULT; } ret = twm_data_fetch(&ui_obj_msg); qseecom_shutdown_app(&dev->qseecom_handle); } break; case BG_READ_TWM_DATA: if (copy_from_user(&ui_obj_msg, (void __user *) arg, sizeof(ui_obj_msg))) { pr_err("The copy from user failed\n"); ret = -EFAULT; } ret = twm_data_read(&ui_obj_msg); break; default: ret = -ENOIOCTLCMD; break; } return ret; } static int bgcom_char_close(struct inode *inode, struct file *file) { int ret; mutex_lock(&bg_char_mutex); ret = bgcom_close(&handle); device_open = 0; mutex_unlock(&bg_char_mutex); return ret; } static int bg_daemon_probe(struct platform_device *pdev) { struct device_node *node; unsigned int reset_gpio; int ret; node = pdev->dev.of_node; pr_info("%s started", __func__); dev = kzalloc(sizeof(struct bgdaemon_priv), GFP_KERNEL); if (!dev) return -ENOMEM; reset_gpio = of_get_named_gpio(node, "qcom,bg-reset-gpio", 0); if (!gpio_is_valid(reset_gpio)) { pr_err("gpio %d found is not valid\n", reset_gpio); goto err_ret; } if (gpio_request(reset_gpio, "bg_reset_gpio")) { pr_err("gpio %d request failed\n", reset_gpio); goto err_ret; } if (gpio_direction_output(reset_gpio, 1)) { pr_err("gpio %d direction not set\n", reset_gpio); goto err_ret; } pr_info("bg-soft-reset gpio successfully requested\n"); bgreset_gpio = reset_gpio; ret = bgdaemon_init_regulators(&pdev->dev); if (ret != 0) { pr_err("Failed to init regulators:%d\n", ret); goto err_device; } ret = bgdaemon_configure_regulators(true); if (ret) { pr_err("Failed to confifigure regulators:%d\n", ret); bgdaemon_configure_regulators(false); goto err_ret; } dev->platform_dev = &pdev->dev; pr_info("%s success", __func__); err_device: return -ENODEV; err_ret: return 0; } static const struct of_device_id bg_daemon_of_match[] = { { .compatible = "qcom,bg-daemon", }, { } }; MODULE_DEVICE_TABLE(of, bg_daemon_of_match); static struct platform_driver bg_daemon_driver = { .probe = bg_daemon_probe, .driver = { .name = "bg-daemon", .of_match_table = bg_daemon_of_match, }, }; static const struct file_operations fops = { .owner = THIS_MODULE, .open = bgcom_char_open, .release = bgcom_char_close, .unlocked_ioctl = bg_com_ioctl, }; static int __init init_bg_com_dev(void) { int ret; ret = alloc_chrdev_region(&bg_dev, 0, 1, BGCOM); if (ret < 0) { pr_err("failed with error %d\n", ret); return ret; } cdev_init(&bg_cdev, &fops); ret = cdev_add(&bg_cdev, bg_dev, 1); if (ret < 0) { unregister_chrdev_region(bg_dev, 1); pr_err("device registration failed\n"); return ret; } bg_class = class_create(THIS_MODULE, BGCOM); if (IS_ERR_OR_NULL(bg_class)) { cdev_del(&bg_cdev); unregister_chrdev_region(bg_dev, 1); pr_err("class creation failed\n"); return PTR_ERR(bg_class); } dev_ret = device_create(bg_class, NULL, bg_dev, NULL, BGCOM); if (IS_ERR_OR_NULL(dev_ret)) { class_destroy(bg_class); cdev_del(&bg_cdev); unregister_chrdev_region(bg_dev, 1); pr_err("device create failed\n"); return PTR_ERR(dev_ret); } if (platform_driver_register(&bg_daemon_driver)) pr_err("%s: failed to register bg-daemon register\n", __func__); dev->bgdaemon_wq = create_singlethread_workqueue("bgdaemon-work-queue"); if (!dev->bgdaemon_wq) { pr_err("Failed to init BG-DAEMON work-queue\n"); goto free_wq; } INIT_WORK(&dev->bgdaemon_load_twm_bg_work, bgcom_load_twm_bg_work); return 0; free_wq: destroy_workqueue(dev->bgdaemon_wq); return -EFAULT; } static void __exit exit_bg_com_dev(void) { device_destroy(bg_class, bg_dev); class_destroy(bg_class); cdev_del(&bg_cdev); unregister_chrdev_region(bg_dev, 1); bgdaemon_configure_regulators(false); platform_driver_unregister(&bg_daemon_driver); } /** *ssr_bg_cb(): callback function is called *by ssr framework when BG goes down, up and during ramdump *collection. It handles BG shutdown and power up events. */ static int ssr_bg_cb(struct notifier_block *this, unsigned long opcode, void *data) { struct bg_event bge; switch (opcode) { case SUBSYS_BEFORE_SHUTDOWN: bge.e_type = BG_BEFORE_POWER_DOWN; bgdaemon_ldowork(ENABLE_LDO03); bgdaemon_ldowork(ENABLE_LDO09); bgcom_bgdown_handler(); bgcom_set_spi_state(BGCOM_SPI_BUSY); send_uevent(&bge); break; case SUBSYS_AFTER_SHUTDOWN: if (dev->pending_bg_twm_wear_load) { bg_soft_reset(); bgcom_bgdown_handler(); /* Load bg-twm */ dev->pending_bg_twm_wear_load = false; queue_work(dev->bgdaemon_wq, &dev->bgdaemon_load_twm_bg_work); } break; case SUBSYS_AFTER_POWERUP: if (dev->bg_twm_wear_load) bge.e_type = TWM_BG_AFTER_POWER_UP; else bge.e_type = BG_AFTER_POWER_UP; bgdaemon_ldowork(DISABLE_LDO03); bgdaemon_ldowork(DISABLE_LDO09); bgcom_set_spi_state(BGCOM_SPI_FREE); send_uevent(&bge); break; } return NOTIFY_DONE; } /** *ssr_modem_cb(): callback function is called *by ssr framework when modem goes down, up and during ramdump *collection. It handles modem shutdown and power up events. */ static int ssr_modem_cb(struct notifier_block *this, unsigned long opcode, void *data) { struct bg_event modeme; int ret; switch (opcode) { case SUBSYS_BEFORE_SHUTDOWN: modeme.e_type = MODEM_BEFORE_POWER_DOWN; reinit_completion(&bg_modem_down_wait); send_uevent(&modeme); ret = wait_for_completion_timeout(&bg_modem_down_wait, msecs_to_jiffies(MPPS_DOWN_EVENT_TO_BG_TIMEOUT)); if (!ret) pr_err("Time out on modem down event\n"); break; case SUBSYS_AFTER_POWERUP: modeme.e_type = MODEM_AFTER_POWER_UP; send_uevent(&modeme); break; } return NOTIFY_DONE; } static int ssr_adsp_cb(struct notifier_block *this, unsigned long opcode, void *data) { struct bg_event adspe; int ret; switch (opcode) { case SUBSYS_BEFORE_SHUTDOWN: adspe.e_type = ADSP_BEFORE_POWER_DOWN; reinit_completion(&bg_adsp_down_wait); send_uevent(&adspe); ret = wait_for_completion_timeout(&bg_adsp_down_wait, msecs_to_jiffies(ADSP_DOWN_EVENT_TO_BG_TIMEOUT)); if (!ret) pr_err("Time out on adsp down event\n"); break; case SUBSYS_AFTER_POWERUP: adspe.e_type = ADSP_AFTER_POWER_UP; send_uevent(&adspe); break; } return NOTIFY_DONE; } bool is_twm_exit(void) { if (twm_exit) { twm_exit = false; return true; } return false; } EXPORT_SYMBOL(is_twm_exit); bool is_bg_running(void) { if (bg_app_running) { bg_app_running = false; return true; } return false; } EXPORT_SYMBOL(is_bg_running); static struct notifier_block ssr_modem_nb = { .notifier_call = ssr_modem_cb, .priority = 0, }; static struct notifier_block ssr_adsp_nb = { .notifier_call = ssr_adsp_cb, .priority = 0, }; static struct notifier_block ssr_bg_nb = { .notifier_call = ssr_bg_cb, .priority = 0, }; static struct service_info service_data[3] = { { .name = "SSR_BG", .domain_id = SSR_DOMAIN_BG, .nb = &ssr_bg_nb, .handle = NULL, }, { .name = "SSR_MODEM", .domain_id = SSR_DOMAIN_MODEM, .nb = &ssr_modem_nb, .handle = NULL, }, { .name = "SSR_ADSP", .domain_id = SSR_DOMAIN_ADSP, .nb = &ssr_adsp_nb, .handle = NULL, }, }; /** * ssr_register checks that domain id should be in range and register * SSR framework for value at domain id. */ static int __init ssr_register(void) { int i; for (i = 0; i < ARRAY_SIZE(service_data); i++) { if ((service_data[i].domain_id < 0) || (service_data[i].domain_id >= SSR_DOMAIN_MAX)) { pr_err("Invalid service ID = %d\n", service_data[i].domain_id); } else { service_data[i].handle = subsys_notif_register_notifier( ssr_domains[service_data[i].domain_id], service_data[i].nb); if (IS_ERR_OR_NULL(service_data[i].handle)) { pr_err("subsys register failed for id = %d", service_data[i].domain_id); service_data[i].handle = NULL; } } } return 0; } module_init(init_bg_com_dev); late_initcall(ssr_register); module_exit(exit_bg_com_dev); MODULE_LICENSE("GPL v2");