// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ /* * Secure-Processor-SubSystem (SPSS) utilities. * * This driver provides utilities for the Secure Processor (SP). * * The SP daemon needs to load different SPSS images based on: * * 1. Test/Production key used to sign the SPSS image (read fuses). * 2. SPSS HW version (selected via Device Tree). * */ #define pr_fmt(fmt) "spss_utils [%s]: " fmt, __func__ #include /* min() */ #include /* MODULE_LICENSE */ #include /* class_create() */ #include /* kzalloc() */ #include /* file_operations */ #include /* cdev_add() */ #include /* EINVAL, ETIMEDOUT */ #include /* pr_err() */ #include /* BIT(x) */ #include /* platform_driver_register() */ #include /* of_property_count_strings() */ #include /* of_address_to_resource() */ #include /* ioremap() */ #include #include /* SZ_4K */ #include /* copy_from_user() */ #include /* wait_for_completion_timeout() */ #include /* kernel_restart() */ #include #include #include #include /* VMID_HLOS */ #include /* ioctl() */ #include /* IOCTL to user space */ /* driver name */ #define DEVICE_NAME "spss_utils" enum spss_firmware_type { SPSS_FW_TYPE_DEV = 'd', SPSS_FW_TYPE_TEST = 't', SPSS_FW_TYPE_PROD = 'p', SPSS_FW_TYPE_NONE = 'z', }; static enum spss_firmware_type firmware_type = SPSS_FW_TYPE_TEST; static const char *dev_firmware_name; static const char *test_firmware_name; static const char *prod_firmware_name; static const char *none_firmware_name = "nospss"; static const char *firmware_name = "NA"; static struct device *spss_dev; /* SP_SCSR_MBn_SP2CL_GPm(n,m) */ static u32 spss_debug_reg_addr; /*SP_SCSR_MB0_SP2CL_GP0*/ static u32 spss_debug_reg_addr1; /*SP_SCSR_MB1_SP2CL_GP0*/ static u32 spss_debug_reg_addr3; /*SP_SCSR_MB3_SP2CL_GP0*/ static u32 spss_emul_type_reg_addr; /* TCSR_SOC_EMULATION_TYPE */ static void *iar_notif_handle; static struct notifier_block *iar_nb; static bool is_iar_active; static bool is_ssr_disabled; #define CMAC_SIZE_IN_BYTES (128/8) /* 128 bit = 16 bytes */ #define CMAC_SIZE_IN_DWORDS (CMAC_SIZE_IN_BYTES/sizeof(u32)) /* 4 dwords */ // MCP code size register holds size divided by a factor // To get the actual size, need to multiply by the same factor #define MCP_SIZE_MUL_FACTOR (4) static u32 pil_addr; static u32 pil_size; /* * The saved fw cmac is stored in file in IAR-DB. * It is provided via ioctl from user space spu service. */ static u32 saved_fw_cmac[CMAC_SIZE_IN_DWORDS]; /* saved fw cmac */ /* * The calculated fw cmac is calculated by SPU PBL. * It is read from shared memory and provided back to user space service * via device attribute. */ static u32 calc_fw_cmac[CMAC_SIZE_IN_DWORDS]; /* calculated pbl fw cmac */ /* * The saved apps cmac is stored in file in IAR-DB. * It is provided via ioctl from user space spu service. */ static u32 saved_apps_cmac[MAX_SPU_UEFI_APPS][CMAC_SIZE_IN_DWORDS]; /* * The calculated apps cmac is calculated by SPU firmware. * It is read from shared memory and provided back to user space service * via device attribute. */ static u32 calc_apps_cmac[MAX_SPU_UEFI_APPS][CMAC_SIZE_IN_DWORDS]; static void __iomem *cmac_mem; static phys_addr_t cmac_mem_addr; #define CMAC_MEM_SIZE SZ_4K /* XPU align to 4KB */ #define SPSS_BASE_ADDR_MASK 0xFFFF0000 #define SPSS_RMB_CODE_SIZE_REG_OFFSET 0x1008 #define SPU_EMULATUION (BIT(0) | BIT(1)) #define SPU_PRESENT_IN_EMULATION BIT(0) /* IOCTL max request size is 1KB */ #define MAX_IOCTL_REQ_SIZE 1024 /* Events notification */ static struct completion spss_events[SPSS_NUM_EVENTS]; static bool spss_events_signaled[SPSS_NUM_EVENTS]; /* Protect from ioctl signal func called by multiple-proc at the same time */ static struct mutex event_lock; /** * struct device state */ struct spss_utils_device { /* char device info */ struct cdev *cdev; dev_t device_no; struct class *driver_class; struct device *class_dev; struct platform_device *pdev; }; /* Device State */ static struct spss_utils_device *spss_utils_dev; /* static functions declaration */ static int spss_set_saved_fw_cmac(u32 *cmac, size_t cmac_size); static int spss_set_saved_uefi_apps_cmac(void); static int spss_get_fw_calc_cmac(void); static int spss_get_apps_calc_cmac(void); /*==========================================================================*/ /* Device Sysfs */ /*==========================================================================*/ static ssize_t firmware_name_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; if (!dev || !attr || !buf) { pr_err("invalid param.\n"); return -EINVAL; } if (firmware_name == NULL) ret = scnprintf(buf, PAGE_SIZE, "%s\n", "unknown"); else ret = scnprintf(buf, PAGE_SIZE, "%s\n", firmware_name); return ret; } static DEVICE_ATTR_RO(firmware_name); static ssize_t test_fuse_state_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; if (!dev || !attr || !buf) { pr_err("invalid param.\n"); return -EINVAL; } switch (firmware_type) { case SPSS_FW_TYPE_DEV: ret = scnprintf(buf, PAGE_SIZE, "%s", "dev"); break; case SPSS_FW_TYPE_TEST: ret = scnprintf(buf, PAGE_SIZE, "%s", "test"); break; case SPSS_FW_TYPE_PROD: ret = scnprintf(buf, PAGE_SIZE, "%s", "prod"); break; default: return -EINVAL; } return ret; } static DEVICE_ATTR_RO(test_fuse_state); static ssize_t spss_debug_reg_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0; void __iomem *spss_debug_reg = NULL; void __iomem *spss_debug_reg1 = NULL; void __iomem *spss_debug_reg3 = NULL; u32 val1, val2, val3, val4, val7, val8; if (!dev || !attr || !buf) { pr_err("invalid param.\n"); return -EINVAL; } pr_debug("spss_debug_reg_addr [0x%x].\n", spss_debug_reg_addr); pr_debug("spss_debug_reg_addr1 [0x%x].\n", spss_debug_reg_addr1); pr_debug("spss_debug_reg_addr3 [0x%x].\n", spss_debug_reg_addr3); spss_debug_reg = ioremap(spss_debug_reg_addr, sizeof(u32)*2); spss_debug_reg1 = ioremap(spss_debug_reg_addr1, sizeof(u32)*2); spss_debug_reg3 = ioremap(spss_debug_reg_addr3, sizeof(u32)*2); if (!spss_debug_reg) { pr_err("can't map SPSS debug reg addr\n"); goto unmap_reg; } if (!spss_debug_reg1) { pr_err("can't map SPSS debug reg addr1\n"); goto unmap_reg1; } if (!spss_debug_reg3) { pr_err("can't map SPSS debug reg addr3\n"); goto unmap_reg3; } val1 = readl_relaxed(spss_debug_reg); val2 = readl_relaxed(((char *) spss_debug_reg) + sizeof(u32)); val3 = readl_relaxed(spss_debug_reg1); val4 = readl_relaxed(((char *) spss_debug_reg1) + sizeof(u32)); val7 = readl_relaxed(spss_debug_reg3); val8 = readl_relaxed(((char *) spss_debug_reg3) + sizeof(u32)); ret = scnprintf(buf, PAGE_SIZE, "MB0: val1[0x%x] val2[0x%x],\n MB1: val3[0x%x] val4[0x%x],\n MB3: val7[0x%x] val8[0x%x]\n", val1, val2, val3, val4, val7, val8); unmap_reg3: iounmap(spss_debug_reg3); unmap_reg1: iounmap(spss_debug_reg1); unmap_reg: iounmap(spss_debug_reg); return ret; } static DEVICE_ATTR_RO(spss_debug_reg); static ssize_t calc_fw_cmac_show(struct device *dev, struct device_attribute *attr, char *buf) { if (!dev || !attr || !buf) { pr_err("invalid param.\n"); return -EINVAL; } /* first make sure the calc cmac is updated */ spss_get_fw_calc_cmac(); memcpy(buf, calc_fw_cmac, sizeof(calc_fw_cmac)); return sizeof(calc_fw_cmac); } static DEVICE_ATTR_RO(calc_fw_cmac); static ssize_t calc_apps_cmac_show(struct device *dev, struct device_attribute *attr, char *buf) { if (!dev || !attr || !buf) { pr_err("invalid param.\n"); return -EINVAL; } /* first make sure the calc cmac is updated */ spss_get_apps_calc_cmac(); memcpy(buf, calc_apps_cmac, sizeof(calc_apps_cmac)); return sizeof(calc_apps_cmac); } static DEVICE_ATTR_RO(calc_apps_cmac); /*--------------------------------------------------------------------------*/ static int spss_create_sysfs(struct device *dev) { int ret; ret = device_create_file(dev, &dev_attr_firmware_name); if (ret < 0) { pr_err("failed to create sysfs file for firmware_name.\n"); return ret; } ret = device_create_file(dev, &dev_attr_test_fuse_state); if (ret < 0) { pr_err("failed to create sysfs file for test_fuse_state.\n"); goto remove_firmware_name; } ret = device_create_file(dev, &dev_attr_spss_debug_reg); if (ret < 0) { pr_err("failed to create sysfs file for spss_debug_reg.\n"); goto remove_test_fuse_state; } ret = device_create_file(dev, &dev_attr_calc_fw_cmac); if (ret < 0) { pr_err("failed to create sysfs file for calc_fw_cmac.\n"); goto remove_spss_debug_reg; } ret = device_create_file(dev, &dev_attr_calc_apps_cmac); if (ret < 0) { pr_err("failed to create sysfs file for calc_apps_cmac.\n"); goto remove_calc_fw_cmac; } return 0; remove_calc_fw_cmac: device_remove_file(dev, &dev_attr_calc_fw_cmac); remove_spss_debug_reg: device_remove_file(dev, &dev_attr_spss_debug_reg); remove_test_fuse_state: device_remove_file(dev, &dev_attr_test_fuse_state); remove_firmware_name: device_remove_file(dev, &dev_attr_firmware_name); return ret; } static void spss_destroy_sysfs(struct device *dev) { device_remove_file(dev, &dev_attr_calc_apps_cmac); device_remove_file(dev, &dev_attr_calc_fw_cmac); device_remove_file(dev, &dev_attr_spss_debug_reg); device_remove_file(dev, &dev_attr_test_fuse_state); device_remove_file(dev, &dev_attr_firmware_name); } /*==========================================================================*/ /* IOCTL */ /*==========================================================================*/ static int spss_wait_for_event(struct spss_ioc_wait_for_event *req) { int ret; uint32_t event_id; uint32_t timeout_sec; long timeleft = 1; event_id = req->event_id; timeout_sec = req->timeout_sec; if (event_id >= SPSS_NUM_EVENTS) { pr_err("event_id [%d] invalid\n", event_id); return -EINVAL; } pr_debug("wait for event [%d], timeout_sec [%d]\n", event_id, timeout_sec); if (timeout_sec) { unsigned long jiffies = 0; jiffies = msecs_to_jiffies(timeout_sec*1000); timeleft = wait_for_completion_interruptible_timeout( &spss_events[event_id], jiffies); ret = timeleft; } else { ret = wait_for_completion_interruptible( &spss_events[event_id]); } if (timeleft == 0) { pr_err("wait for event [%d] timeout [%d] sec expired\n", event_id, timeout_sec); req->status = EVENT_STATUS_TIMEOUT; } else if (ret < 0) { pr_err("wait for event [%d] interrupted. ret [%d]\n", event_id, ret); req->status = EVENT_STATUS_ABORTED; if (ret == -ERESTARTSYS) /* handle LPM event */ return ret; } else { pr_debug("wait for event [%d] completed.\n", event_id); req->status = EVENT_STATUS_SIGNALED; } return 0; } static int spss_signal_event(struct spss_ioc_signal_event *req) { uint32_t event_id; mutex_lock(&event_lock); event_id = req->event_id; if (event_id >= SPSS_NUM_EVENTS) { pr_err("event_id [%d] invalid\n", event_id); mutex_unlock(&event_lock); return -EINVAL; } if (spss_events_signaled[event_id]) { pr_err("event_id [%d] already signaled\n", event_id); mutex_unlock(&event_lock); return -EINVAL; } pr_debug("signal event [%d]\n", event_id); complete_all(&spss_events[event_id]); req->status = EVENT_STATUS_SIGNALED; spss_events_signaled[event_id] = true; mutex_unlock(&event_lock); return 0; } static int spss_is_event_signaled(struct spss_ioc_is_signaled *req) { uint32_t event_id; mutex_lock(&event_lock); event_id = req->event_id; if (event_id >= SPSS_NUM_EVENTS) { pr_err("event_id [%d] invalid\n", event_id); mutex_unlock(&event_lock); return -EINVAL; } if (spss_events_signaled[event_id]) req->status = EVENT_STATUS_SIGNALED; else req->status = EVENT_STATUS_NOT_SIGNALED; mutex_unlock(&event_lock); return 0; } static int spss_handle_set_fw_and_apps_cmac(struct spss_ioc_set_fw_and_apps_cmac *req) { int ret = 0; u32 cmac_buf_size = req->cmac_buf_size; void __user *cmac_buf_ptr = u64_to_user_ptr(req->cmac_buf_ptr); u32 num_of_cmacs = req->num_of_cmacs; /* Saved cmacs of spu firmware and UEFI loaded spu apps */ u32 fw_and_apps_cmacs[1+MAX_SPU_UEFI_APPS][CMAC_SIZE_IN_DWORDS]; pr_debug("cmac_buf_size [0x%x].\n", (int) req->cmac_buf_size); pr_debug("cmac_buf_ptr [0x%x].\n", (int) req->cmac_buf_ptr); pr_debug("num_of_cmacs [0x%x].\n", (int) req->num_of_cmacs); if (cmac_buf_size != sizeof(fw_and_apps_cmacs)) { pr_err("cmac_buf_size [0x%x] invalid.\n", cmac_buf_size); return -EINVAL; } if (num_of_cmacs > (u32)(MAX_SPU_UEFI_APPS+1)) { pr_err("num_of_cmacs [0x%x] invalid.\n", num_of_cmacs); return -EINVAL; } /* copy the saved cmacs from user buffer to loacl variable */ ret = copy_from_user(fw_and_apps_cmacs, cmac_buf_ptr, cmac_buf_size); if (ret < 0) { pr_err("copy_from_user() from cmac_buf_ptr failed.\n"); return -EFAULT; } /* store the saved fw cmac */ memcpy(saved_fw_cmac, fw_and_apps_cmacs[0], sizeof(saved_fw_cmac)); pr_debug("saved fw cmac: 0x%08x,0x%08x,0x%08x,0x%08x\n", saved_fw_cmac[0], saved_fw_cmac[1], saved_fw_cmac[2], saved_fw_cmac[3]); /* store the saved apps cmac */ memcpy(saved_apps_cmac, &fw_and_apps_cmacs[1][0], sizeof(saved_apps_cmac)); /* * SPSS is loaded now by UEFI, * so PIL-IAR-callback is not being called on power-up by PIL. * therefore get the saved spu fw cmac and apps cmac from ioctl. * The PIL-IAR-callback shall be called on spss SSR. * The saved cmacs are used on QCOM_SSR_AFTER_POWERUP event ! */ spss_set_saved_fw_cmac(saved_fw_cmac, sizeof(saved_fw_cmac)); spss_set_saved_uefi_apps_cmac(); pr_debug("completed ok\n"); return 0; } static long spss_utils_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret; void *buf = (void *) arg; uint8_t data[MAX_IOCTL_REQ_SIZE] = {0}; size_t size = 0; void *req = (void *) data; if (buf == NULL) { pr_err("invalid ioctl arg\n"); return -EINVAL; } size = _IOC_SIZE(cmd); if (size && (cmd & IOC_IN)) { if (size > sizeof(data)) { pr_err("cmd [0x%x] size [0x%x] too large\n", cmd, size); return -EINVAL; } if (copy_from_user(data, (void __user *)arg, size)) { pr_err("copy_from_user() failed, cmd [0x%x] size [0x%x]\n", cmd, size); return -EFAULT; } } switch (cmd) { case SPSS_IOC_SET_FW_AND_APPS_CMAC: pr_debug("ioctl [SPSS_IOC_SET_FW_AND_APPS_CMAC]\n"); /* spdaemon uses this ioctl only when IAR is active */ is_iar_active = true; if (cmac_mem == NULL) { cmac_mem = ioremap(cmac_mem_addr, CMAC_MEM_SIZE); if (!cmac_mem) { pr_err("can't map cmac_mem.\n"); return -EFAULT; } } ret = spss_handle_set_fw_and_apps_cmac(req); if (ret < 0) return ret; break; case SPSS_IOC_WAIT_FOR_EVENT: /* check input params */ if (size != sizeof(struct spss_ioc_wait_for_event)) { pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size); return -EINVAL; } ret = spss_wait_for_event(req); if (ret < 0) return ret; ret = copy_to_user((void __user *)arg, data, size); if (ret) { pr_err("cmd [0x%x] copy_to_user failed - %d\n", cmd, ret); return ret; } break; case SPSS_IOC_SIGNAL_EVENT: /* check input params */ if (size != sizeof(struct spss_ioc_signal_event)) { pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size); return -EINVAL; } ret = spss_signal_event(req); if (ret < 0) return ret; ret = copy_to_user((void __user *)arg, data, size); if (ret) { pr_err("cmd [0x%x] copy_to_user failed - %d\n", cmd, ret); return ret; } break; case SPSS_IOC_IS_EVENT_SIGNALED: /* check input params */ if (size != sizeof(struct spss_ioc_is_signaled)) { pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size); return -EINVAL; } ret = spss_is_event_signaled(req); if (ret < 0) return ret; ret = copy_to_user((void __user *)arg, data, size); if (ret) { pr_err("cmd [0x%x] copy_to_user failed - %d\n", cmd, ret); return ret; } break; case SPSS_IOC_SET_SSR_STATE: /* check input params */ if (size != sizeof(uint32_t)) { pr_err("cmd [0x%x] invalid size [0x%x]\n", cmd, size); return -EINVAL; } if (is_iar_active) { uint32_t tmp = 0; memcpy(&tmp, data, sizeof(tmp)); is_ssr_disabled = (bool) tmp; /* u32 to bool */ pr_info("SSR disabled state updated to: %d\n", is_ssr_disabled); } pr_info("is_iar_active [%d] is_ssr_disabled [%d].\n", is_iar_active, is_ssr_disabled); break; default: pr_err("invalid ioctl cmd [0x%x]\n", cmd); return -ENOIOCTLCMD; } return 0; } static const struct file_operations spss_utils_fops = { .owner = THIS_MODULE, .unlocked_ioctl = spss_utils_ioctl, .compat_ioctl = spss_utils_ioctl, }; static int spss_utils_create_chardev(struct device *dev) { int ret; unsigned int baseminor = 0; unsigned int count = 1; void *priv = (void *) spss_utils_dev; spss_utils_dev->cdev = kzalloc(sizeof(*spss_utils_dev->cdev), GFP_KERNEL); if (!spss_utils_dev->cdev) return -ENOMEM; /* get device_no */ ret = alloc_chrdev_region(&spss_utils_dev->device_no, baseminor, count, DEVICE_NAME); if (ret < 0) { pr_err("alloc_chrdev_region failed %d\n", ret); return ret; } spss_utils_dev->driver_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(spss_utils_dev->driver_class)) { ret = -ENOMEM; pr_err("class_create failed %d\n", ret); goto exit_unreg_chrdev_region; } spss_utils_dev->class_dev = device_create(spss_utils_dev->driver_class, NULL, spss_utils_dev->device_no, priv, DEVICE_NAME); if (IS_ERR(spss_utils_dev->class_dev)) { pr_err("class_device_create failed %d\n", ret); ret = -ENOMEM; goto exit_destroy_class; } cdev_init(spss_utils_dev->cdev, &spss_utils_fops); spss_utils_dev->cdev->owner = THIS_MODULE; ret = cdev_add(spss_utils_dev->cdev, MKDEV(MAJOR(spss_utils_dev->device_no), 0), 1); if (ret < 0) { pr_err("cdev_add failed %d\n", ret); goto exit_destroy_device; } pr_debug("char device created.\n"); return 0; exit_destroy_device: device_destroy(spss_utils_dev->driver_class, spss_utils_dev->device_no); exit_destroy_class: class_destroy(spss_utils_dev->driver_class); exit_unreg_chrdev_region: unregister_chrdev_region(spss_utils_dev->device_no, 1); return ret; } static void spss_utils_destroy_chardev(void) { device_destroy(spss_utils_dev->driver_class, spss_utils_dev->device_no); class_destroy(spss_utils_dev->driver_class); unregister_chrdev_region(spss_utils_dev->device_no, 1); } /*==========================================================================*/ /* Device Tree */ /*==========================================================================*/ /* get the ACTUAL spss PIL firmware size from spu reg */ static int get_pil_size(phys_addr_t base_addr) { u32 spss_code_size_addr = 0; void __iomem *spss_code_size_reg = NULL; u32 pil_size = 0; spss_code_size_addr = base_addr + SPSS_RMB_CODE_SIZE_REG_OFFSET; spss_code_size_reg = ioremap(spss_code_size_addr, sizeof(u32)); if (!spss_code_size_reg) { pr_err("can't map spss_code_size_addr\n"); return -EINVAL; } pil_size = readl_relaxed(spss_code_size_reg); iounmap(spss_code_size_reg); // Multiply the value read from code size register by factor // to get the actual size (see MCP_SIZE_MUL_FACTOR documentation) pil_size *= MCP_SIZE_MUL_FACTOR; if (pil_size % SZ_4K) { pr_err("pil_size [0x%08x] is not 4K aligned.\n", pil_size); return -EFAULT; } return pil_size; } /** * spss_parse_dt() - Parse Device Tree info. */ static int spss_parse_dt(struct device_node *node) { int ret; u32 spss_fuse1_addr = 0; u32 spss_fuse1_bit = 0; u32 spss_fuse1_mask = 0; void __iomem *spss_fuse1_reg = NULL; u32 spss_fuse2_addr = 0; u32 spss_fuse2_bit = 0; u32 spss_fuse2_mask = 0; void __iomem *spss_fuse2_reg = NULL; struct device_node *np; struct resource r; u32 val1 = 0; u32 val2 = 0; void __iomem *spss_emul_type_reg = NULL; u32 spss_emul_type_val = 0; phys_addr_t spss_regs_base_addr = 0; ret = of_property_read_string(node, "qcom,spss-dev-firmware-name", &dev_firmware_name); if (ret < 0) { pr_err("can't get dev fw name\n"); return -EINVAL; } ret = of_property_read_string(node, "qcom,spss-test-firmware-name", &test_firmware_name); if (ret < 0) { pr_err("can't get test fw name\n"); return -EINVAL; } ret = of_property_read_string(node, "qcom,spss-prod-firmware-name", &prod_firmware_name); if (ret < 0) { pr_err("can't get prod fw name\n"); return -EINVAL; } ret = of_property_read_u32(node, "qcom,spss-fuse1-addr", &spss_fuse1_addr); if (ret < 0) { pr_err("can't get fuse1 addr\n"); return -EINVAL; } ret = of_property_read_u32(node, "qcom,spss-fuse2-addr", &spss_fuse2_addr); if (ret < 0) { pr_err("can't get fuse2 addr\n"); return -EINVAL; } ret = of_property_read_u32(node, "qcom,spss-fuse1-bit", &spss_fuse1_bit); if (ret < 0) { pr_err("can't get fuse1 bit\n"); return -EINVAL; } ret = of_property_read_u32(node, "qcom,spss-fuse2-bit", &spss_fuse2_bit); if (ret < 0) { pr_err("can't get fuse2 bit\n"); return -EINVAL; } spss_fuse1_mask = BIT(spss_fuse1_bit); spss_fuse2_mask = BIT(spss_fuse2_bit); pr_debug("spss fuse1 addr [0x%x] bit [%d]\n", (int) spss_fuse1_addr, (int) spss_fuse1_bit); pr_debug("spss fuse2 addr [0x%x] bit [%d]\n", (int) spss_fuse2_addr, (int) spss_fuse2_bit); spss_fuse1_reg = ioremap(spss_fuse1_addr, sizeof(u32)); if (!spss_fuse1_reg) { pr_err("can't map fuse1 addr\n"); return -EINVAL; } spss_fuse2_reg = ioremap(spss_fuse2_addr, sizeof(u32)); if (!spss_fuse2_reg) { iounmap(spss_fuse1_reg); pr_err("can't map fuse2 addr\n"); return -EINVAL; } val1 = readl_relaxed(spss_fuse1_reg); val2 = readl_relaxed(spss_fuse2_reg); pr_debug("spss fuse1 value [0x%08x]\n", (int) val1); pr_debug("spss fuse2 value [0x%08x]\n", (int) val2); pr_debug("spss fuse1 mask [0x%08x]\n", (int) spss_fuse1_mask); pr_debug("spss fuse2 mask [0x%08x]\n", (int) spss_fuse2_mask); /** * Set firmware_type based on fuses: * SPSS_CONFIG_MODE 11: prod, changed from DEV to PROD due to PTE configuration error in Waipio 2.1 CONFIG_MODE 11 will be used for production signed MCP as workaround. * SPSS_CONFIG_MODE 01 or 10: test * SPSS_CONFIG_MODE 00: prod */ if ((val1 & spss_fuse1_mask) && (val2 & spss_fuse2_mask)) firmware_type = SPSS_FW_TYPE_PROD; else if ((val1 & spss_fuse1_mask) || (val2 & spss_fuse2_mask)) firmware_type = SPSS_FW_TYPE_TEST; else firmware_type = SPSS_FW_TYPE_PROD; iounmap(spss_fuse1_reg); iounmap(spss_fuse2_reg); pr_debug("firmware_type value [%c]\n", firmware_type); ret = of_property_read_u32(node, "qcom,spss-debug-reg-addr", &spss_debug_reg_addr); if (ret < 0) { pr_err("can't get debug regs addr\n"); return ret; } ret = of_property_read_u32(node, "qcom,spss-debug-reg-addr1", &spss_debug_reg_addr1); if (ret < 0) { pr_err("can't get debug regs addr1\n"); return ret; } ret = of_property_read_u32(node, "qcom,spss-debug-reg-addr3", &spss_debug_reg_addr3); if (ret < 0) { pr_err("can't get debug regs addr3\n"); return ret; } ret = of_property_read_u32(node, "qcom,spss-emul-type-reg-addr", &spss_emul_type_reg_addr); if (ret < 0) { pr_err("can't get spss-emulation-type-reg addr\n"); return -EINVAL; } spss_emul_type_reg = ioremap(spss_emul_type_reg_addr, sizeof(u32)); if (!spss_emul_type_reg) { pr_err("can't map soc-emulation-type reg addr\n"); return -EINVAL; } spss_emul_type_val = readl_relaxed(spss_emul_type_reg); pr_debug("spss_emul_type value [0x%08x]\n", (int)spss_emul_type_val); if (spss_emul_type_val & SPU_EMULATUION) { if (spss_emul_type_val & SPU_PRESENT_IN_EMULATION) { firmware_type = SPSS_FW_TYPE_TEST; } else { /* for some emulation platforms SPSS is not present */ firmware_type = SPSS_FW_TYPE_NONE; } pr_debug("remap firmware_type value [%c]\n", firmware_type); } iounmap(spss_emul_type_reg); /* PIL-SPSS area */ np = of_parse_phandle(node, "pil-mem", 0); if (!np) { pr_err("no pil-mem entry, check pil-addr\n"); ret = of_property_read_u32(node, "qcom,pil-addr", &pil_addr); if (ret < 0) { pr_err("can't get pil_addr\n"); return -EFAULT; } } else { ret = of_address_to_resource(np, 0, &r); of_node_put(np); if (ret) return ret; pil_addr = (u32)r.start; } spss_regs_base_addr = (spss_debug_reg_addr & SPSS_BASE_ADDR_MASK); ret = get_pil_size(spss_regs_base_addr); if (ret < 0) { pr_err("failed to get pil_size.\n"); return -EFAULT; } pil_size = (u32) ret; pr_debug("pil_addr [0x%08x].\n", pil_addr); pr_debug("pil_size [0x%08x].\n", pil_size); /* cmac buffer after spss firmware end */ cmac_mem_addr = pil_addr + pil_size; pr_info("iar_buf_addr [0x%08x].\n", cmac_mem_addr); memset(saved_fw_cmac, 0xA5, sizeof(saved_fw_cmac)); memset(saved_apps_cmac, 0xA5, sizeof(saved_apps_cmac)); return 0; } static int spss_set_saved_fw_cmac(u32 *cmac, size_t cmac_size) { u8 __iomem *reg = NULL; int i; if (cmac_mem == NULL) { pr_err("invalid cmac_mem.\n"); return -EFAULT; } reg = cmac_mem; for (i = 0; i < cmac_size/sizeof(u32); i++) writel_relaxed(cmac[i], reg + i*sizeof(u32)); pr_debug("saved fw cmac: 0x%08x,0x%08x,0x%08x,0x%08x\n", cmac[0], cmac[1], cmac[2], cmac[3]); return 0; } static int spss_get_fw_calc_cmac(void) { u8 __iomem *reg = NULL; int i; u32 val; u32 cmac[CMAC_SIZE_IN_DWORDS] = {0}; if (cmac_mem == NULL) { pr_err("invalid cmac_mem.\n"); return -EFAULT; } reg = cmac_mem; /* IAR buffer base */ reg += CMAC_SIZE_IN_BYTES; /* skip the saved cmac */ memset(calc_fw_cmac, 0, sizeof(calc_fw_cmac)); /* get pbl fw cmac from ddr */ for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) { val = readl_relaxed(reg); calc_fw_cmac[i] = val; reg += sizeof(u32); } /* check for any pattern to mark invalid cmac */ if (cmac[0] == cmac[1]) return -EINVAL; /* not valid cmac */ memcpy(calc_fw_cmac, cmac, sizeof(calc_fw_cmac)); pr_debug("calc_fw_cmac : 0x%08x,0x%08x,0x%08x,0x%08x\n", calc_fw_cmac[0], calc_fw_cmac[1], calc_fw_cmac[2], calc_fw_cmac[3]); return 0; } static int spss_get_apps_calc_cmac(void) { u8 __iomem *reg = NULL; int i, j; u32 val; if (cmac_mem == NULL) { pr_err("invalid cmac_mem.\n"); return -EFAULT; } reg = cmac_mem; /* IAR buffer base */ reg += CMAC_SIZE_IN_BYTES; /* skip the saved fw cmac */ reg += CMAC_SIZE_IN_BYTES; /* skip the calc fw cmac */ reg += CMAC_SIZE_IN_BYTES; /* skip the saved 1st app cmac */ memset(calc_apps_cmac, 0, sizeof(calc_apps_cmac)); /* get apps cmac from ddr */ for (j = 0; j < ARRAY_SIZE(calc_apps_cmac); j++) { u32 cmac[CMAC_SIZE_IN_DWORDS] = {0}; memset(cmac, 0, sizeof(cmac)); for (i = 0; i < ARRAY_SIZE(cmac); i++) { val = readl_relaxed(reg); cmac[i] = val; reg += sizeof(u32); } reg += CMAC_SIZE_IN_BYTES; /* skip the saved cmac */ /* check for any pattern to mark end of cmacs */ if (cmac[0] == cmac[1]) break; /* no more valid cmacs */ memcpy(calc_apps_cmac[j], cmac, sizeof(calc_apps_cmac[j])); pr_debug("app [%d] cmac : 0x%08x,0x%08x,0x%08x,0x%08x\n", j, calc_apps_cmac[j][0], calc_apps_cmac[j][1], calc_apps_cmac[j][2], calc_apps_cmac[j][3]); } return 0; } static int spss_set_saved_uefi_apps_cmac(void) { u8 __iomem *reg = NULL; int i, j; u32 val; if (cmac_mem == NULL) { pr_err("invalid cmac_mem.\n"); return -EFAULT; } reg = cmac_mem; /* IAR buffer base */ reg += (2*CMAC_SIZE_IN_BYTES); /* skip the saved and calc fw cmac */ /* get saved apps cmac from ddr - were written by UEFI spss driver */ for (j = 0; j < MAX_SPU_UEFI_APPS; j++) { if (saved_apps_cmac[j][0] == saved_apps_cmac[j][1]) break; /* no more cmacs */ for (i = 0; i < CMAC_SIZE_IN_DWORDS; i++) { val = saved_apps_cmac[j][i]; writel_relaxed(val, reg); reg += sizeof(u32); } reg += CMAC_SIZE_IN_BYTES; /* skip the calc app cmac */ pr_debug("app[%d] saved cmac: 0x%08x,0x%08x,0x%08x,0x%08x\n", j, saved_apps_cmac[j][0], saved_apps_cmac[j][1], saved_apps_cmac[j][2], saved_apps_cmac[j][3]); } return 0; } static int spss_utils_rproc_callback(struct notifier_block *nb, unsigned long code, void *data) { int i, event_id; switch (code) { case QCOM_SSR_BEFORE_SHUTDOWN: pr_debug("[QCOM_SSR_BEFORE_SHUTDOWN] event.\n"); mutex_lock(&event_lock); /* Reset NVM-ready and SPU-ready events */ for (i = SPSS_EVENT_ID_NVM_READY; i <= SPSS_EVENT_ID_SPU_READY; i++) { reinit_completion(&spss_events[i]); spss_events_signaled[i] = false; } mutex_unlock(&event_lock); pr_debug("reset spss events.\n"); break; case QCOM_SSR_AFTER_SHUTDOWN: pr_debug("[QCOM_SSR_AFTER_SHUTDOWN] event.\n"); mutex_lock(&event_lock); event_id = SPSS_EVENT_ID_SPU_POWER_DOWN; complete_all(&spss_events[event_id]); spss_events_signaled[event_id] = true; event_id = SPSS_EVENT_ID_SPU_POWER_UP; reinit_completion(&spss_events[event_id]); spss_events_signaled[event_id] = false; mutex_unlock(&event_lock); break; case QCOM_SSR_BEFORE_POWERUP: pr_debug("[QCOM_SSR_BEFORE_POWERUP] event.\n"); if (is_iar_active) { if (is_ssr_disabled) { pr_warn("SPSS SSR disabled, requesting reboot\n"); kernel_restart("SPSS SSR disabled, requesting reboot"); } else { /* Called on SSR as spss firmware is loaded by UEFI */ spss_set_saved_fw_cmac(saved_fw_cmac, sizeof(saved_fw_cmac)); spss_set_saved_uefi_apps_cmac(); } } break; case QCOM_SSR_AFTER_POWERUP: pr_info("QCOM_SSR_AFTER_POWERUP] event.\n"); mutex_lock(&event_lock); event_id = SPSS_EVENT_ID_SPU_POWER_UP; complete_all(&spss_events[event_id]); spss_events_signaled[event_id] = true; event_id = SPSS_EVENT_ID_SPU_POWER_DOWN; reinit_completion(&spss_events[event_id]); spss_events_signaled[event_id] = false; mutex_unlock(&event_lock); /* * For IAR-DB-Recovery, read cmac regadless of is_iar_active. * please notice that HYP unmap this area, it is a race. */ if (cmac_mem == NULL) { cmac_mem = ioremap(cmac_mem_addr, CMAC_MEM_SIZE); if (!cmac_mem) { pr_err("can't map cmac_mem.\n"); return -EFAULT; } } spss_get_fw_calc_cmac(); spss_get_apps_calc_cmac(); break; default: pr_err("unknown code [0x%x] .\n", (int) code); break; } return NOTIFY_OK; } /** * spss_probe() - initialization sequence */ static int spss_probe(struct platform_device *pdev) { int ret = 0; int i; struct device_node *np = NULL; struct device *dev = &pdev->dev; struct property *prop = NULL; struct rproc *rproc = NULL; np = pdev->dev.of_node; spss_dev = dev; platform_set_drvdata(pdev, dev); ret = spss_parse_dt(np); if (ret < 0) return ret; switch (firmware_type) { case SPSS_FW_TYPE_DEV: firmware_name = dev_firmware_name; break; case SPSS_FW_TYPE_TEST: firmware_name = test_firmware_name; break; case SPSS_FW_TYPE_PROD: firmware_name = prod_firmware_name; break; case SPSS_FW_TYPE_NONE: firmware_name = none_firmware_name; break; default: pr_err("invalid firmware type %d, sysfs entry not created\n", firmware_type); return -EINVAL; } prop = of_find_property(np, "qcom,rproc-handle", NULL); if (!prop) return -EINVAL; rproc = rproc_get_by_phandle(be32_to_cpup(prop->value)); if (!rproc) return -EPROBE_DEFER; ret = qcom_spss_set_fw_name(rproc, firmware_name); if (ret < 0) { if (ret != -EINVAL) pr_err("fail to set firmware name for remoteproc (%d)\n", ret); return -EPROBE_DEFER; } rproc_put(rproc); spss_utils_dev = kzalloc(sizeof(*spss_utils_dev), GFP_KERNEL); if (spss_utils_dev == NULL) return -ENOMEM; ret = spss_utils_create_chardev(dev); if (ret < 0) return ret; ret = spss_create_sysfs(dev); if (ret < 0) return ret; iar_nb = kzalloc(sizeof(*iar_nb), GFP_KERNEL); if (!iar_nb) return -ENOMEM; iar_nb->notifier_call = spss_utils_rproc_callback; iar_notif_handle = qcom_register_ssr_notifier("spss", iar_nb); if (IS_ERR_OR_NULL(iar_notif_handle)) { pr_err("register fail for IAR notifier\n"); kfree(iar_nb); iar_notif_handle = NULL; iar_nb = NULL; } for (i = 0 ; i < SPSS_NUM_EVENTS; i++) { init_completion(&spss_events[i]); spss_events_signaled[i] = false; } mutex_init(&event_lock); is_iar_active = false; is_ssr_disabled = false; pr_info("Probe completed successfully, [%s].\n", firmware_name); return 0; } static int spss_remove(struct platform_device *pdev) { spss_utils_destroy_chardev(); spss_destroy_sysfs(spss_dev); if (!iar_notif_handle && !iar_nb) qcom_unregister_ssr_notifier(iar_notif_handle, iar_nb); kfree(iar_nb); iar_nb = 0; kfree(spss_utils_dev); spss_utils_dev = 0; if (cmac_mem != NULL) { iounmap(cmac_mem); cmac_mem = NULL; } return 0; } static const struct of_device_id spss_match_table[] = { { .compatible = "qcom,spss-utils", }, { }, }; static struct platform_driver spss_driver = { .probe = spss_probe, .remove = spss_remove, .driver = { .name = DEVICE_NAME, .of_match_table = of_match_ptr(spss_match_table), }, }; /*==========================================================================*/ /* Driver Init/Exit */ /*==========================================================================*/ static int __init spss_init(void) { int ret = 0; ret = platform_driver_register(&spss_driver); if (ret) pr_err("register platform driver failed, ret [%d]\n", ret); return ret; } late_initcall(spss_init); /* start after PIL driver */ static void __exit spss_exit(void) { platform_driver_unregister(&spss_driver); } module_exit(spss_exit) MODULE_SOFTDEP("pre: qcom_spss"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Secure Processor Utilities");