// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include "devapc-mtk-common.h" struct mtk_devapc_context { struct clk *devapc_infra_clk; uint32_t devapc_irq; /* HW reg mapped addr */ void __iomem *devapc_pd_base; void __iomem *devapc_ao_base; void __iomem *sramrom_base; struct mtk_devapc_soc *soc; } mtk_devapc_ctx[1]; LIST_HEAD(viocb_list); /************************************************************************** *STATIC FUNCTION **************************************************************************/ static void __iomem *mtk_devapc_pd_get(enum DEVAPC_PD_REG_TYPE pd_type, const struct mtk_devapc_pd_desc *devapc_pds, uint32_t index) { void __iomem *reg = NULL; if (unlikely(devapc_pds == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return NULL; } if (pd_type == VIO_MASK) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_vio_mask_offset + 0x4 * index; } else if (pd_type == VIO_STA) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_vio_sta_offset + 0x4 * index; } else if (pd_type == VIO_DBG0) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_vio_dbg0_offset; } else if (pd_type == VIO_DBG1) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_vio_dbg1_offset; } else if (pd_type == APC_CON) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_apc_con_offset; } else if (pd_type == VIO_SHIFT_STA) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_shift_sta_offset; } else if (pd_type == VIO_SHIFT_SEL) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_shift_sel_offset; } else if (pd_type == VIO_SHIFT_CON) { reg = mtk_devapc_ctx->devapc_pd_base + devapc_pds->pd_shift_con_offset; } return reg; } static void unmask_infra_module_irq(uint32_t module) { uint32_t apc_index = 0; uint32_t apc_bit_index = 0; int vio_max_idx = mtk_devapc_ctx->soc->vio_info->vio_max_idx; void __iomem *reg; if (module > vio_max_idx) { DEVAPC_MSG("%s:%d module overflow!\n", __func__, __LINE__); return; } apc_index = module / (MOD_NO_IN_1_DEVAPC * 2); apc_bit_index = module % (MOD_NO_IN_1_DEVAPC * 2); reg = mtk_devapc_pd_get(VIO_MASK, mtk_devapc_ctx->soc->devapc_pds, apc_index); writel(readl(reg) & (0xFFFFFFFF ^ (1 << apc_bit_index)), reg); } static void mask_infra_module_irq(uint32_t module) { uint32_t apc_index = 0; uint32_t apc_bit_index = 0; int vio_max_idx = mtk_devapc_ctx->soc->vio_info->vio_max_idx; void __iomem *reg; if (module > vio_max_idx) { DEVAPC_MSG("%s:%d module overflow!\n", __func__, __LINE__); return; } apc_index = module / (MOD_NO_IN_1_DEVAPC * 2); apc_bit_index = module % (MOD_NO_IN_1_DEVAPC * 2); reg = mtk_devapc_pd_get(VIO_MASK, mtk_devapc_ctx->soc->devapc_pds, apc_index); writel(readl(reg) | (1 << apc_bit_index), reg); } static int clear_infra_vio_status(uint32_t module) { uint32_t apc_index = 0; uint32_t apc_bit_index = 0; int vio_max_idx = mtk_devapc_ctx->soc->vio_info->vio_max_idx; int sramrom_vio_idx = mtk_devapc_ctx->soc->vio_info->sramrom_vio_idx; void __iomem *reg; if (module > vio_max_idx) { DEVAPC_MSG("%s:%d module overflow!\n", __func__, __LINE__); return -EOVERFLOW; } if (module == sramrom_vio_idx) handle_sramrom_vio(); apc_index = module / (MOD_NO_IN_1_DEVAPC * 2); apc_bit_index = module % (MOD_NO_IN_1_DEVAPC * 2); reg = mtk_devapc_pd_get(VIO_STA, mtk_devapc_ctx->soc->devapc_pds, apc_index); writel((0x1 << apc_bit_index), reg); return 0; } static int check_infra_vio_status(uint32_t module) { uint32_t apc_index = 0; uint32_t apc_bit_index = 0; int vio_max_idx = mtk_devapc_ctx->soc->vio_info->vio_max_idx; void __iomem *reg; if (module > vio_max_idx) { DEVAPC_MSG("%s:%d module overflow!\n", __func__, __LINE__); return -EOVERFLOW; } apc_index = module / (MOD_NO_IN_1_DEVAPC * 2); apc_bit_index = module % (MOD_NO_IN_1_DEVAPC * 2); reg = mtk_devapc_pd_get(VIO_STA, mtk_devapc_ctx->soc->devapc_pds, apc_index); if (readl(reg) & (0x1 << apc_bit_index)) return VIOLATION_TRIGGERED; return 0; } static void print_vio_mask_sta(void) { int i; int vio_mask_sta_num; vio_mask_sta_num = mtk_devapc_ctx->soc->vio_info->vio_mask_sta_num; for (i = 0; i < vio_mask_sta_num; i++) { DEVAPC_DBG_MSG("%s: (%d:0x%x) %s: (%d:0x%x)\n", "INFRA VIO_MASK", i, readl(mtk_devapc_pd_get(VIO_MASK, mtk_devapc_ctx->soc->devapc_pds, i) ), "INFRA VIO_STA", i, readl(mtk_devapc_pd_get(VIO_STA, mtk_devapc_ctx->soc->devapc_pds, i) ) ); } } static void devapc_test_cb(void) { DEVAPC_MSG("%s success !\n", __func__); } static struct devapc_vio_callbacks devapc_test_handle = { .id = DEVAPC_SUBSYS_TEST, .debug_dump = devapc_test_cb, }; static void start_devapc(void) { int i; uint32_t vio_shift_sta; void __iomem *pd_apc_con_reg; void __iomem *pd_vio_shift_sta_reg; const struct mtk_device_info *device_info; DEVAPC_MSG("%s...\n", __func__); pd_apc_con_reg = mtk_devapc_pd_get(APC_CON, mtk_devapc_ctx->soc->devapc_pds, 0); pd_vio_shift_sta_reg = mtk_devapc_pd_get(VIO_SHIFT_STA, mtk_devapc_ctx->soc->devapc_pds, 0); device_info = mtk_devapc_ctx->soc->device_info; if (unlikely(pd_apc_con_reg == NULL || pd_vio_shift_sta_reg == NULL || device_info == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return; } writel(0x80000000, pd_apc_con_reg); print_vio_mask_sta(); DEVAPC_DBG_MSG("Clear INFRA VIO_STA and unmask INFRA VIO_MASK...\n"); vio_shift_sta = readl(pd_vio_shift_sta_reg); if (vio_shift_sta) { DEVAPC_MSG("(Pre) clear VIO_SHIFT_STA = 0x%x\n", vio_shift_sta); writel(vio_shift_sta, pd_vio_shift_sta_reg); DEVAPC_MSG("(Post) clear VIO_SHIFT_STA = 0x%x\n", readl(pd_vio_shift_sta_reg)); } else DEVAPC_MSG("No violation happened before booting kernel\n"); DEVAPC_MSG("Number of devices: %u\n", mtk_devapc_ctx->soc->ndevices); for (i = 0; i < mtk_devapc_ctx->soc->ndevices; i++) { if (true == device_info[i].enable_vio_irq) { unmask_infra_module_irq(i); clear_infra_vio_status(i); } else mask_infra_module_irq(i); } print_vio_mask_sta(); register_devapc_vio_callback(&devapc_test_handle); } static void devapc_violation_triggered(uint32_t vio_idx, uint32_t vio_addr, const char *vio_master) { char subsys_str[48] = {0}; struct devapc_vio_callbacks *viocb; enum infra_subsys_id id = DEVAPC_SUBSYS_RESERVED; const struct mtk_device_info *device_info; struct mtk_devapc_dbg_status *dbg_stat; device_info = mtk_devapc_ctx->soc->device_info; dbg_stat = mtk_devapc_ctx->soc->dbg_stat; if (unlikely(dbg_stat == NULL || device_info == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return; } DEVAPC_MSG("%s, count=%d\n", __func__, mtk_devapc_ctx->soc->vio_info->devapc_vio_trigger_times++); /* mask irq for module index "vio_idx" */ mask_infra_module_irq(vio_idx); /* Dispatch slave owner if APMCU access. Others, dispatch master. */ if (!strncmp(vio_master, "APMCU", 5)) { strncpy(subsys_str, mtk_devapc_ctx->soc->subsys_get(vio_idx), sizeof(subsys_str)); } else { strncpy(subsys_str, vio_master, sizeof(subsys_str)); } subsys_str[sizeof(subsys_str) - 1] = '\0'; /* Callback func for vio master */ if (!strncasecmp(vio_master, "MD", 2)) id = INFRA_SUBSYS_MD; else if (!strncasecmp(vio_master, "CONNSYS", 7)) id = INFRA_SUBSYS_CONN; else if (!strncasecmp(vio_master, "HIFI3", 5)) id = INFRA_SUBSYS_ADSP; else if (!strncasecmp(vio_master, "GCE", 3)) id = INFRA_SUBSYS_GCE; else if (!strncasecmp(vio_master, "APMCU", 5)) id = INFRA_SUBSYS_APMCU; /* enable_ut to test callback */ if (dbg_stat->enable_ut) id = DEVAPC_SUBSYS_TEST; if (id != DEVAPC_SUBSYS_RESERVED) { list_for_each_entry(viocb, &viocb_list, list) { if (viocb->id == id && viocb->debug_dump) viocb->debug_dump(); /* always call clkmgr cb if it's registered */ if (viocb->id == DEVAPC_SUBSYS_CLKMGR && viocb->debug_dump) viocb->debug_dump(); } } if (dbg_stat->enable_KE) { DEVAPC_MSG("Violation master: %s access %s\n", vio_master, device_info[vio_idx].device); DEVAPC_MSG("Device APC Violation Issue/%s", subsys_str); /* Connsys will trigger EE instead of AP KE */ if (id != INFRA_SUBSYS_CONN) BUG(); } else if (dbg_stat->enable_AEE) { /* call mtk aee_kernel_exception */ aee_kernel_exception("[DEVAPC]", "%s %s %s %s, Vio Addr: 0x%x\n%s%s\n", "Violation Master:", vio_master, "Access Violation Slave:", device_info[vio_idx].device, vio_addr, "CRDISPATCH_KEY:Device APC Violation Issue/", subsys_str ); } /* unmask irq for module index "vio_idx" */ unmask_infra_module_irq(vio_idx); } static uint32_t sync_vio_dbg(int shift_bit) { uint32_t shift_count = 0; uint32_t sync_done; void __iomem *pd_vio_shift_sta_reg; void __iomem *pd_vio_shift_sel_reg; void __iomem *pd_vio_shift_con_reg; pd_vio_shift_sta_reg = mtk_devapc_pd_get(VIO_SHIFT_STA, mtk_devapc_ctx->soc->devapc_pds, 0); pd_vio_shift_sel_reg = mtk_devapc_pd_get(VIO_SHIFT_SEL, mtk_devapc_ctx->soc->devapc_pds, 0); pd_vio_shift_con_reg = mtk_devapc_pd_get(VIO_SHIFT_CON, mtk_devapc_ctx->soc->devapc_pds, 0); writel(0x1 << shift_bit, pd_vio_shift_sel_reg); writel(0x1, pd_vio_shift_con_reg); for (shift_count = 0; (shift_count < 100) && ((readl(pd_vio_shift_con_reg) & 0x3) != 0x3); ++shift_count) DEVAPC_DBG_MSG("Syncing VIO DBG0 & DBG1 (%d, %d)\n", shift_bit, shift_count); if ((readl(pd_vio_shift_con_reg) & 0x3) == 0x3) sync_done = 1; else { sync_done = 0; DEVAPC_MSG("sync failed, shift_bit: %d\n", shift_bit); } /* disable shift mechanism */ writel(0x0, pd_vio_shift_con_reg); writel(0x0, pd_vio_shift_sel_reg); writel(0x1 << shift_bit, pd_vio_shift_sta_reg); DEVAPC_DBG_MSG("%s%X, %s%X, %s%X\n", "VIO_SHIFT_STA=0x", readl(pd_vio_shift_sta_reg), "VIO_SHIFT_SEL=0x", readl(pd_vio_shift_sel_reg), "VIO_SHIFT_CON=0x", readl(pd_vio_shift_con_reg)); return sync_done; } static void dump_backtrace(void *passed_regs) { struct pt_regs *regs = passed_regs; DEVAPC_MSG("====== %s ======\n", "Start dumping Device APC violation tracing"); DEVAPC_MSG("****** %s ******\n", "[All IRQ Registers]"); if (regs) show_regs(regs); DEVAPC_MSG("****** %s ******\n", "[All Current Task Stack]"); show_stack(current, NULL); DEVAPC_MSG("====== %s ======\n", "End of dumping Device APC violation tracing"); } static char *perm_to_string(uint32_t perm) { if (perm == 0x0) return "NO_PROTECTION"; else if (perm == 0x1) return "SECURE_RW_ONLY"; else if (perm == 0x2) return "SECURE_RW_NS_R_ONLY"; else if (perm == 0x3) return "FORBIDDEN"; else return "NO_PERM_CTRL"; } static uint32_t get_permission(int vio_index, int domain) { int slave_type; int config_idx; int apc_set_idx; uint32_t ret; struct arm_smccc_res res; const struct mtk_device_info *device_info; device_info = mtk_devapc_ctx->soc->device_info; if (vio_index >= mtk_devapc_ctx->soc->ndevices) return -EOVERFLOW; slave_type = device_info[vio_index].slave_type; config_idx = device_info[vio_index].config_index; DEVAPC_DBG_MSG("%s, slave type = 0x%x, config_idx = 0x%x\n", __func__, slave_type, config_idx); if (slave_type >= E_DAPC_OTHERS_SLAVE || config_idx == -1) { DEVAPC_MSG("%s, cannot get APC\n", __func__); return DEAD; } arm_smccc_smc(MTK_SIP_KERNEL_DAPC_PERM_GET, slave_type, domain, config_idx, 0, 0, 0, 0, &res); ret = res.a0; if (ret == DEAD) { DEVAPC_MSG("%s, param is overflow\n", __func__); return ret; } DEVAPC_DBG_MSG("%s, dump perm = 0x%x\n", __func__, ret); apc_set_idx = config_idx % MOD_NO_IN_1_DEVAPC; ret = (ret & (0x3 << (apc_set_idx * 2))) >> (apc_set_idx * 2); DEVAPC_DBG_MSG("%s, after shipping, dump perm = 0x%x\n", __func__, (ret & 0x3)); return (ret & 0x3); } void register_devapc_vio_callback(struct devapc_vio_callbacks *viocb) { INIT_LIST_HEAD(&viocb->list); list_add_tail(&viocb->list, &viocb_list); } EXPORT_SYMBOL(register_devapc_vio_callback); uint32_t devapc_vio_check(void) { void __iomem *reg; reg = mtk_devapc_pd_get(VIO_SHIFT_STA, mtk_devapc_ctx->soc->devapc_pds, 0); return readl(reg); } EXPORT_SYMBOL(devapc_vio_check); /* * dump_dbg_info - dump all vio dbg info * */ void dump_dbg_info(void) { uint32_t dbg0 = 0; uint32_t write_vio, read_vio; uint32_t vio_addr_high; int i; void __iomem *vio_dbg0_reg, *vio_dbg1_reg; const struct mtk_infra_vio_dbg_desc *vio_dbgs; struct mtk_devapc_vio_info *vio_info; vio_dbg0_reg = mtk_devapc_pd_get(VIO_DBG0, mtk_devapc_ctx->soc->devapc_pds, 0); vio_dbg1_reg = mtk_devapc_pd_get(VIO_DBG1, mtk_devapc_ctx->soc->devapc_pds, 0); vio_dbgs = mtk_devapc_ctx->soc->vio_dbgs; vio_info = mtk_devapc_ctx->soc->vio_info; for (i = 0; i <= vio_info->vio_shift_max_bit; ++i) { if (devapc_vio_check() & (0x1 << i)) { if (sync_vio_dbg(i) == 0) continue; dbg0 = readl(vio_dbg0_reg); vio_info->vio_dbg1 = readl(vio_dbg1_reg); DEVAPC_DBG_MSG("%s vio_dbg0=0x%x, vio_dbg1=0x%x\n", __func__, dbg0, vio_info->vio_dbg1); vio_info->master_id = (dbg0 & vio_dbgs->infra_vio_dbg_mstid) >> vio_dbgs->infra_vio_dbg_mstid_start_bit; vio_info->domain_id = (dbg0 & vio_dbgs->infra_vio_dbg_dmnid) >> vio_dbgs->infra_vio_dbg_dmnid_start_bit; write_vio = (dbg0 & vio_dbgs->infra_vio_dbg_w_vio) >> vio_dbgs->infra_vio_dbg_w_vio_start_bit; read_vio = (dbg0 & vio_dbgs->infra_vio_dbg_r_vio) >> vio_dbgs->infra_vio_dbg_r_vio_start_bit; vio_addr_high = (dbg0 & vio_dbgs->infra_vio_addr_high) >> vio_dbgs->infra_vio_addr_high_start_bit; /* violation information */ DEVAPC_MSG("%s%s%s%s%x %s%x, %s%x, %s%x\n", "Violation(", read_vio == 1?" R":"", write_vio == 1?" W ) - ":" ) - ", "Vio Addr:0x", vio_info->vio_dbg1, "High:0x", vio_addr_high, "Bus ID:0x", vio_info->master_id, "Dom ID:0x", vio_info->domain_id); DEVAPC_MSG("%s - %s%s, %s%i\n", "Violation", "Current Process:", current->comm, "PID:", current->pid); } } } EXPORT_SYMBOL(dump_dbg_info); /* * devapc_extract_vio_dbg - get vio dbg info after sync_vio_dbg * */ static void devapc_extract_vio_dbg(void) { uint32_t dbg0 = 0; uint32_t write_vio, read_vio; uint32_t vio_addr_high; void __iomem *vio_dbg0_reg, *vio_dbg1_reg; const struct mtk_infra_vio_dbg_desc *vio_dbgs; struct mtk_devapc_vio_info *vio_info; vio_dbg0_reg = mtk_devapc_pd_get(VIO_DBG0, mtk_devapc_ctx->soc->devapc_pds, 0); vio_dbg1_reg = mtk_devapc_pd_get(VIO_DBG1, mtk_devapc_ctx->soc->devapc_pds, 0); vio_dbgs = mtk_devapc_ctx->soc->vio_dbgs; vio_info = mtk_devapc_ctx->soc->vio_info; dbg0 = readl(vio_dbg0_reg); vio_info->vio_dbg1 = readl(vio_dbg1_reg); vio_info->master_id = (dbg0 & vio_dbgs->infra_vio_dbg_mstid) >> vio_dbgs->infra_vio_dbg_mstid_start_bit; vio_info->domain_id = (dbg0 & vio_dbgs->infra_vio_dbg_dmnid) >> vio_dbgs->infra_vio_dbg_dmnid_start_bit; write_vio = (dbg0 & vio_dbgs->infra_vio_dbg_w_vio) >> vio_dbgs->infra_vio_dbg_w_vio_start_bit; read_vio = (dbg0 & vio_dbgs->infra_vio_dbg_r_vio) >> vio_dbgs->infra_vio_dbg_r_vio_start_bit; vio_addr_high = (dbg0 & vio_dbgs->infra_vio_addr_high) >> vio_dbgs->infra_vio_addr_high_start_bit; /* violation information */ DEVAPC_MSG("%s%s%s%s%x %s%x, %s%x, %s%x\n", "Violation(", read_vio == 1?" R":"", write_vio == 1?" W ) - ":" ) - ", "Vio Addr:0x", vio_info->vio_dbg1, "High:0x", vio_addr_high, "Bus ID:0x", vio_info->master_id, "Dom ID:0x", vio_info->domain_id); DEVAPC_MSG("%s - %s%s, %s%i\n", "Violation", "Current Process:", current->comm, "PID:", current->pid); } void handle_sramrom_vio(void) { size_t sramrom_vio_sta; int rw, sramrom_vio; struct arm_smccc_res res; const struct mtk_sramrom_sec_vio_desc *sramrom_vios; struct mtk_devapc_vio_info *vio_info; sramrom_vios = mtk_devapc_ctx->soc->sramrom_sec_vios; vio_info = mtk_devapc_ctx->soc->vio_info; arm_smccc_smc(MTK_SIP_KERNEL_CLR_SRAMROM_VIO, 0, 0, 0, 0, 0, 0, 0, &res); sramrom_vio = res.a0; sramrom_vio_sta = res.a1; vio_info->vio_dbg1 = res.a2; if (sramrom_vio == SRAM_VIOLATION) DEVAPC_MSG("%s, SRAM violation is triggered\n", __func__); else if (sramrom_vio == ROM_VIOLATION) DEVAPC_MSG("%s, ROM violation is triggered\n", __func__); else { DEVAPC_MSG("SRAMROM violation is not triggered\n"); return; } vio_info->master_id = (sramrom_vio_sta & sramrom_vios->sramrom_sec_vio_id_mask) >> sramrom_vios->sramrom_sec_vio_id_shift; vio_info->domain_id = (sramrom_vio_sta & sramrom_vios->sramrom_sec_vio_domain_mask) >> sramrom_vios->sramrom_sec_vio_domain_shift; rw = (sramrom_vio_sta & sramrom_vios->sramrom_sec_vio_rw_mask) >> sramrom_vios->sramrom_sec_vio_rw_shift; DEVAPC_MSG("%s, %s: 0x%x, %s: 0x%x, rw: %s, vio addr: 0x%x\n", __func__, "master_id", vio_info->master_id, "domain_id", vio_info->domain_id, rw ? "Write" : "Read", vio_info->vio_dbg1 ); } static irqreturn_t devapc_violation_irq(int irq_number, void *dev_id) { int i, device_count; uint32_t perm; const char *vio_master; struct pt_regs *regs = get_irq_regs(); const struct mtk_device_info *device_info; struct mtk_devapc_vio_info *vio_info; uint32_t shift_bit, vio_shift_sta; uint32_t (*shift_group_get)(uint32_t vio_idx); device_info = mtk_devapc_ctx->soc->device_info; vio_info = mtk_devapc_ctx->soc->vio_info; shift_group_get = mtk_devapc_ctx->soc->shift_group_get; if (irq_number != mtk_devapc_ctx->devapc_irq) { DEVAPC_MSG("(ERROR) irq_number %d is not registered\n", irq_number); return IRQ_NONE; } print_vio_mask_sta(); if (!shift_group_get) dump_dbg_info(); device_count = mtk_devapc_ctx->soc->ndevices; /* checking and showing violation normal slaves */ for (i = 0; i < device_count; i++) { if (device_info[i].enable_vio_irq == true && check_infra_vio_status(i) == VIOLATION_TRIGGERED) { /* check vio_shift when enable_vio_irq and vio triggerd */ if (shift_group_get) { vio_shift_sta = devapc_vio_check(); shift_bit = shift_group_get(i); DEVAPC_DBG_MSG("%s:0x%x, %s:%d, %s:%d\n", "vio_shift_sta", vio_shift_sta, "shift_bit", shift_bit, "vio_shift_max_bit", vio_info->vio_shift_max_bit); if ((shift_bit <= vio_info->vio_shift_max_bit) && (vio_shift_sta & (0x1 << shift_bit))) { DEVAPC_MSG("%s:0x%x is matched with %s:%d\n", "vio_shift_sta", vio_shift_sta, "shift_bit", shift_bit); if (!sync_vio_dbg(shift_bit)) continue; devapc_extract_vio_dbg(); /* * Ensure that violation info are written before * further operations */ smp_mb(); } else DEVAPC_MSG("%s:0x%x is not matched with %s:%d\n", "vio_shift_sta", vio_shift_sta, "shift_bit", shift_bit); } clear_infra_vio_status(i); perm = get_permission(i, vio_info->domain_id); vio_master = mtk_devapc_ctx->soc->master_get( vio_info->master_id, vio_info->vio_dbg1, i); if (vio_master == NULL) vio_master = "UNKNOWN"; DEVAPC_MSG("%s %s %s %s (%s=%d)\n", "Violation Master:", vio_master, "Access Violation Slave:", device_info[i].device, "vio idx", i); DEVAPC_MSG("Permission: %s\n", perm_to_string(perm)); devapc_violation_triggered(i, vio_info->vio_dbg1, vio_master); } } dump_backtrace(regs); return IRQ_HANDLED; } static const char *mtk_sid_to_str(int sid) { if (sid == INFRA_SUBSYS_MD) return "INFRA_SUBSYS_MD"; else if (sid == INFRA_SUBSYS_CONN) return "INFRA_SUBSYS_CONN"; else if (sid == INFRA_SUBSYS_ADSP) return "INFRA_SUBSYS_ADSP"; else if (sid == INFRA_SUBSYS_GCE) return "INFRA_SUBSYS_GCE"; else if (sid == DEVAPC_SUBSYS_CLKMGR) return "DEVAPC_SUBSYS_CLKMGR"; else if (sid == DEVAPC_SUBSYS_TEST) return "DEVAPC_SUBSYS_TEST"; return "UNKNOWN_SUBSYS"; } static void devapc_ut(uint32_t cmd) { uint32_t offset = 0x88; void __iomem *devapc_ao_base, *sramrom_base; struct devapc_vio_callbacks *viocb; devapc_ao_base = mtk_devapc_ctx->devapc_ao_base; sramrom_base = mtk_devapc_ctx->sramrom_base; DEVAPC_MSG("%s, test violation..., cmd = %d\n", __func__, cmd); if (cmd == DEVAPC_UT_DAPC_VIO) { if (unlikely(devapc_ao_base == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return; } DEVAPC_MSG("%s, devapc_ao_infra_base = 0x%x\n", __func__, readl(devapc_ao_base)); DEVAPC_MSG("%s, test done, it should generate violation!\n", __func__); } else if (cmd == DEVAPC_UT_SRAM_VIO) { if (unlikely(sramrom_base == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return; } DEVAPC_MSG("%s, sramrom_base = 0x%x\n", __func__, readl(sramrom_base + offset)); DEVAPC_MSG("%s, test done, it should generate violation!\n", __func__); } else if (cmd == DEVAPC_UT_DUMP_SUBSYS_CB) { DEVAPC_MSG("DEVAPC dump subsys cb:\n"); list_for_each_entry(viocb, &viocb_list, list) { if (viocb->id != DEVAPC_SUBSYS_RESERVED && viocb->debug_dump) { DEVAPC_MSG("\t%s is registered\n", mtk_sid_to_str(viocb->id)); } } } else { DEVAPC_MSG("%s, cmd(0x%x) not supported\n", __func__, cmd); } } #ifdef CONFIG_DEVAPC_SWP_SUPPORT static struct devapc_swp_context { void __iomem *devapc_swp_base; bool swp_enable; bool swp_clr; bool swp_rw; uint32_t swp_phy_addr; uint32_t swp_rg; uint32_t swp_wr_val; uint32_t swp_wr_mask; } devapc_swp_ctx[1]; static ssize_t set_swp_addr_show(struct device_driver *driver, char *buf) { return snprintf(buf, PAGE_SIZE, "%s:%s\n\t%s:%s\n\t%s:0x%x\n\t%s:0x%x\n\t%s:0x%x\n\t%s:0x%x\n", "devapc_swp", devapc_swp_ctx->swp_enable ? "enable" : "disable", "swp_rw", devapc_swp_ctx->swp_rw ? "write" : "read", "swp_physical_addr", devapc_swp_ctx->swp_phy_addr, "swp_rg", devapc_swp_ctx->swp_rg, "swp_wr_val", devapc_swp_ctx->swp_wr_val, "swp_wr_mask", devapc_swp_ctx->swp_wr_mask ); } static ssize_t set_swp_addr_store(struct device_driver *driver, const char *buf, size_t count) { char *cmd_str, *param_str; unsigned int param; int err; pr_info(PFX "buf: %s", buf); cmd_str = strsep((char **)&buf, " "); if (!cmd_str) return -EINVAL; param_str = strsep((char **)&buf, " "); if (!param_str) return -EINVAL; err = kstrtou32(param_str, 16, ¶m); if (err) return err; if (!strncmp(cmd_str, "enable_swp", sizeof("enable_swp"))) { devapc_swp_ctx->swp_enable = (param != 0); pr_info(PFX "devapc_swp_enable = %s\n", devapc_swp_ctx->swp_enable ? "enable" : "disable"); writel(param, devapc_swp_ctx->devapc_swp_base); if (!devapc_swp_ctx->swp_enable) devapc_swp_ctx->swp_phy_addr = 0x0; } else if (!strncmp(cmd_str, "set_swp_clr", sizeof("set_swp_clr"))) { pr_info(PFX "set swp clear: 0x%x\n", param); devapc_swp_ctx->swp_clr = (param != 0); if (devapc_swp_ctx->swp_clr) writel(0x1 << DEVAPC_SWP_CON_CLEAR, devapc_swp_ctx->devapc_swp_base); } else if (!strncmp(cmd_str, "set_swp_rw", sizeof("set_swp_rw"))) { pr_info(PFX "set swp r/w: %s\n", param ? "write" : "read"); devapc_swp_ctx->swp_rw = (param != 0); if (devapc_swp_ctx->swp_rw) writel(0x1 << DEVAPC_SWP_CON_RW, devapc_swp_ctx->devapc_swp_base); } else if (!strncmp(cmd_str, "set_swp_addr", sizeof("set_swp_addr"))) { pr_info(PFX "set swp physical addr: 0x%x\n", param); devapc_swp_ctx->swp_phy_addr = param; writel(devapc_swp_ctx->swp_phy_addr, devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_SA_OFFSET); } else if (!strncmp(cmd_str, "set_swp_rg", sizeof("set_swp_rg"))) { pr_info(PFX "set swp range: 0x%x\n", param); devapc_swp_ctx->swp_rg = param; writel(devapc_swp_ctx->swp_rg, devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_RG_OFFSET); } else if (!strncmp(cmd_str, "set_swp_wr_val", sizeof("set_swp_wr_val"))) { pr_info(PFX "set swp write value: 0x%x\n", param); devapc_swp_ctx->swp_wr_val = param; writel(devapc_swp_ctx->swp_wr_val, devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_WR_VAL_OFFSET); } else if (!strncmp(cmd_str, "set_swp_wr_mask", sizeof("set_swp_wr_mask"))) { pr_info(PFX "set swp write mask: 0x%x\n", param); devapc_swp_ctx->swp_wr_mask = param; writel(devapc_swp_ctx->swp_wr_mask, devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_WR_MASK_OFFSET); } else return -EINVAL; return count; } static DRIVER_ATTR_RW(set_swp_addr); #endif /* CONFIG_DEVAPC_SWP_SUPPORT */ int mtk_devapc_probe(struct platform_device *pdev, struct mtk_devapc_soc *soc) { struct device_node *node = pdev->dev.of_node; int ret; DEVAPC_MSG("driver registered\n"); if (IS_ERR(node)) { pr_err(PFX "cannot find device node\n"); return -ENODEV; } mtk_devapc_ctx->soc = soc; mtk_devapc_ctx->devapc_pd_base = of_iomap(node, DT_DEVAPC_PD_IDX); mtk_devapc_ctx->devapc_ao_base = of_iomap(node, DT_DEVAPC_AO_IDX); mtk_devapc_ctx->sramrom_base = of_iomap(node, DT_SRAMROM_IDX); mtk_devapc_ctx->devapc_irq = irq_of_parse_and_map(node, DT_DEVAPC_PD_IDX); DEVAPC_DBG_MSG("devapc_pd_base: %p, IRQ: %d\n", mtk_devapc_ctx->devapc_pd_base, mtk_devapc_ctx->devapc_irq); /* CCF (Common Clock Framework) */ mtk_devapc_ctx->devapc_infra_clk = devm_clk_get(&pdev->dev, "devapc-infra-clock"); if (IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) pr_info(PFX "(Infra) Cannot get devapc clock from CCF, error(%ld)\n", PTR_ERR(mtk_devapc_ctx->devapc_infra_clk)); if (!IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) { if (clk_prepare_enable(mtk_devapc_ctx->devapc_infra_clk)) { pr_err(PFX "Cannot enable devapc clock\n"); return -EINVAL; } } start_devapc(); ret = request_irq(mtk_devapc_ctx->devapc_irq, (irq_handler_t)devapc_violation_irq, IRQF_TRIGGER_NONE, "devapc", NULL); if (ret) { pr_err(PFX "Failed to request devapc irq, ret(%d)\n", ret); return ret; } #ifdef CONFIG_DEVAPC_SWP_SUPPORT devapc_swp_ctx->devapc_swp_base = of_iomap(node, DT_DEVAPC_SWP_IDX); ret = driver_create_file(pdev->dev.driver, &driver_attr_set_swp_addr); if (ret) pr_info(PFX "create SWP sysfs file failed, ret:%d\n", ret); #endif return 0; } int mtk_devapc_remove(struct platform_device *dev) { if (!IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) clk_disable_unprepare(mtk_devapc_ctx->devapc_infra_clk); return 0; } ssize_t mtk_devapc_dbg_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { int len; char msg_buf[1024] = {0}; char *p = msg_buf; struct mtk_devapc_dbg_status *dbg_stat; dbg_stat = mtk_devapc_ctx->soc->dbg_stat; if (unlikely(dbg_stat == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return -EINVAL; } devapc_log("DEVAPC debug status:\n"); devapc_log("\tenable_ut = %d\n", dbg_stat->enable_ut); devapc_log("\tenable_KE = %d\n", dbg_stat->enable_KE); devapc_log("\tenable_AEE = %d\n", dbg_stat->enable_AEE); devapc_log("\tenable_dapc = %d\n", dbg_stat->enable_dapc); devapc_log("\n"); len = p - msg_buf; return simple_read_from_buffer(buffer, count, ppos, msg_buf, len); } ssize_t mtk_devapc_dbg_write(struct file *file, const char __user *buffer, size_t count, loff_t *data) { char input[32] = {0}; char *cmd_str = NULL; char *parm_str = NULL; char *pinput = NULL; /* pointer to input */ unsigned long param = 0; uint32_t ret, len; int err = 0, apc_set_idx; long slave_type = 0, domain = 0, index = 0; struct arm_smccc_res res; struct mtk_devapc_dbg_status *dbg_stat; dbg_stat = mtk_devapc_ctx->soc->dbg_stat; if (unlikely(dbg_stat == NULL)) { pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); return -EINVAL; } len = (count < (sizeof(input) - 1)) ? count : (sizeof(input) - 1); if (copy_from_user(input, buffer, len)) { DEVAPC_MSG("copy from user failed!\n"); return -EFAULT; } input[len] = '\0'; pinput = input; cmd_str = strsep(&pinput, " "); if (cmd_str == NULL) return -EINVAL; parm_str = strsep(&pinput, " "); if (parm_str == NULL) return -EINVAL; err = kstrtol(parm_str, 10, ¶m); if (err != 0) return err; if (!strncmp(cmd_str, "enable_ut", sizeof("enable_ut"))) { dbg_stat->enable_ut = (param != 0); DEVAPC_MSG("debapc_dbg_stat->enable_ut = %s\n", dbg_stat->enable_ut ? "enable" : "disable"); return count; } else if (!strncmp(cmd_str, "enable_KE", sizeof("enable_KE"))) { dbg_stat->enable_KE = (param != 0); DEVAPC_MSG("debapc_dbg_stat->enable_KE = %s\n", dbg_stat->enable_KE ? "enable" : "disable"); return count; } else if (!strncmp(cmd_str, "enable_AEE", sizeof("enable_AEE"))) { dbg_stat->enable_AEE = (param != 0); DEVAPC_MSG("debapc_dbg_stat->enable_AEE = %s\n", dbg_stat->enable_AEE ? "enable" : "disable"); return count; } else if (!strncmp(cmd_str, "enable_dapc", sizeof("enable_dapc"))) { dbg_stat->enable_dapc = (param != 0); DEVAPC_MSG("debapc_dbg_stat->enable_dapc = %s\n", dbg_stat->enable_dapc ? "enable" : "disable"); return count; } else if (!strncmp(cmd_str, "devapc_ut", sizeof("devapc_ut"))) { if (dbg_stat->enable_ut) devapc_ut(param); else DEVAPC_MSG("devapc_ut is not enabled\n"); return count; } else if (!strncmp(cmd_str, "dump_apc", sizeof("dump_apc"))) { if (!dbg_stat->enable_dapc) { DEVAPC_MSG("dump_apc is not enabled\n"); return -EINVAL; } /* slave_type is already parse before */ slave_type = param; if (slave_type >= E_DAPC_OTHERS_SLAVE || err) { DEVAPC_MSG("Wrong slave type(%lu), err(0x%x)\n", slave_type, err); return -EFAULT; } parm_str = strsep(&pinput, " "); if (parm_str != NULL) err = kstrtol(parm_str, 10, &domain); else domain = E_DOMAIN_OTHERS; if (domain >= E_DOMAIN_OTHERS || err) { DEVAPC_MSG("Wrong domain type(%lu), err(0x%x)\n", domain, err); return -EFAULT; } parm_str = strsep(&pinput, " "); if (parm_str != NULL) err = kstrtol(parm_str, 10, &index); else index = 0xFFFFFFFF; if (index > mtk_devapc_ctx->soc->vio_info->vio_cfg_max_idx || err) { DEVAPC_MSG("Wrong index(%lu), err(0x%x)\n", index, err); return -EFAULT; } DEVAPC_MSG("slave_type = %lu\n", slave_type); DEVAPC_MSG("domain id = %lu\n", domain); DEVAPC_MSG("slave config_idx = %lu\n", index); arm_smccc_smc(MTK_SIP_KERNEL_DAPC_PERM_GET, slave_type, domain, index, 0, 0, 0, 0, &res); ret = res.a0; if (ret == DEAD) { DEVAPC_MSG("%s, param is overflow\n", __func__); return -EOVERFLOW; } DEVAPC_MSG("dump perm = 0x%x\n", ret); apc_set_idx = index % MOD_NO_IN_1_DEVAPC; ret = (ret & (0x3 << (apc_set_idx * 2))) >> (apc_set_idx * 2); DEVAPC_MSG("The permission is %s\n", perm_to_string((ret & 0x3))); return count; } else { return -EINVAL; } return count; }