unplugged-kernel/drivers/misc/mediatek/emi_mpu/mt6739/emi_mpu.c

586 lines
15 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/list.h>
#ifdef CONFIG_MTK_AEE_FEATURE
#include <mt-plat/aee.h>
#endif
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <mt-plat/mtk_device_apc.h>
#include <mt-plat/sync_write.h>
/* #include "mach/irqs.h" */
#include <mt-plat/dma.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <mt-plat/mtk_io.h>
#include "emi_mpu.h"
#include "emi_module.h"
#include <mt-plat/mtk_ccci_common.h>
#include <linux/delay.h>
#include <emi_bwl.h>
#include <linux/memblock.h>
#include <linux/arm-smccc.h>
#include <linux/soc/mediatek/mtk_sip_svc.h>
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 *)&region);
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 *)&region);
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);