kernel_samsung_a34x-permissive/drivers/soc/mediatek/devapc/devapc-mtk-common.c
2024-04-28 15:51:13 +02:00

1194 lines
30 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 MediaTek Inc.
*/
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/sched/debug.h>
#include <linux/clk.h>
#include <linux/fs.h>
#include <linux/arm-smccc.h>
#include <mt-plat/mtk_secure_api.h>
#include <mt-plat/devapc_public.h>
#include <mt-plat/aee.h>
#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, &param);
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, &param);
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;
}