569 lines
16 KiB
C
Executable file
569 lines
16 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "sysmmu: " fmt
|
|
|
|
#include <linux/smc.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/sec_debug.h>
|
|
|
|
#include "samsung-iommu.h"
|
|
|
|
#define MMU_TLB_INFO(n) (0x2000 + ((n) * 0x20))
|
|
#define MMU_CAPA1_NUM_TLB_SET(reg) (((reg) >> 16) & 0xFF)
|
|
#define MMU_CAPA1_NUM_TLB_WAY(reg) ((reg) & 0xFF)
|
|
#define MMU_CAPA1_SET_TLB_READ_ENTRY(tid, set, way, line) \
|
|
((set) | ((way) << 8) | \
|
|
((line) << 16) | ((tid) << 20))
|
|
#define MMU_CAPA1_V82_SET_TLB_READ_ENTRY(tid, set, way, line) \
|
|
((set) | ((way) << 10) | \
|
|
((line) << 18) | ((tid) << 20))
|
|
|
|
#define MMU_TLB_ENTRY_VALID(reg) ((reg) >> 28)
|
|
#define MMU_SBB_ENTRY_VALID(reg) ((reg) >> 28)
|
|
#define MMU_VADDR_FROM_TLB(reg, idx) (((reg) & 0xFFFFC | (idx) & 0x3) << 12)
|
|
#define MMU_PADDR_FROM_TLB(reg) (((reg) & 0xFFFFFF) << 12)
|
|
#define MMU_VADDR_FROM_SBB(reg) (((reg) & 0xFFFFF) << 12)
|
|
#define MMU_PADDR_FROM_SBB(reg) (((reg) & 0x3FFFFFF) << 10)
|
|
|
|
#define REG_MMU_INT_STATUS 0x060
|
|
#define REG_MMU_INT_CLEAR 0x064
|
|
#define REG_MMU_FAULT_VA 0x070
|
|
#define REG_MMU_FAULT_TRANS_INFO 0x078
|
|
#define REG_MMU_FAULT_RW_MASK GENMASK(20, 20)
|
|
#define IS_READ_FAULT(x) (((x) & REG_MMU_FAULT_RW_MASK) == 0)
|
|
|
|
#define SYSMMU_FAULT_PTW_ACCESS 0
|
|
#define SYSMMU_FAULT_PAGE_FAULT 1
|
|
#define SYSMMU_FAULT_ACCESS 3
|
|
#define SYSMMU_FAULT_SECURITY 4
|
|
#define SYSMMU_FAULT_UNKNOWN 5
|
|
|
|
#define SYSMMU_FAULTS_NUM (SYSMMU_FAULT_UNKNOWN + 1)
|
|
|
|
#if IS_ENABLED(CONFIG_EXYNOS_CONTENT_PATH_PROTECTION)
|
|
#define SMC_DRM_SEC_SMMU_INFO (0x820020D0)
|
|
/* secure SysMMU SFR access */
|
|
enum sec_sysmmu_sfr_access_t {
|
|
SEC_SMMU_SFR_READ,
|
|
SEC_SMMU_SFR_WRITE,
|
|
};
|
|
|
|
#define is_secure_info_fail(x) ((((x) >> 16) & 0xffff) == 0xdead)
|
|
static inline u32 read_sec_info(unsigned int addr)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
arm_smccc_smc(SMC_DRM_SEC_SMMU_INFO,
|
|
(unsigned long)addr, 0, SEC_SMMU_SFR_READ, 0, 0, 0, 0,
|
|
&res);
|
|
if (is_secure_info_fail(res.a0))
|
|
pr_err("Invalid value returned, %#lx\n", res.a0);
|
|
|
|
return (u32)res.a0;
|
|
}
|
|
#else
|
|
static inline u32 read_sec_info(unsigned int addr)
|
|
{
|
|
return 0xdead;
|
|
}
|
|
#endif
|
|
|
|
static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
|
|
"PTW ACCESS FAULT",
|
|
"PAGE FAULT",
|
|
"RESERVED",
|
|
"ACCESS FAULT",
|
|
"SECURITY FAULT",
|
|
"UNKNOWN FAULT"
|
|
};
|
|
|
|
static int sysmmu_fault_type[SYSMMU_FAULTS_NUM] = {
|
|
IOMMU_FAULT_REASON_WALK_EABT,
|
|
IOMMU_FAULT_REASON_PTE_FETCH,
|
|
IOMMU_FAULT_REASON_UNKNOWN,
|
|
IOMMU_FAULT_REASON_ACCESS,
|
|
IOMMU_FAULT_REASON_PERMISSION,
|
|
IOMMU_FAULT_REASON_UNKNOWN,
|
|
};
|
|
|
|
struct samsung_sysmmu_fault_info {
|
|
struct sysmmu_drvdata *drvdata;
|
|
struct iommu_fault_event event;
|
|
};
|
|
|
|
static inline u32 __sysmmu_get_intr_status(struct sysmmu_drvdata *data,
|
|
bool is_secure)
|
|
{
|
|
if (is_secure)
|
|
return read_sec_info(data->secure_base + REG_MMU_INT_STATUS);
|
|
else
|
|
return readl_relaxed(data->sfrbase + REG_MMU_INT_STATUS);
|
|
}
|
|
|
|
static inline u32 __sysmmu_get_fault_address(struct sysmmu_drvdata *data,
|
|
bool is_secure, int vmid)
|
|
{
|
|
if (is_secure)
|
|
return read_sec_info(MMU_SEC_VM_REG(data, IDX_FAULT_VA, vmid));
|
|
else
|
|
return readl_relaxed(MMU_VM_REG(data, IDX_FAULT_VA, vmid));
|
|
}
|
|
|
|
static inline void sysmmu_tlb_compare(phys_addr_t pgtable,
|
|
int idx_sub, u32 vpn, u32 ppn)
|
|
{
|
|
sysmmu_pte_t *entry;
|
|
unsigned long vaddr = MMU_VADDR_FROM_TLB((unsigned long)vpn, idx_sub);
|
|
unsigned long paddr = MMU_PADDR_FROM_TLB((unsigned long)ppn);
|
|
unsigned long phys = 0;
|
|
|
|
if (!pgtable)
|
|
return;
|
|
|
|
entry = section_entry(phys_to_virt(pgtable), vaddr);
|
|
|
|
if (lv1ent_section(entry)) {
|
|
phys = section_phys(entry);
|
|
} else if (lv1ent_page(entry)) {
|
|
entry = page_entry(entry, vaddr);
|
|
|
|
if (lv2ent_large(entry))
|
|
phys = lpage_phys(entry);
|
|
else if (lv2ent_small(entry))
|
|
phys = spage_phys(entry);
|
|
} else {
|
|
pr_crit(">> Invalid address detected! entry: %#lx",
|
|
(unsigned long)*entry);
|
|
return;
|
|
}
|
|
|
|
if (paddr != phys) {
|
|
pr_crit(">> TLB mismatch detected!\n");
|
|
pr_crit(" TLB: %#010lx, PT entry: %#010lx\n", paddr, phys);
|
|
}
|
|
}
|
|
|
|
static inline void sysmmu_sbb_compare(u32 sbb_vpn, u32 sbb_link,
|
|
phys_addr_t pgtable)
|
|
{
|
|
sysmmu_pte_t *entry;
|
|
unsigned long vaddr = MMU_VADDR_FROM_SBB((unsigned long)sbb_vpn);
|
|
unsigned long paddr = MMU_PADDR_FROM_SBB((unsigned long)sbb_link);
|
|
unsigned long phys = 0;
|
|
|
|
if (!pgtable)
|
|
return;
|
|
|
|
entry = section_entry(phys_to_virt(pgtable), vaddr);
|
|
|
|
if (lv1ent_page(entry)) {
|
|
phys = lv2table_base(entry);
|
|
|
|
if (paddr != phys) {
|
|
pr_crit(">> SBB mismatch detected!\n");
|
|
pr_crit(" entry addr: %lx / SBB addr %lx\n",
|
|
paddr, phys);
|
|
}
|
|
} else {
|
|
pr_crit(">> Invalid address detected! entry: %#lx",
|
|
(unsigned long)*entry);
|
|
}
|
|
}
|
|
|
|
static inline
|
|
unsigned int dump_tlb_entry_port_type(struct sysmmu_drvdata *drvdata,
|
|
phys_addr_t pgtable,
|
|
int idx_way, int idx_set, int idx_sub)
|
|
{
|
|
u32 attr = readl_relaxed(MMU_REG(drvdata, IDX_TLB_ATTR));
|
|
|
|
if (MMU_TLB_ENTRY_VALID(attr)) {
|
|
u32 vpn, ppn;
|
|
|
|
vpn = readl_relaxed(MMU_REG(drvdata, IDX_TLB_VPN)) + idx_sub;
|
|
ppn = readl_relaxed(MMU_REG(drvdata, IDX_TLB_PPN));
|
|
|
|
pr_crit("[%02d][%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n",
|
|
idx_way, idx_set, vpn, ppn, attr);
|
|
sysmmu_tlb_compare(pgtable, idx_sub, vpn, ppn);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int get_sysmmu_tlb_read_val(struct sysmmu_drvdata *drvdata,
|
|
int tlb, int set, int way, int line)
|
|
{
|
|
if (drvdata->version >= MMU_VERSION(8, 2, 0))
|
|
return MMU_CAPA1_V82_SET_TLB_READ_ENTRY(tlb, set, way, line);
|
|
|
|
return MMU_CAPA1_SET_TLB_READ_ENTRY(tlb, set, way, line);
|
|
}
|
|
|
|
#define MMU_NUM_TLB_SUBLINE 4
|
|
static unsigned int dump_tlb_entry_port(struct sysmmu_drvdata *drvdata,
|
|
phys_addr_t pgtable,
|
|
int tlb, int way, int num_set)
|
|
{
|
|
int cnt = 0;
|
|
int set, line, val;
|
|
|
|
for (set = 0; set < num_set; set++) {
|
|
for (line = 0; line < MMU_NUM_TLB_SUBLINE; line++) {
|
|
val = get_sysmmu_tlb_read_val(drvdata, tlb, set, way, line);
|
|
writel_relaxed(val, MMU_REG(drvdata, IDX_TLB_READ));
|
|
cnt += dump_tlb_entry_port_type(drvdata, pgtable,
|
|
way, set, line);
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
DEFINE_STATIC_PR_AUTO_NAME_ONCE(iommu, ASL4);
|
|
|
|
static inline void dump_sysmmu_tlb_status(struct sysmmu_drvdata *drvdata,
|
|
phys_addr_t pgtable)
|
|
{
|
|
int t, i;
|
|
u32 capa0, capa1, info;
|
|
unsigned int cnt;
|
|
int num_tlb, num_port, num_sbb;
|
|
void __iomem *sfrbase = drvdata->sfrbase;
|
|
|
|
capa0 = readl_relaxed(sfrbase + REG_MMU_CAPA0_V7);
|
|
capa1 = readl_relaxed(sfrbase + REG_MMU_CAPA1_V7);
|
|
|
|
num_tlb = MMU_CAPA1_NUM_TLB(capa1);
|
|
num_port = MMU_CAPA1_NUM_PORT(capa1);
|
|
num_sbb = 1 << MMU_CAPA_NUM_SBB_ENTRY(capa0);
|
|
|
|
pr_crit("SysMMU has %d TLBs, %d ports, %d sbb entries\n",
|
|
num_tlb, num_port, num_sbb);
|
|
|
|
for (t = 0; t < num_tlb; t++) {
|
|
int num_set, num_way;
|
|
|
|
info = readl_relaxed(sfrbase + MMU_TLB_INFO(t));
|
|
num_way = MMU_CAPA1_NUM_TLB_WAY(info);
|
|
num_set = MMU_CAPA1_NUM_TLB_SET(info);
|
|
|
|
pr_crit("TLB.%d has %d way, %d set.\n", t, num_way, num_set);
|
|
pr_crit("------------- TLB[WAY][SET][ENTRY] -------------\n");
|
|
for (i = 0, cnt = 0; i < num_way; i++)
|
|
cnt += dump_tlb_entry_port(drvdata, pgtable,
|
|
t, i, num_set);
|
|
}
|
|
if (!cnt)
|
|
pr_auto_name(iommu, ">> No Valid TLB Entries\n");
|
|
|
|
pr_crit("--- SBB(Second-Level Page Table Base Address Buffer) ---\n");
|
|
for (i = 0, cnt = 0; i < num_sbb; i++) {
|
|
u32 sbb_vpn, sbblink;
|
|
|
|
writel_relaxed(i, MMU_REG(drvdata, IDX_SBB_READ));
|
|
sbb_vpn = readl_relaxed(MMU_REG(drvdata, IDX_SBB_VPN));
|
|
|
|
if (MMU_SBB_ENTRY_VALID(sbb_vpn)) {
|
|
sbblink = readl_relaxed(MMU_REG(drvdata, IDX_SBB_LINK));
|
|
|
|
pr_crit("[%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x",
|
|
i, sbb_vpn, sbblink,
|
|
readl_relaxed(MMU_REG(drvdata, IDX_SBB_ATTR)));
|
|
sysmmu_sbb_compare(sbb_vpn, sbblink, pgtable);
|
|
cnt++;
|
|
}
|
|
}
|
|
if (!cnt)
|
|
pr_auto_name(iommu, ">> No Valid SBB Entries\n");
|
|
}
|
|
|
|
static inline void dump_sysmmu_status(struct sysmmu_drvdata *drvdata,
|
|
phys_addr_t pgtable, int vmid)
|
|
{
|
|
int info;
|
|
void __iomem *sfrbase = drvdata->sfrbase;
|
|
|
|
info = MMU_RAW_VER(readl_relaxed(sfrbase + REG_MMU_VERSION));
|
|
|
|
pr_crit("ADDR: (VA: %p), MMU_CTRL: %#010x, PT_BASE: %#010x, VMID:%d\n",
|
|
sfrbase,
|
|
readl_relaxed(sfrbase + REG_MMU_CTRL),
|
|
readl_relaxed(MMU_REG(drvdata, IDX_FLPT_BASE)), vmid);
|
|
pr_crit("VERSION %d.%d.%d, MMU_CFG: %#010x, MMU_STATUS: %#010x\n",
|
|
MMU_MAJ_VER(info), MMU_MIN_VER(info), MMU_REV_VER(info),
|
|
readl_relaxed(sfrbase + REG_MMU_CFG),
|
|
readl_relaxed(sfrbase + REG_MMU_STATUS));
|
|
|
|
if (drvdata->has_vcr)
|
|
pr_crit("MMU_CTRL_VM: %#010x, MMU_CFG_VM: %#010x\n",
|
|
readl_relaxed(sfrbase + REG_MMU_CTRL_VM(vmid)),
|
|
readl_relaxed(sfrbase + REG_MMU_CFG_VM(vmid)));
|
|
|
|
dump_sysmmu_tlb_status(drvdata, pgtable);
|
|
}
|
|
|
|
static void sysmmu_show_secure_fault_information(struct sysmmu_drvdata *drvdata,
|
|
int intr_type, unsigned long fault_addr)
|
|
{
|
|
const char *port_name = NULL;
|
|
unsigned int info;
|
|
phys_addr_t pgtable;
|
|
unsigned int sfrbase = drvdata->secure_base;
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
|
|
char temp_buf[SZ_128];
|
|
#endif
|
|
|
|
pgtable = read_sec_info(MMU_SEC_REG(drvdata, IDX_SEC_FLPT_BASE));
|
|
pgtable <<= PAGE_SHIFT;
|
|
|
|
info = read_sec_info(MMU_SEC_REG(drvdata, IDX_FAULT_TRANS_INFO));
|
|
|
|
of_property_read_string(drvdata->dev->of_node, "port-name", &port_name);
|
|
|
|
pr_auto_name_once(iommu);
|
|
pr_auto_name(iommu, "----------------------------------------------------------\n");
|
|
pr_auto_name(iommu, "From [%s], SysMMU %s %s at %#010lx (page table @ %pa)\n",
|
|
port_name ? port_name : dev_name(drvdata->dev),
|
|
IS_READ_FAULT(info) ? "READ" : "WRITE",
|
|
sysmmu_fault_name[intr_type], fault_addr, &pgtable);
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
|
|
snprintf(temp_buf, SZ_128, "%s %s %s at %#010lx (%pa)",
|
|
port_name ? port_name : dev_name(drvdata->dev),
|
|
IS_READ_FAULT(info) ? "READ" : "WRITE",
|
|
sysmmu_fault_name[intr_type], fault_addr, &pgtable);
|
|
secdbg_exin_set_sysmmu(temp_buf);
|
|
#endif
|
|
|
|
if (intr_type == SYSMMU_FAULT_UNKNOWN) {
|
|
pr_auto_name(iommu, "The fault is not caused by this System MMU.\n");
|
|
pr_auto_name(iommu, "Please check IRQ and SFR base address.\n");
|
|
goto finish;
|
|
}
|
|
|
|
pr_auto_name(iommu, "AxID: %#x, AxLEN: %#x\n", info & 0xFFFF, (info >> 16) & 0xF);
|
|
|
|
if (!pfn_valid(pgtable >> PAGE_SHIFT)) {
|
|
pr_auto_name(iommu, "Page table base is not in a valid memory region\n");
|
|
pgtable = 0;
|
|
}
|
|
|
|
if (intr_type == SYSMMU_FAULT_PTW_ACCESS) {
|
|
pr_auto_name(iommu, "System MMU has failed to access page table\n");
|
|
pgtable = 0;
|
|
}
|
|
|
|
info = MMU_RAW_VER(read_sec_info(sfrbase + REG_MMU_VERSION));
|
|
|
|
pr_auto_name(iommu, "ADDR: %#x, MMU_CTRL: %#010x, PT_BASE: %#010x\n",
|
|
sfrbase,
|
|
read_sec_info(sfrbase + REG_MMU_CTRL),
|
|
read_sec_info(MMU_SEC_REG(drvdata, IDX_SEC_FLPT_BASE)));
|
|
pr_auto_name(iommu, "VERSION %d.%d.%d, MMU_CFG: %#010x, MMU_STATUS: %#010x\n",
|
|
MMU_MAJ_VER(info), MMU_MIN_VER(info), MMU_REV_VER(info),
|
|
read_sec_info(sfrbase + REG_MMU_CFG),
|
|
read_sec_info(sfrbase + REG_MMU_STATUS));
|
|
|
|
finish:
|
|
pr_auto_name(iommu, "----------------------------------------------------------\n");
|
|
pr_auto_name_disable(iommu);
|
|
}
|
|
|
|
static void sysmmu_show_fault_info_simple(struct sysmmu_drvdata *drvdata,
|
|
int intr_type, unsigned long fault_addr,
|
|
phys_addr_t *pt)
|
|
{
|
|
const char *port_name = NULL;
|
|
u32 info;
|
|
phys_addr_t pgtable;
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
|
|
char temp_buf[SZ_128];
|
|
#endif
|
|
|
|
pgtable = readl_relaxed(MMU_REG(drvdata, IDX_FLPT_BASE));
|
|
pgtable <<= PAGE_SHIFT;
|
|
|
|
info = readl_relaxed(MMU_REG(drvdata, IDX_FAULT_TRANS_INFO));
|
|
|
|
of_property_read_string(drvdata->dev->of_node, "port-name", &port_name);
|
|
|
|
pr_auto_name_on(pt, iommu, "From [%s], SysMMU %s %s at %#010lx (pgtable @ %pa, AxID: %#x)\n",
|
|
port_name ? port_name : dev_name(drvdata->dev),
|
|
IS_READ_FAULT(info) ? "READ" : "WRITE",
|
|
sysmmu_fault_name[intr_type], fault_addr, &pgtable, info & 0xFFFF);
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
|
|
/* only for called by sysmmu_show_fault_information */
|
|
if (pt) {
|
|
snprintf(temp_buf, SZ_128, "%s %s %s at %#010lx (%pa %#x)\n",
|
|
port_name ? port_name : dev_name(drvdata->dev),
|
|
IS_READ_FAULT(info) ? "READ" : "WRITE",
|
|
sysmmu_fault_name[intr_type], fault_addr, &pgtable, info & 0xFFFF);
|
|
secdbg_exin_set_sysmmu(temp_buf);
|
|
}
|
|
#endif
|
|
|
|
if (pt)
|
|
*pt = pgtable;
|
|
}
|
|
|
|
static void sysmmu_show_fault_information(struct sysmmu_drvdata *drvdata,
|
|
int intr_type, unsigned long fault_addr, int vmid)
|
|
{
|
|
phys_addr_t pgtable;
|
|
|
|
pr_auto_name_once(iommu);
|
|
pr_auto_name(iommu, "----------------------------------------------------------\n");
|
|
sysmmu_show_fault_info_simple(drvdata, intr_type, fault_addr, &pgtable);
|
|
|
|
if (intr_type == SYSMMU_FAULT_UNKNOWN) {
|
|
pr_crit("The fault is not caused by this System MMU.\n");
|
|
pr_crit("Please check IRQ and SFR base address.\n");
|
|
goto finish;
|
|
}
|
|
|
|
if (pgtable != drvdata->pgtable)
|
|
pr_auto_name(iommu, "Page table base of driver: %pa\n",
|
|
&drvdata->pgtable);
|
|
|
|
if (!pfn_valid(pgtable >> PAGE_SHIFT)) {
|
|
pr_auto_name(iommu, "Page table base is not in a valid memory region\n");
|
|
pgtable = 0;
|
|
} else {
|
|
sysmmu_pte_t *ent;
|
|
|
|
ent = section_entry(phys_to_virt(pgtable), fault_addr);
|
|
pr_auto_name(iommu, "Lv1 entry: %#010x\n", *ent);
|
|
|
|
if (lv1ent_page(ent)) {
|
|
ent = page_entry(ent, fault_addr);
|
|
pr_auto_name(iommu, "Lv2 entry: %#010x\n", *ent);
|
|
}
|
|
}
|
|
|
|
if (intr_type == SYSMMU_FAULT_PTW_ACCESS) {
|
|
pr_auto_name(iommu, "System MMU has failed to access page table\n");
|
|
pgtable = 0;
|
|
}
|
|
|
|
dump_sysmmu_status(drvdata, pgtable, vmid);
|
|
finish:
|
|
pr_auto_name(iommu, "----------------------------------------------------------\n");
|
|
pr_auto_name_disable(iommu);
|
|
}
|
|
|
|
static void sysmmu_get_interrupt_info(struct sysmmu_drvdata *data,
|
|
int *intr_type, unsigned long *addr,
|
|
int *vmid, bool is_secure)
|
|
{
|
|
*intr_type = __ffs(__sysmmu_get_intr_status(data, is_secure));
|
|
*vmid = *intr_type / 4;
|
|
*intr_type %= 4;
|
|
*addr = __sysmmu_get_fault_address(data, is_secure, *vmid);
|
|
}
|
|
|
|
static void sysmmu_clear_interrupt(struct sysmmu_drvdata *data)
|
|
{
|
|
u32 val = __sysmmu_get_intr_status(data, false);
|
|
|
|
writel(val, data->sfrbase + REG_MMU_INT_CLEAR);
|
|
}
|
|
|
|
irqreturn_t samsung_sysmmu_irq(int irq, void *dev_id)
|
|
{
|
|
int itype, vmid;
|
|
unsigned long addr;
|
|
struct sysmmu_drvdata *drvdata = dev_id;
|
|
bool is_secure = (irq == drvdata->secure_irq);
|
|
|
|
dev_info(drvdata->dev, "[%s] interrupt (%d) happened\n",
|
|
is_secure ? "Secure" : "Non-secure", irq);
|
|
|
|
if (drvdata->async_fault_mode)
|
|
return IRQ_WAKE_THREAD;
|
|
|
|
sysmmu_get_interrupt_info(drvdata, &itype, &addr, &vmid, is_secure);
|
|
if (is_secure)
|
|
sysmmu_show_secure_fault_information(drvdata, itype, addr);
|
|
else
|
|
sysmmu_show_fault_information(drvdata, itype, addr, vmid);
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
static int samsung_sysmmu_fault_notifier(struct device *dev, void *data)
|
|
{
|
|
struct samsung_sysmmu_fault_info *fi;
|
|
struct sysmmu_clientdata *client;
|
|
struct sysmmu_drvdata *drvdata;
|
|
int i, ret, result = 0;
|
|
|
|
fi = (struct samsung_sysmmu_fault_info *)data;
|
|
drvdata = fi->drvdata;
|
|
|
|
client = (struct sysmmu_clientdata *) dev_iommu_priv_get(dev);
|
|
|
|
for (i = 0; i < client->sysmmu_count; i++) {
|
|
if (drvdata == client->sysmmus[i]) {
|
|
ret = iommu_report_device_fault(dev, &fi->event);
|
|
if (ret == -EAGAIN)
|
|
result = ret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
irqreturn_t samsung_sysmmu_irq_thread(int irq, void *dev_id)
|
|
{
|
|
int itype, vmid, ret;
|
|
unsigned long addr;
|
|
struct sysmmu_drvdata *drvdata = dev_id;
|
|
bool is_secure = (irq == drvdata->secure_irq);
|
|
struct iommu_group *group = drvdata->group;
|
|
enum iommu_fault_reason reason;
|
|
struct samsung_sysmmu_fault_info fi = {
|
|
.drvdata = drvdata,
|
|
.event.fault.type = IOMMU_FAULT_DMA_UNRECOV,
|
|
};
|
|
const char *port_name = NULL;
|
|
u32 info;
|
|
|
|
sysmmu_get_interrupt_info(drvdata, &itype, &addr, &vmid, is_secure);
|
|
reason = sysmmu_fault_type[itype];
|
|
|
|
fi.event.fault.event.addr = addr;
|
|
fi.event.fault.event.reason = reason;
|
|
if (reason == IOMMU_FAULT_REASON_PTE_FETCH ||
|
|
reason == IOMMU_FAULT_REASON_PERMISSION)
|
|
fi.event.fault.type = IOMMU_FAULT_PAGE_REQ;
|
|
|
|
ret = iommu_group_for_each_dev(group, &fi,
|
|
samsung_sysmmu_fault_notifier);
|
|
if (ret == -EAGAIN && !is_secure) {
|
|
sysmmu_show_fault_info_simple(drvdata, itype, addr, NULL);
|
|
sysmmu_clear_interrupt(drvdata);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (drvdata->async_fault_mode) {
|
|
if (is_secure)
|
|
sysmmu_show_secure_fault_information(drvdata, itype, addr);
|
|
else
|
|
sysmmu_show_fault_information(drvdata, itype, addr, vmid);
|
|
}
|
|
|
|
info = readl_relaxed(MMU_REG(drvdata, IDX_FAULT_TRANS_INFO));
|
|
of_property_read_string(drvdata->dev->of_node, "port-name", &port_name);
|
|
panic("(%s) From [%s], SysMMU %s %s at %#010lx\n",
|
|
dev_name(drvdata->dev), port_name ? port_name : dev_name(drvdata->dev),
|
|
IS_READ_FAULT(info) ? "READ" : "WRITE", sysmmu_fault_name[itype], addr);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|