From 90c4319e85fd9a591787c0881fbbb331b4e1999f Mon Sep 17 00:00:00 2001 From: "Isaac J. Manjarres" Date: Thu, 20 Feb 2020 16:14:39 -0800 Subject: [PATCH] iommu/arm-smmu: Rationalize TLB sync timeout handling The current TLB sync timeout handling doesn't make sense in the context of non QSMMU-V500 SMMU V2 implementations, so introduce the notion of architecture specific handling for TLB sync timeouts. Change-Id: Ibceb5422d6488f9e67cc4c4ed968348ad92aa834 Signed-off-by: Isaac J. Manjarres --- drivers/iommu/arm-smmu.c | 72 ++++++++++++++++++++++++++++++++-------- drivers/iommu/arm-smmu.h | 1 - 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 30ceffa271cf..2b9c457a3680 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -199,8 +199,6 @@ static int __arm_smmu_domain_set_attr(struct iommu_domain *domain, static int arm_smmu_domain_get_attr(struct iommu_domain *domain, enum iommu_attr attr, void *data); -static void __arm_smmu_tlb_sync_timeout(struct arm_smmu_device *smmu); - static inline int arm_smmu_rpm_get(struct arm_smmu_device *smmu) { if (pm_runtime_enabled(smmu->dev)) @@ -405,6 +403,14 @@ static void arm_smmu_interrupt_selftest(struct arm_smmu_device *smmu) * * device_group() * Hook for checking whether a device is compatible with a said group. + * + * tlb_sync_timeout() + * Hook for performing architecture-specific procedures to collect additional + * debugging information on a TLB sync timeout. + * + * device_remove() + * Hook for performing architecture-specific procedures prior to powering off + * the SMMU. */ struct arm_smmu_arch_ops { int (*init)(struct arm_smmu_device *smmu); @@ -414,6 +420,8 @@ struct arm_smmu_arch_ops { void (*init_context_bank)(struct arm_smmu_domain *smmu_domain, struct device *dev); int (*device_group)(struct device *dev, struct iommu_group *group); + void (*tlb_sync_timeout)(struct arm_smmu_device *smmu); + void (*device_remove)(struct arm_smmu_device *smmu); }; static int arm_smmu_arch_init(struct arm_smmu_device *smmu) @@ -459,6 +467,24 @@ static int arm_smmu_arch_device_group(struct device *dev, return smmu->arch_ops->device_group(dev, group); } +static void arm_smmu_arch_tlb_sync_timeout(struct arm_smmu_device *smmu) +{ + if (!smmu->arch_ops || !smmu->arch_ops->tlb_sync_timeout) + return; + + smmu->arch_ops->tlb_sync_timeout(smmu); +} + + +static void arm_smmu_arch_device_remove(struct arm_smmu_device *smmu) +{ + if (!smmu->arch_ops) + return; + if (!smmu->arch_ops->device_remove) + return; + return smmu->arch_ops->device_remove(smmu); +} + static void arm_smmu_arch_write_sync(struct arm_smmu_device *smmu) { u32 id; @@ -894,6 +920,13 @@ static int __arm_smmu_tlb_sync(struct arm_smmu_device *smmu, int page, unsigned int inc, delay; u32 reg; + /* + * Allowing an unbounded number of sync requests to be submitted when a + * TBU is not processing sync requests can cause a TBU's command queue + * to fill up. Once the queue is full, subsequent sync requests can + * stall the CPU indefinitely. Avoid this by gating subsequent sync + * requests after the first sync timeout on an SMMU. + */ if (IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG) && test_bit(0, &smmu->sync_timed_out)) return -EINVAL; @@ -915,7 +948,7 @@ static int __arm_smmu_tlb_sync(struct arm_smmu_device *smmu, int page, goto out; trace_tlbsync_timeout(smmu->dev, 0); - __arm_smmu_tlb_sync_timeout(smmu); + arm_smmu_arch_tlb_sync_timeout(smmu); out: return -EINVAL; @@ -4853,7 +4886,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev) arm_smmu_bus_init(NULL); iommu_device_unregister(&smmu->iommu); - cancel_work_sync(&smmu->outstanding_tnx_work); + arm_smmu_arch_device_remove(smmu); idr_destroy(&smmu->asid_idr); @@ -5009,6 +5042,8 @@ struct qsmmuv500_archdata { u32 version; struct actlr_setting *actlrs; u32 actlr_tbl_size; + struct work_struct outstanding_tnx_work; + struct arm_smmu_device *smmu; }; #define get_qsmmuv500_archdata(smmu) \ ((struct qsmmuv500_archdata *)(smmu->archdata)) @@ -5047,15 +5082,15 @@ static struct qsmmuv500_tbu_device *qsmmuv500_find_tbu( */ static DEFINE_MUTEX(capture_reg_lock); -static void arm_smmu_log_outstanding_transactions(struct work_struct *work) +static void qsmmuv500_log_outstanding_transactions(struct work_struct *work) { struct qsmmuv500_tbu_device *tbu = NULL; u64 outstanding_tnxs; u64 tcr_cntl_val, res; - struct arm_smmu_device *smmu = container_of(work, - struct arm_smmu_device, - outstanding_tnx_work); - struct qsmmuv500_archdata *data = get_qsmmuv500_archdata(smmu); + struct qsmmuv500_archdata *data = container_of(work, + struct qsmmuv500_archdata, + outstanding_tnx_work); + struct arm_smmu_device *smmu = data->smmu; void __iomem *base; if (!mutex_trylock(&capture_reg_lock)) { @@ -5126,13 +5161,14 @@ bug: BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); } -static void __arm_smmu_tlb_sync_timeout(struct arm_smmu_device *smmu) +static void qsmmuv500_tlb_sync_timeout(struct arm_smmu_device *smmu) { u32 sync_inv_ack, tbu_pwr_status, sync_inv_progress; u32 tbu_inv_pending = 0, tbu_sync_pending = 0; u32 tbu_inv_acked = 0, tbu_sync_acked = 0; u32 tcu_inv_pending = 0, tcu_sync_pending = 0; u32 tbu_ids = 0; + struct qsmmuv500_archdata *data = get_qsmmuv500_archdata(smmu); int ret; static DEFINE_RATELIMIT_STATE(_rs, @@ -5212,12 +5248,19 @@ static void __arm_smmu_tlb_sync_timeout(struct arm_smmu_device *smmu) } if (tcu_sync_pending) - schedule_work(&smmu->outstanding_tnx_work); + schedule_work(&data->outstanding_tnx_work); else out: BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); } +static void qsmmuv500_device_remove(struct arm_smmu_device *smmu) +{ + struct qsmmuv500_archdata *data = get_qsmmuv500_archdata(smmu); + + cancel_work_sync(&data->outstanding_tnx_work); +} + static bool arm_smmu_fwspec_match_smr(struct iommu_fwspec *fwspec, struct arm_smmu_smr *smr) { @@ -5726,6 +5769,7 @@ static int qsmmuv500_arch_init(struct arm_smmu_device *smmu) data->version = readl_relaxed(data->tcu_base + TCU_HW_VERSION_HLOS1); smmu->archdata = data; + data->smmu = smmu; ret = qsmmuv500_read_actlr_tbl(smmu); if (ret) @@ -5746,8 +5790,8 @@ static int qsmmuv500_arch_init(struct arm_smmu_device *smmu) if (ret) return ret; - INIT_WORK(&smmu->outstanding_tnx_work, - arm_smmu_log_outstanding_transactions); + INIT_WORK(&data->outstanding_tnx_work, + qsmmuv500_log_outstanding_transactions); /* Attempt to register child devices */ ret = device_for_each_child(dev, smmu, qsmmuv500_tbu_register); @@ -5762,6 +5806,8 @@ static struct arm_smmu_arch_ops qsmmuv500_arch_ops = { .iova_to_phys_hard = qsmmuv500_iova_to_phys_hard, .init_context_bank = qsmmuv500_init_cb, .device_group = qsmmuv500_device_group, + .tlb_sync_timeout = qsmmuv500_tlb_sync_timeout, + .device_remove = qsmmuv500_device_remove, }; static const struct of_device_id qsmmuv500_tbu_of_match[] = { diff --git a/drivers/iommu/arm-smmu.h b/drivers/iommu/arm-smmu.h index 01695396184f..691e908a04bc 100644 --- a/drivers/iommu/arm-smmu.h +++ b/drivers/iommu/arm-smmu.h @@ -375,7 +375,6 @@ struct arm_smmu_device { struct arm_smmu_arch_ops *arch_ops; void *archdata; - struct work_struct outstanding_tnx_work; unsigned long sync_timed_out; };