[COMMON] iommu/exynos: support secure sysmmu interrupt
authorJanghyuck Kim <janghyuck.kim@samsung.com>
Sat, 6 Aug 2016 07:20:47 +0000 (16:20 +0900)
committerSangwook Ju <sw.ju@samsung.com>
Mon, 14 May 2018 10:45:19 +0000 (19:45 +0900)
Abnormal situation like page fault can be happened when sysmmu is
operating in secure context. To ease debugging with maintaining secure
state, secure interrupt handler is added. In this handler, useful
information of sysmmu status can be obtained through exynos_smc().

Change-Id: I066f70f655e41eda9ba5d7f2d8dd83f1cea94176
Signed-off-by: Janghyuck Kim <janghyuck.kim@samsung.com>
drivers/iommu/exynos-iommu.c
drivers/iommu/exynos-iommu.h

index 750c4b1e00722e596ff0b452faee56a70cce7ebc..87ee91d4bb319831cd2ab44c09816fb0fb5b7513 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/slab.h>
+#include <linux/smc.h>
 #include <linux/swap.h>
 #include <linux/swapops.h>
 
@@ -91,6 +92,19 @@ static inline void pgtable_flush(void *vastart, void *vaend)
        __dma_flush_area(vastart, vaend - vastart);
 }
 
+#define is_secure_info_fail(x) ((((x) >> 16) & 0xffff) == 0xdead)
+static u32 __secure_info_read(unsigned int addr)
+{
+       u32 ret;
+
+       ret = exynos_smc(SMC_DRM_SEC_SMMU_INFO, (unsigned long)addr,
+                       0, SEC_SMMU_SFR_READ);
+       if (is_secure_info_fail(ret))
+               pr_err("Invalid value returned, %#x\n", ret);
+
+       return ret;
+}
+
 static bool has_sysmmu_capable_pbuf(void __iomem *sfrbase)
 {
        unsigned long cfg = readl_relaxed(sfrbase + REG_MMU_CAPA);
@@ -289,6 +303,64 @@ void dump_sysmmu_status(void __iomem *sfrbase)
        dump_sysmmu_tlb(sfrbase);
 }
 
+static void show_secure_fault_information(struct sysmmu_drvdata *drvdata,
+                                  int flags, unsigned long fault_addr)
+{
+       unsigned int info;
+       phys_addr_t pgtable;
+       int fault_id = SYSMMU_FAULT_ID(flags);
+       unsigned int sfrbase = drvdata->securebase;
+
+       pgtable = __secure_info_read(sfrbase + REG_PT_BASE_PPN);
+       pgtable <<= PAGE_SHIFT;
+
+       info = __secure_info_read(sfrbase + REG_FAULT_TRANS_INFO);
+
+       pr_crit("----------------------------------------------------------\n");
+       pr_crit("%s %s %s at %#010lx (page table @ %pa)\n",
+               dev_name(drvdata->sysmmu),
+               (flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ",
+               sysmmu_fault_name[fault_id], fault_addr, &pgtable);
+
+       if (fault_id == 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;
+       }
+
+       pr_crit("AxID: %#x, AxLEN: %#x\n", info & 0xFFFF, (info >> 16) & 0xF);
+
+       if (fault_id == SYSMMU_FAULT_PTW_ACCESS)
+               pr_crit("System MMU has failed to access page table\n");
+
+       if (!pfn_valid(pgtable >> PAGE_SHIFT)) {
+               pr_crit("Page table base is not in a valid memory region\n");
+       } else {
+               sysmmu_pte_t *ent;
+               ent = section_entry(phys_to_virt(pgtable), fault_addr);
+               pr_crit("Lv1 entry: %#010x\n", *ent);
+
+               if (lv1ent_page(ent)) {
+                       ent = page_entry(ent, fault_addr);
+                       pr_crit("Lv2 entry: %#010x\n", *ent);
+               }
+       }
+
+       info = MMU_RAW_VER(__secure_info_read(sfrbase + REG_MMU_VERSION));
+
+       pr_crit("ADDR: %#x, MMU_CTRL: %#010x, PT_BASE: %#010x\n",
+               sfrbase,
+               __secure_info_read(sfrbase + REG_MMU_CTRL),
+               __secure_info_read(sfrbase + REG_PT_BASE_PPN));
+       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),
+               __secure_info_read(sfrbase + REG_MMU_CFG),
+               __secure_info_read(sfrbase + REG_MMU_STATUS));
+
+finish:
+       pr_crit("----------------------------------------------------------\n");
+}
+
 static void show_fault_information(struct sysmmu_drvdata *drvdata,
                                   int flags, unsigned long fault_addr)
 {
@@ -349,6 +421,41 @@ finish:
 }
 
 #define REG_INT_STATUS_WRITE_BIT 16
+
+irqreturn_t exynos_sysmmu_irq_secure(int irq, void *dev_id)
+{
+       struct sysmmu_drvdata *drvdata = dev_id;
+       unsigned int itype = 0;
+       unsigned long addr = -1;
+       int flags = 0;
+
+       dev_err(drvdata->sysmmu, "Secure irq occured!\n");
+       if (!drvdata->securebase) {
+               dev_err(drvdata->sysmmu, "Unknown interrupt occurred\n");
+               BUG();
+       } else {
+               dev_err(drvdata->sysmmu, "Secure base = %#lx\n",
+                               (unsigned long)drvdata->securebase);
+       }
+
+       itype =  __ffs(__secure_info_read(drvdata->securebase + REG_INT_STATUS));
+       if (itype >= REG_INT_STATUS_WRITE_BIT) {
+               itype -= REG_INT_STATUS_WRITE_BIT;
+               flags = IOMMU_FAULT_WRITE;
+       }
+
+       addr = __secure_info_read(drvdata->securebase + REG_FAULT_ADDR);
+       flags |= SYSMMU_FAULT_FLAG(itype);
+
+       show_secure_fault_information(drvdata, flags, addr);
+
+       atomic_notifier_call_chain(&drvdata->fault_notifiers, addr, &flags);
+
+       BUG();
+
+       return IRQ_HANDLED;
+}
+
 static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
 {
        struct sysmmu_drvdata *drvdata = dev_id;
@@ -415,6 +522,40 @@ static int get_hw_version(struct device *dev, void __iomem *sfrbase)
        return ret;
 }
 
+static int __init __sysmmu_secure_irq_init(struct device *sysmmu,
+                                    struct sysmmu_drvdata *drvdata)
+{
+       struct platform_device *pdev = to_platform_device(sysmmu);
+       u32 secure_reg;
+       int ret;
+
+       ret = platform_get_irq(pdev, 1);
+       if (ret <= 0) {
+               dev_err(sysmmu, "Unable to find secure IRQ resource\n");
+               return -EINVAL;
+       }
+       dev_info(sysmmu, "Registering secure irq %d\n", ret);
+
+       ret = devm_request_irq(sysmmu, ret, exynos_sysmmu_irq_secure, 0,
+                       dev_name(sysmmu), drvdata);
+       if (ret) {
+               dev_err(sysmmu, "Failed to register secure irq handler\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(sysmmu->of_node,
+                               "sysmmu,secure_base", &secure_reg);
+       if (!ret) {
+               drvdata->securebase = secure_reg;
+               dev_info(sysmmu, "Secure base = %#x\n", drvdata->securebase);
+       } else {
+               dev_err(sysmmu, "Failed to get secure register\n");
+               return ret;
+       }
+
+       return ret;
+}
+
 static int __init sysmmu_parse_dt(struct device *sysmmu,
                                struct sysmmu_drvdata *drvdata)
 {
@@ -436,6 +577,15 @@ static int __init sysmmu_parse_dt(struct device *sysmmu,
        }
        drvdata->qos = qos;
 
+       /* Secure IRQ */
+       if (of_find_property(sysmmu->of_node, "sysmmu,secure-irq", NULL)) {
+               ret = __sysmmu_secure_irq_init(sysmmu, drvdata);
+               if (ret) {
+                       dev_err(sysmmu, "Failed to init secure irq\n");
+                       return ret;
+               }
+       }
+
        /* Parsing TLB properties */
        cnt = of_property_count_u32_elems(sysmmu->of_node, props_name);
        for (i = 0; i < cnt; i+=2) {
index c849dab1ae1ee79d6058909af1fbc4cbbde96f60..287ce6366b830b02edac80ee0d0f90d93dfe7b8d 100644 (file)
@@ -278,6 +278,7 @@ struct sysmmu_drvdata {
        phys_addr_t pgtable;            /* assigned page table structure */
        int version;                    /* our version */
        int qos;
+       int securebase;
        struct atomic_notifier_head fault_notifiers;
        struct tlb_props tlb_props;
        bool is_suspended;