diff options
Diffstat (limited to 'drivers/iommu')
-rw-r--r-- | drivers/iommu/Kconfig | 10 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu-v1.c | 161 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu_dev-v1.c | 2 |
3 files changed, 156 insertions, 17 deletions
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 2fb14ea3bd0e..1561550063f8 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -101,6 +101,16 @@ config IOMMU_LPAE If unsure, say N here. +config MSM_IOMMU_VBIF_CHECK + bool "Enable support for VBIF check when IOMMU gets stuck" + depends on MSM_IOMMU + help + Enables an extra check in the IOMMU driver that logs debugging + information when TLB sync or iommu halt issue occurs. This helps + in debugging such issues. + + If unsure, say N here. + config IOMMU_NON_SECURE bool "Turns on programming of secure SMMU by kernel" depends on MSM_IOMMU diff --git a/drivers/iommu/msm_iommu-v1.c b/drivers/iommu/msm_iommu-v1.c index f9f60e19c489..447952cc7871 100644 --- a/drivers/iommu/msm_iommu-v1.c +++ b/drivers/iommu/msm_iommu-v1.c @@ -17,6 +17,7 @@ #include <linux/platform_device.h> #include <linux/errno.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/interrupt.h> #include <linux/list.h> #include <linux/mutex.h> @@ -174,13 +175,141 @@ struct iommu_access_ops iommu_access_ops_v1 = { .iommu_lock_release = _iommu_lock_release, }; -void iommu_halt(const struct msm_iommu_drvdata *iommu_drvdata) +#ifdef CONFIG_MSM_IOMMU_VBIF_CHECK + +#define VBIF_XIN_HALT_CTRL0 0x200 +#define VBIF_XIN_HALT_CTRL1 0x204 +#define VBIF_AXI_HALT_CTRL0 0x208 +#define VBIF_AXI_HALT_CTRL1 0x20C + +static void __halt_vbif_xin(void __iomem *vbif_base) +{ + pr_err("Halting VBIF_XIN\n"); + writel_relaxed(0xFFFFFFFF, vbif_base + VBIF_XIN_HALT_CTRL0); +} + +static void __dump_vbif_state(void __iomem *base, void __iomem *vbif_base) +{ + unsigned int reg_val; + + reg_val = readl_relaxed(base + MICRO_MMU_CTRL); + pr_err("Value of SMMU_IMPLDEF_MICRO_MMU_CTRL = 0x%x\n", reg_val); + + reg_val = readl_relaxed(vbif_base + VBIF_XIN_HALT_CTRL0); + pr_err("Value of VBIF_XIN_HALT_CTRL0 = 0x%x\n", reg_val); + reg_val = readl_relaxed(vbif_base + VBIF_XIN_HALT_CTRL1); + pr_err("Value of VBIF_XIN_HALT_CTRL1 = 0x%x\n", reg_val); + reg_val = readl_relaxed(vbif_base + VBIF_AXI_HALT_CTRL0); + pr_err("Value of VBIF_AXI_HALT_CTRL0 = 0x%x\n", reg_val); + reg_val = readl_relaxed(vbif_base + VBIF_AXI_HALT_CTRL1); + pr_err("Value of VBIF_AXI_HALT_CTRL1 = 0x%x\n", reg_val); +} + +static int __check_vbif_state(struct msm_iommu_drvdata const *drvdata) +{ + phys_addr_t addr = (phys_addr_t) (drvdata->phys_base + - (phys_addr_t) 0x4000); + void __iomem *base = ioremap(addr, 0x1000); + int ret = 0; + + if (base) { + __dump_vbif_state(drvdata->base, base); + __halt_vbif_xin(drvdata->base); + __dump_vbif_state(drvdata->base, base); + iounmap(base); + } else { + pr_err("%s: Unable to ioremap\n", __func__); + ret = -ENOMEM; + } + return ret; +} + +static void check_halt_state(struct msm_iommu_drvdata const *drvdata) +{ + int res; + unsigned int val; + void __iomem *base = drvdata->base; + char const *name = drvdata->name; + + pr_err("Timed out waiting for IOMMU halt to complete for %s\n", name); + res = __check_vbif_state(drvdata); + if (res) + BUG(); + + pr_err("Checking if IOMMU halt completed for %s\n", name); + + res = readl_tight_poll_timeout( + GLB_REG(MICRO_MMU_CTRL, base), val, + (val & MMU_CTRL_IDLE) == MMU_CTRL_IDLE, 5000000); + + if (res) { + pr_err("Timed out (again) waiting for IOMMU halt to complete for %s\n", + name); + } else { + pr_err("IOMMU halt completed. VBIF FIFO most likely not getting drained by master\n"); + } + BUG(); +} + +static void check_tlb_sync_state(struct msm_iommu_drvdata const *drvdata, + int ctx) +{ + int res; + unsigned int val; + void __iomem *base = drvdata->base; + char const *name = drvdata->name; + + pr_err("Timed out waiting for TLB SYNC to complete for %s\n", name); + res = __check_vbif_state(drvdata); + if (res) + BUG(); + + pr_err("Checking if TLB sync completed for %s\n", name); + + res = readl_tight_poll_timeout(CTX_REG(CB_TLBSTATUS, base, ctx), val, + (val & CB_TLBSTATUS_SACTIVE) == 0, 5000000); + if (res) { + pr_err("Timed out (again) waiting for TLB SYNC to complete for %s\n", + name); + } else { + pr_err("TLB Sync completed. VBIF FIFO most likely not getting drained by master\n"); + } + BUG(); +} + +#else + +/* + * For targets without VBIF or for targets with the VBIF check disabled + * we directly just crash to capture the issue + */ +static void check_halt_state(struct msm_iommu_drvdata const *drvdata) +{ + BUG(); +} + +static void check_tlb_sync_state(struct msm_iommu_drvdata const *drvdata, + int ctx) +{ + BUG(); +} + +#endif + +void iommu_halt(struct msm_iommu_drvdata const *iommu_drvdata) { if (iommu_drvdata->halt_enabled) { - SET_MICRO_MMU_CTRL_HALT_REQ(iommu_drvdata->base, 1); + unsigned int val; + void __iomem *base = iommu_drvdata->base; + int res; - while (GET_MICRO_MMU_CTRL_IDLE(iommu_drvdata->base) == 0) - cpu_relax(); + SET_MICRO_MMU_CTRL_HALT_REQ(base, 1); + res = readl_tight_poll_timeout( + GLB_REG(MICRO_MMU_CTRL, base), val, + (val & MMU_CTRL_IDLE) == MMU_CTRL_IDLE, 5000000); + + if (res) + check_halt_state(iommu_drvdata); /* Ensure device is idle before continuing */ mb(); } @@ -204,21 +333,19 @@ void iommu_resume(const struct msm_iommu_drvdata *iommu_drvdata) } } -static void __sync_tlb(void __iomem *base, int ctx) +static void __sync_tlb(struct msm_iommu_drvdata *iommu_drvdata, int ctx) { - int i; + unsigned int val; + unsigned int res; + void __iomem *base = iommu_drvdata->cb_base; SET_TLBSYNC(base, ctx, 0); - - /* No barrier needed due to register proximity */ - for (i = 0; i < IOMMU_MSEC_TIMEOUT; i += IOMMU_MSEC_STEP) - if (GET_CB_TLBSTATUS_SACTIVE(base, ctx) == 0) - break; - else - msleep(IOMMU_MSEC_STEP); - - BUG_ON(i >= IOMMU_MSEC_TIMEOUT); /* No barrier needed due to read dependency */ + + res = readl_tight_poll_timeout(CTX_REG(CB_TLBSTATUS, base, ctx), val, + (val & CB_TLBSTATUS_SACTIVE) == 0, 5000000); + if (res) + check_tlb_sync_state(iommu_drvdata, ctx); } static int __flush_iotlb_va(struct iommu_domain *domain, unsigned int va) @@ -242,7 +369,7 @@ static int __flush_iotlb_va(struct iommu_domain *domain, unsigned int va) SET_TLBIVA(iommu_drvdata->cb_base, ctx_drvdata->num, ctx_drvdata->asid | (va & CB_TLBIVA_VA)); mb(); - __sync_tlb(iommu_drvdata->cb_base, ctx_drvdata->num); + __sync_tlb(iommu_drvdata, ctx_drvdata->num); __disable_clocks(iommu_drvdata); } fail: @@ -269,7 +396,7 @@ static int __flush_iotlb(struct iommu_domain *domain) SET_TLBIASID(iommu_drvdata->cb_base, ctx_drvdata->num, ctx_drvdata->asid); mb(); - __sync_tlb(iommu_drvdata->cb_base, ctx_drvdata->num); + __sync_tlb(iommu_drvdata, ctx_drvdata->num); __disable_clocks(iommu_drvdata); } diff --git a/drivers/iommu/msm_iommu_dev-v1.c b/drivers/iommu/msm_iommu_dev-v1.c index e579e93a8708..c6572330f317 100644 --- a/drivers/iommu/msm_iommu_dev-v1.c +++ b/drivers/iommu/msm_iommu_dev-v1.c @@ -309,6 +309,8 @@ static int msm_iommu_probe(struct platform_device *pdev) if (!drvdata->base) return -ENOMEM; + drvdata->phys_base = r->start; + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smmu_local_base"); if (r) { |