// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct emi_mpu_test { unsigned long long dram_start; unsigned long long dram_end; unsigned int region_cnt; unsigned int domain_cnt; unsigned int addr_align; unsigned int ctrl_intf; unsigned int show_region; struct device_driver *driver; }; static struct emi_mpu_test *global_mpu_test; static unsigned int emimpu_read_protection( unsigned int reg_type, unsigned int region, unsigned int dgroup) { struct arm_smccc_res smc_res; arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIMPU_READ, reg_type, region, dgroup, 0, 0, 0, &smc_res); return (unsigned int)smc_res.a0; } static ssize_t emimpu_ctrl_show(struct device_driver *driver, char *buf) { struct emi_mpu_test *mpu_test; ssize_t ret; unsigned int i; unsigned int region; unsigned int apc; unsigned long long start, end; static const char *permission[8] = { "No", "S_RW", "S_RW_NS_R", "S_RW_NS_W", "S_R_NS_R", "FORBIDDEN", "S_R_NS_RW", "NONE" }; mpu_test = global_mpu_test; if (!mpu_test) return -ENXIO; for (ret = 0, region = mpu_test->show_region; region < mpu_test->region_cnt; region++) { start = (unsigned long long)emimpu_read_protection( MTK_EMIMPU_READ_SA, region, 0); start = (start << (mpu_test->addr_align)) + mpu_test->dram_start; end = (unsigned long long)emimpu_read_protection( MTK_EMIMPU_READ_EA, region, 0); end = (end << (mpu_test->addr_align)) + mpu_test->dram_start; ret += snprintf(buf + ret, PAGE_SIZE - ret, "R%u-> 0x%llx to 0x%llx\n", region, start, end + 0xFFFF); if (ret >= PAGE_SIZE) return strlen(buf); for (i = 0; i < (mpu_test->domain_cnt / 8); i++) { apc = emimpu_read_protection( MTK_EMIMPU_READ_APC, region, i); ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s, %s, %s, %s\n%s, %s, %s, %s\n\n", permission[(apc >> 0) & 0x7], permission[(apc >> 3) & 0x7], permission[(apc >> 6) & 0x7], permission[(apc >> 9) & 0x7], permission[(apc >> 12) & 0x7], permission[(apc >> 15) & 0x7], permission[(apc >> 18) & 0x7], permission[(apc >> 21) & 0x7]); if (ret >= PAGE_SIZE) return strlen(buf); } } return strlen(buf); } static ssize_t emimpu_ctrl_store (struct device_driver *driver, const char *buf, size_t count) { struct emi_mpu_test *mpu_test; char *command, *backup_command; char *ptr, *token[MTK_EMI_MAX_TOKEN]; static struct emimpu_region_t *rg_info; unsigned long long start, end; unsigned long region; unsigned long dgroup; unsigned long apc; int i, j, ret; mpu_test = global_mpu_test; if (!mpu_test) return -EFAULT; if (!(mpu_test->ctrl_intf)) return count; if (!rg_info) { rg_info = kmalloc(sizeof(struct emimpu_region_t), GFP_KERNEL); if (!rg_info) return -ENOMEM; rg_info->apc = kmalloc_array( mpu_test->domain_cnt, sizeof(unsigned int), GFP_KERNEL); if (!(rg_info->apc)) { kfree(rg_info); rg_info = NULL; return -ENOMEM; } rg_info->lock = false; } if ((strlen(buf) + 1) > MTK_EMI_MAX_CMD_LEN) { pr_info("%s: store command overflow\n", __func__); return -EINVAL; } pr_info("%s: store: %s\n", __func__, buf); command = kmalloc((size_t)MTK_EMI_MAX_CMD_LEN, GFP_KERNEL); if (!command) return -ENOMEM; backup_command = command; strncpy(command, buf, (size_t)MTK_EMI_MAX_CMD_LEN); for (i = 0; i < MTK_EMI_MAX_TOKEN; i++) { ptr = strsep(&command, " "); if (!ptr) break; token[i] = ptr; } if (!strncmp(buf, "SHOW", strlen("SHOW"))) { if (i < 2) goto emimpu_ctrl_store_end; pr_info("%s: %s %s\n", __func__, token[0], token[1]); ret = kstrtoul(token[1], 10, ®ion); if (ret != 0) pr_info("%s: fail to parse region\n", __func__); if (region < mpu_test->region_cnt) { mpu_test->show_region = (unsigned int) region; pr_info("%s: show_region to %u\n", __func__, mpu_test->show_region); } } else if (!strncmp(buf, "SET", strlen("SET"))) { if (i < 3) goto emimpu_ctrl_store_end; pr_info("%s: %s %s %s\n", __func__, token[0], token[1], token[2]); ret = kstrtoul(token[1], 10, &dgroup); if (ret != 0) pr_info("%s: fail to parse dgroup\n", __func__); ret = kstrtoul(token[2], 16, &apc); if (ret != 0) pr_info("[MPU] fail to parse apc\n"); if (dgroup < (mpu_test->domain_cnt / 8)) { pr_info("%s: apc[%lu]: 0x%lx\n", __func__, dgroup, apc); for (j = 0; j < 8; j++) rg_info->apc[dgroup * 8 + j] = ((unsigned int)apc >> (3 * j)) & 0x7; } if (dgroup == 0) rg_info->lock = apc & 0x80000000; } else if (!strncmp(buf, "ON", strlen("ON"))) { if (i < 4) goto emimpu_ctrl_store_end; pr_info("%s: %s %s %s %s\n", __func__, token[0], token[1], token[2], token[3]); ret = kstrtoull(token[1], 16, &start); if (ret != 0) pr_info("%s: fail to parse start\n", __func__); ret = kstrtoull(token[2], 16, &end); if (ret != 0) pr_info("%s: fail to parse end\n", __func__); ret = kstrtoul(token[3], 10, ®ion); if (ret != 0) pr_info("%s: fail to parse region\n", __func__); if (region < mpu_test->region_cnt) { rg_info->start = start; rg_info->end = end; rg_info->rg_num = (unsigned int)region; mtk_emimpu_set_protection(rg_info); } } else if (!strncmp(buf, "OFF", strlen("OFF"))) { if (i < 2) goto emimpu_ctrl_store_end; pr_info("%s: %s %s\n", __func__, token[0], token[1]); ret = kstrtoul(token[1], 10, ®ion); if (ret != 0) pr_info("%s: fail to parse region\n", __func__); if (region < mpu_test->region_cnt) { rg_info->rg_num = (unsigned int)region; mtk_emimpu_clear_protection(rg_info); } } else pr_info("%s: unknown store command\n", __func__); emimpu_ctrl_store_end: kfree(backup_command); return count; } static DRIVER_ATTR_RW(emimpu_ctrl); static __init int emimputest_init(void) { struct device_node *node; struct platform_device *pdev; struct emi_mpu_test *mpu_test; int ret; pr_info("emimputest was loaded\n"); node = of_find_compatible_node(NULL, NULL, "mediatek,common-emimpu"); if (!node) { pr_info("emimputest: cannot find emimpu node\n"); return -ENXIO; } pdev = of_find_device_by_node(node); if (!pdev) { pr_info("emimputest: cannot find emimpu pdev\n"); return -ENXIO; } if (!pdev->dev.driver) { pr_info("emimputest: cannot find emimpu driver\n"); return -ENXIO; } mpu_test = kzalloc(sizeof(struct emi_mpu_test), GFP_KERNEL); if (!mpu_test) return -ENOMEM; ret = of_property_read_u32(node, "region_cnt", &(mpu_test->region_cnt)); if (ret) { pr_info("emimputest: no region_cnt\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = of_property_read_u32(node, "domain_cnt", &(mpu_test->domain_cnt)); if (ret) { pr_info("emimputest: no domain_cnt\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = of_property_read_u32(node, "addr_align", &(mpu_test->addr_align)); if (ret) { pr_info("emimputest: no addr_align\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = of_property_read_u64(node, "dram_start", &(mpu_test->dram_start)); if (ret) { pr_info("emimputest: no dram_start\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = of_property_read_u64(node, "dram_end", &(mpu_test->dram_end)); if (ret) { pr_info("emimputest: no dram_end\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = of_property_read_u32(node, "ctrl_intf", &(mpu_test->ctrl_intf)); if (ret) { pr_info("emimputest: no ctrl_intf\n"); ret = -ENXIO; goto free_emi_mpu_test; } ret = driver_create_file(pdev->dev.driver, &driver_attr_emimpu_ctrl); if (ret) { pr_info("emimputest: failed to create file\n"); goto free_emi_mpu_test; } mpu_test->driver = pdev->dev.driver; global_mpu_test = mpu_test; return 0; free_emi_mpu_test: kfree(mpu_test); return ret; } static __exit void emimputest_exit(void) { struct emi_mpu_test *mpu_test = global_mpu_test; pr_info("emimputest was unloaded\n"); driver_remove_file(mpu_test->driver, &driver_attr_emimpu_ctrl); global_mpu_test = NULL; kfree(mpu_test); } module_init(emimputest_init); module_exit(emimputest_exit); MODULE_DESCRIPTION("MediaTek EMI MPU Driver"); MODULE_LICENSE("GPL v2");