/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MTK_AEE_FEATURE #include #endif #include #include #include #include /* #include "mach/irqs.h" */ #include #include #include #include #include #include "emi_mpu.h" #include "emi_module.h" #include #include #include #include #include #include static int Violation_PortID = MASTER_ALL; #define EMI_MPU_TEST 0 #if EMI_MPU_TEST char mpu_test_buffer[0x20000] __aligned(PAGE_SIZE); #endif #define MAX_EMI_MPU_STORE_CMD_LEN 128 #define AXI_VIO_MONITOR_TIME (1 * HZ) #define DRIVER_ATTR(_name, _mode, _show, _store) \ struct driver_attribute driver_attr_##_name = \ __ATTR(_name, _mode, _show, _store) static void __iomem *CEN_EMI_BASE; static void __iomem *EMI_MPU_BASE; static unsigned long long vio_addr; static unsigned int emi_physical_offset; static const char *UNKNOWN_MASTER = "unknown"; static DEFINE_SPINLOCK(emi_mpu_lock); static int __match_id(u32 axi_id, int tbl_idx, u32 port_ID) { int found = 0; if (((axi_id & mst_tbl[tbl_idx].id_mask) == mst_tbl[tbl_idx].id_val)) { if (port_ID == mst_tbl[tbl_idx].port) { pr_debug("Violation master name is %s.\n", mst_tbl[tbl_idx].name); found += 1; } } return found; } static int isetocunt; static void emi_mpu_set_violation_port(int portID) { if (isetocunt == 0) { Violation_PortID = portID; isetocunt = 1; } else if (isetocunt == 2) { isetocunt = 0; } } int emi_mpu_get_violation_port(void) { int ret; pr_info("[EMI MPU] emi_mpu_get_violation_port\n"); if (isetocunt == 0) { pr_info("[EMI MPU] EMI did not set Port ID\n"); isetocunt = 2; return MASTER_ALL; } ret = Violation_PortID; isetocunt = 0; if (ret >= MASTER_ALL) pr_info("[EMI MPU] Port ID: %d is an invalid Port ID\n", ret); else Violation_PortID = MASTER_ALL; return ret; } static const char *__id2name(u32 id) { int i; u32 axi_ID; u32 port_ID; axi_ID = (id >> 3) & 0x00001FFF; port_ID = id & 0x00000007; emi_mpu_set_violation_port(port_ID); pr_info("[EMI MPU] axi_id = %x, port_id = %x\n", axi_ID, port_ID); for (i = 0; i < ARRAY_SIZE(mst_tbl); i++) { if (__match_id(axi_ID, i, port_ID)) return mst_tbl[i].name; } return (char *)UNKNOWN_MASTER; } static void __clear_emi_mpu_vio(void) { u32 dbg_s, dbg_t, i; /* clear violation status */ for (i = 0; i < EMI_MPU_DOMAIN_NUMBER; i++) { mt_reg_sync_writel(0xFFFFFFFF, EMI_MPUD_ST(i)); /* Region abort violation */ mt_reg_sync_writel(0x3, EMI_MPUD_ST2(i)); /* Out of region abort violation */ } /* clear debug info */ mt_reg_sync_writel(0x80000000, EMI_MPUS); dbg_s = readl(IOMEM(EMI_MPUS)); dbg_t = readl(IOMEM(EMI_MPUT)); if (dbg_s) { pr_info("Fail to clear EMI MPU violation\n"); pr_info("EMI_MPUS = %x, EMI_MPUT = %x", dbg_s, dbg_t); } } static int mpu_check_violation(void) { u32 dbg_s, dbg_t, dbg_t_2nd, err; u32 master_ID, domain_ID, wr_vio, wr_oo_vio; s32 region; const char *master_name; dbg_s = readl(IOMEM(EMI_MPUS)); dbg_t = readl(IOMEM(EMI_MPUT)); dbg_t_2nd = readl(IOMEM(EMI_MPUT_2ND)); vio_addr = (dbg_t + (((unsigned long long)(dbg_t_2nd & 0xF)) << 32) + emi_physical_offset); pr_info("Clear status.\n"); master_ID = (dbg_s & 0x0000FFFF); domain_ID = (dbg_s >> 21) & 0x0000000F; wr_vio = (dbg_s >> 29) & 0x00000003; wr_oo_vio = (dbg_s >> 27) & 0x00000003; region = (dbg_s >> 16) & 0x1F; /*TBD: print the abort region*/ pr_info("EMI MPU violation.\n"); pr_info("[EMI MPU] Debug info start ----------------------------------------\n"); pr_info("EMI_MPUS = %x, EMI_MPUT = %x, EMI_MPUT_2ND = %x.\n", dbg_s, dbg_t, dbg_t_2nd); pr_info("Current process is \"%s \" (pid: %i).\n", current->comm, current->pid); pr_info("Violation address is 0x%llx.\n", vio_addr); pr_info("Violation master ID is 0x%x.\n", master_ID); /*print out the murderer name*/ master_name = __id2name(master_ID); pr_info("Violation domain ID is 0x%x.\n", domain_ID); if (wr_vio == 1) pr_info("Write violation.\n"); else if (wr_vio == 2) pr_info("Read violation.\n"); else pr_info("Strange write / read violation value = %d.\n", wr_vio); pr_info("Corrupted region is %d\n\r", region); if (wr_oo_vio == 1) pr_info("Write out of range violation.\n"); else if (wr_oo_vio == 2) pr_info("Read out of range violation.\n"); pr_info("[EMI MPU] Debug info end------------------------------------------\n"); #ifdef CONFIG_MTK_AEE_FEATURE if (wr_vio != 0) { if (((master_ID & 0x7) == MASTER_MDMCU) || ((master_ID & 0x7) == MASTER_MDDMA)) { char str[60] = "0"; char *pstr = str; err = sprintf(pstr, "EMI_MPUS = 0x%x, ADDR = 0x%llx", dbg_s, vio_addr); exec_ccci_kern_func_by_md_id(0, ID_MD_MPU_ASSERT, str, strlen(str)); if (err > 0) pr_info("[EMI MPU] MPU violation trigger MD str=%s strlen(str)=%d\n", str, (int)strlen(str)); } aee_kernel_exception("EMI MPU", "%sEMI_MPUS = 0x%x,EMI_MPUT = 0x%x,EMI_MPUT_2ND = 0x%x,vio_addr = 0x%llx\n%s%s\n", "EMI MPU violation.\n", dbg_s, dbg_t, dbg_t_2nd, vio_addr, "CRDISPATCH_KEY:EMI MPU Violation Issue/", master_name); } #endif __clear_emi_mpu_vio(); /* mt_devapc_clear_emi_violation(); */ return 0; } /*EMI MPU violation handler*/ static irqreturn_t mpu_violation_irq(int irq, void *dev_id) { pr_info("It's a MPU violation.\n"); mpu_check_violation(); return IRQ_HANDLED; } #if defined(CONFIG_MTK_PASR) /* Acquire DRAM Setting for PASR/DPD */ static unsigned int get_segment_nr(unsigned int rk_index) { unsigned int rank_size; unsigned int num_of_1; unsigned int i; rank_size = get_rank_size(rk_index) / get_ch_num(); num_of_1 = 0; for (i = 0; i < 32; i++) { if (rank_size & 0x1) num_of_1++; rank_size = rank_size >> 1; } if (num_of_1 > 1) return 6; else return 8; } #if 0 void acquire_dram_setting(struct basic_dram_setting *pasrdpd) { unsigned int ch, rk; pasrdpd->channel_nr = get_ch_num(); for (ch = 0; ch < MAX_CHANNELS; ch++) { for (rk = 0; rk < MAX_RANKS; rk++) { if ((ch >= pasrdpd->channel_nr) || (rk >= get_rk_num())) { pasrdpd->channel[ch].rank[rk].valid_rank = false; pasrdpd->channel[ch].rank[rk].rank_size = 0; pasrdpd->channel[ch].rank[rk].segment_nr = 0; continue; } pasrdpd->channel[ch].rank[rk].valid_rank = true; pasrdpd->channel[ch].rank[rk].rank_size = get_rank_size(rk) / (pasrdpd->channel_nr); pasrdpd->channel[ch].rank[rk].segment_nr = get_segment_nr(rk); } } } #endif #endif /* * emi_mpu_set_region_protection: protect a region. * @start: start address of the region * @end: end address of the region * @region: EMI MPU region id * @access_permission: EMI MPU access permission * Return 0 for success, otherwise negative status code. */ int emi_mpu_set_region_protection(unsigned long long start, unsigned long long end, int region, unsigned int access_permission) { int ret = 0; unsigned int start_align; unsigned int end_align; unsigned long flags; struct arm_smccc_res res; access_permission = access_permission & 0x4FFFFFF; access_permission = access_permission | ((region & 0x1F)<<27); start_align = (unsigned int) (start >> 16); end_align = (unsigned int) (end >> 16); spin_lock_irqsave(&emi_mpu_lock, flags); arm_smccc_smc(MTK_SIP_KERNEL_EMIMPU_SET, start_align,end_align, access_permission, 0, 0, 0, 0, &res); spin_unlock_irqrestore(&emi_mpu_lock, flags); return ret; } EXPORT_SYMBOL(emi_mpu_set_region_protection); static ssize_t emi_mpu_show(struct device_driver *driver, char *buf) { ssize_t ret; unsigned long long start, end; unsigned int reg_value; int i, j, region; static const char *permission[7] = { "No", "S_RW", "S_RW_NS_R", "S_RW_NS_W", "S_R_NS_R", "FORBIDDEN", "S_R_NS_RW" }; ret = 0; for (region = 0; region < EMI_MPU_REGION_NUMBER; region++) { start = ((unsigned long long)mt_emi_reg_read(EMI_MPU_SA(region)) << 16) + emi_physical_offset; end = ((unsigned long long)mt_emi_reg_read(EMI_MPU_EA(region)) << 16) + emi_physical_offset; if (ret >= PAGE_SIZE) break; ret += snprintf(buf + ret, PAGE_SIZE - ret, "R%d-> 0x%llx to 0x%llx\n", region, start, end+0xFFFF); /* for(i=0; i<(EMI_MPU_DOMAIN_NUMBER/8); i++) { */ /* only print first 8 domain */ for (i = 0; i < 1; i++) { reg_value = mt_emi_reg_read(EMI_MPU_APC(region, i*8)); for (j = 0; j < 8; j++) { if (ret < PAGE_SIZE) { ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s, ", permission[((reg_value >> (j*3)) & 0x7)]); } if (((j == 3) || (j == 7)) && (ret < PAGE_SIZE)) ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); } } if (ret < PAGE_SIZE) ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); } #if EMI_MPU_TEST { int temp; temp = (*((volatile unsigned int *)(mpu_test_buffer+0x10000))); pr_info("mpu_test_buffer+10000 = 0x%x\n", temp); } #endif return strlen(buf); } static ssize_t emi_mpu_store(struct device_driver *driver, const char *buf, size_t count) { int i; unsigned long long start_addr = 0; unsigned long long end_addr = 0; unsigned long region = 0; unsigned long long access_permission = 0; char *command; char *ptr; char *token[6]; ssize_t ret; if ((strlen(buf) + 1) > MAX_EMI_MPU_STORE_CMD_LEN) { pr_info("emi_mpu_store command overflow."); return count; } pr_info("emi_mpu_store: %s\n", buf); command = kmalloc((size_t) MAX_EMI_MPU_STORE_CMD_LEN, GFP_KERNEL); if (!command) return count; strncpy(command, buf, (size_t) MAX_EMI_MPU_STORE_CMD_LEN); ptr = (char *)buf; if (!strncmp(buf, EN_MPU_STR, strlen(EN_MPU_STR))) { i = 0; while (ptr != NULL) { ptr = strsep(&command, " "); token[i] = ptr; pr_devel("token[%d] = %s\n", i, token[i]); i++; } for (i = 0; i < 5; i++) pr_devel("token[%d] = %s\n", i, token[i]); /* kernel standardization * start_addr = simple_strtoul(token[1], &token[1], 16); * end_addr = simple_strtoul(token[2], &token[2], 16); * region = simple_strtoul(token[3], &token[3], 16); * access_permission = simple_strtoul(token[4], &token[4], 16); */ ret = kstrtoull(token[1], 16, &start_addr); if (ret != 0) pr_info("kstrtoul fails to parse start_addr"); ret = kstrtoull(token[2], 16, &end_addr); if (ret != 0) pr_info("kstrtoul fails to parse end_addr"); ret = kstrtoul(token[3], 10, (unsigned long *)®ion); if (ret != 0) pr_info("kstrtoul fails to parse region"); ret = kstrtoull(token[4], 16, &access_permission); if (ret != 0) pr_info("kstrtoull fails to parse access_permission"); emi_mpu_set_region_protection(start_addr, end_addr, region, access_permission); pr_info("Set EMI_MPU: start: 0x%llx, end: 0x%llx, region: %lx, permission: 0x%llx.\n", start_addr, end_addr, region, access_permission); } else if (!strncmp(buf, DIS_MPU_STR, strlen(DIS_MPU_STR))) { i = 0; while (ptr != NULL) { ptr = strsep(&command, " "); token[i] = ptr; pr_devel("token[%d] = %s\n", i, token[i]); i++; } /* kernel standardization * start_addr = simple_strtoul(token[1], &token[1], 16); * end_addr = simple_strtoul(token[2], &token[2], 16); * region = simple_strtoul(token[3], &token[3], 16); */ ret = kstrtoull(token[1], 16, &start_addr); if (ret != 0) pr_info("kstrtoul fails to parse start_addr"); ret = kstrtoull(token[2], 16, &end_addr); if (ret != 0) pr_info("kstrtoul fails to parse end_addr"); ret = kstrtoul(token[3], 10, (unsigned long *)®ion); if (ret != 0) pr_info("kstrtoul fails to parse region"); emi_mpu_set_region_protection(0x0, 0x0, region, SET_ACCESS_PERMISSON(UNLOCK, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION)); pr_info("set EMI MPU: start: 0x%x, end: 0x%x, region: %lx, permission: 0x%llx\n", 0, 0, region, SET_ACCESS_PERMISSON(UNLOCK, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION, NO_PROTECTION)); } else { pr_info("Unknown emi_mpu command.\n"); } kfree(command); return count; } DRIVER_ATTR(mpu_config, 0644, emi_mpu_show, emi_mpu_store); #if ENABLE_AP_REGION #define AP_REGION_ID 23 static void protect_ap_region(void) { unsigned int ap_mem_mpu_id; unsigned int ap_mem_mpu_attr; unsigned long long kernel_base; phys_addr_t dram_size; kernel_base = emi_physical_offset; /* dram_size = get_max_DRAM_size();*/ dram_size = memblock_end_of_DRAM() - 1 - memblock_start_of_DRAM(); ap_mem_mpu_id = AP_REGION_ID; ap_mem_mpu_attr = SET_ACCESS_PERMISSON(LOCK, FORBIDDEN, SEC_R_NSEC_RW, FORBIDDEN, NO_PROTECTION, FORBIDDEN, FORBIDDEN, FORBIDDEN, NO_PROTECTION); emi_mpu_set_region_protection(kernel_base, (kernel_base+dram_size-1), ap_mem_mpu_id, ap_mem_mpu_attr); } #endif static struct platform_driver emi_mpu_ctrl = { .driver = { .name = "emi_mpu_ctrl", .bus = &platform_bus_type, .owner = THIS_MODULE, }, .id_table = NULL, }; static int __init emi_mpu_mod_init(void) { int ret; unsigned int mpu_irq = 0; #if EMI_MPU_TEST pr_info("[EMI_MPU] ptr_phyical: %llx\n", (unsigned long long)__pa(mpu_test_buffer)); *((volatile unsigned int *)(mpu_test_buffer+0x10000)) = 0xdeaddead; #endif pr_info("Initialize EMI MPU.\n"); isetocunt = 0; /* DTS version */ CEN_EMI_BASE = mt_cen_emi_base_get(); EMI_MPU_BASE = mt_emi_mpu_base_get(); if (CEN_EMI_BASE == NULL) { pr_info("[EMI MPU] can't get CEN_EMI_BASE\n"); return -1; } if (EMI_MPU_BASE == NULL) { pr_info("[EMI MPU] can't get EMI_MPU_BASE\n"); return -1; } mt_emi_reg_base_set(CEN_EMI_BASE); emi_physical_offset = 0x40000000; pr_info("[EMI MPU] EMI_MPUS = 0x%x\n", readl(IOMEM(EMI_MPUS))); pr_info("[EMI MPU] EMI_MPUT = 0x%x\n", readl(IOMEM(EMI_MPUT))); pr_info("[EMI MPU] EMI_MPUT_2ND = 0x%x\n", readl(IOMEM(EMI_MPUT_2ND))); if (readl(IOMEM(EMI_MPUS))) { pr_info("[EMI MPU] get MPU violation in driver init\n"); mpu_check_violation(); } else { __clear_emi_mpu_vio(); } /* * NoteXXX: Interrupts of violation (including SPC in SMI, or EMI MPU) * are triggered by the device APC. * Need to share the interrupt with the SPC driver. */ mpu_irq = mt_emi_mpu_irq_get(); if (mpu_irq != 0) { ret = request_irq(mpu_irq, (irq_handler_t)mpu_violation_irq, IRQF_TRIGGER_LOW | IRQF_SHARED, "mt_emi_mpu", &emi_mpu_ctrl); if (ret != 0) { pr_info("Fail to request EMI_MPU interrupt. Error = %d.\n", ret); return ret; } } #if ENABLE_AP_REGION protect_ap_region(); #endif #if !defined(USER_BUILD_KERNEL) /* register driver and create sysfs files */ ret = platform_driver_register(&emi_mpu_ctrl); if (ret) pr_info("Fail to register EMI_MPU driver.\n"); ret = driver_create_file(&emi_mpu_ctrl.driver, &driver_attr_mpu_config); if (ret) pr_info("Fail to create MPU config sysfs file.\n"); #endif return 0; } static void __exit emi_mpu_mod_exit(void) { } module_init(emi_mpu_mod_init); module_exit(emi_mpu_mod_exit);