From b4a6f88e588166534fca008b3e14645cf1964265 Mon Sep 17 00:00:00 2001 From: Chris Goldsworthy Date: Tue, 29 Oct 2019 13:49:44 -0700 Subject: [PATCH] iommu/arm-smmu: log outstanding transactions on sync timeout On a TLB sync timeout for a given SMMU, dump the outstanding transaction bitmap for each of its TBUs. Additionally, introduce a sync_timed_out flag for each SMMU to be present with the enabling of CONFIG_IOMMU_DEBUG to (1) ensure that only one thread performs the relevant logging and BUG_ON() during a sync timeout, and (2) prevent other incoming threads from triggering further timeouts, which can overwrite the SMMU state data that would contain information on the initial sync timeout. Change-Id: I43855ecc51ddd2f3a1a287a39c123784c54ad538 Signed-off-by: Chris Goldsworthy --- drivers/iommu/Kconfig | 7 ++- drivers/iommu/arm-smmu.c | 120 ++++++++++++++++++++++++++++++++++++++- drivers/iommu/arm-smmu.h | 2 + 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f4a5a24ebee0..93cdc3e48a1b 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -477,9 +477,10 @@ config IOMMU_TLBSYNC_DEBUG bool "TLB sync timeout debug" depends on ARM_SMMU help - Enables to collect the SMMU system state information right - after the first TLB sync timeout failure by calling BUG(). - Note to use this only on debug builds. + Collects the SMMU system state information after the + first TLB sync timeout failure by calling BUG() from + a workqueue thread. Note to use this only on debug + builds. If unsure, say N here. diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 5424d7a277e0..dde8c06dd599 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -891,6 +892,10 @@ static int __arm_smmu_tlb_sync(struct arm_smmu_device *smmu, int page, unsigned int inc, delay; u32 reg; + if (IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG) && + test_bit(0, &smmu->sync_timed_out)) + return -EINVAL; + arm_smmu_writel(smmu, page, sync, QCOM_DUMMY_VAL); for (delay = 1, inc = 1; delay < TLB_LOOP_TIMEOUT; delay += inc) { reg = arm_smmu_readl(smmu, page, status); @@ -903,8 +908,14 @@ static int __arm_smmu_tlb_sync(struct arm_smmu_device *smmu, int page, inc *= 2; } + if (IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG) && + test_and_set_bit_lock(0, &smmu->sync_timed_out)) + goto out; + trace_tlbsync_timeout(smmu->dev, 0); __arm_smmu_tlb_sync_timeout(smmu); + +out: return -EINVAL; } @@ -4823,6 +4834,9 @@ 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); + idr_destroy(&smmu->asid_idr); /* Turn the thing off */ @@ -4955,6 +4969,17 @@ module_exit(arm_smmu_exit); #define TBU_DBG_TIMEOUT_US 100 +#define TNX_TCR_CNTL 0x130 +#define TNX_TCR_CNTL_TBU_OT_CAPTURE_EN BIT(18) +#define TNX_TCR_CNTL_ALWAYS_CAPTURE BIT(15) +#define TNX_TCR_CNTL_MATCH_MASK_UPD BIT(7) +#define TNX_TCR_CNTL_MATCH_MASK_VALID BIT(6) + +#define CAPTURE1_SNAPSHOT_1 0x138 + +#define TNX_TCR_CNTL_2 0x178 +#define TNX_TCR_CNTL_2_CAP1_VALID BIT(0) + struct actlr_setting { struct arm_smmu_smr smr; u32 actlr; @@ -4997,6 +5022,92 @@ struct qsmmuv500_group_iommudata { static struct qsmmuv500_tbu_device *qsmmuv500_find_tbu( struct arm_smmu_device *smmu, u32 sid); +/* + * Provides mutually exclusive access to the registers used by the + * outstanding transaction snapshot feature and the transaction + * snapshot capture feature. + */ +static DEFINE_MUTEX(capture_reg_lock); + +static void arm_smmu_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); + void __iomem *base; + + if (!mutex_trylock(&capture_reg_lock)) { + dev_warn_ratelimited(smmu->dev, + "Tnx snapshot regs in use, not dumping OT tnxs.\n"); + goto bug; + } + + if (arm_smmu_power_on(smmu->pwr)) { + dev_err_ratelimited(smmu->dev, + "%s: Failed to power on SMMU.\n", + __func__); + goto unlock; + } + + list_for_each_entry(tbu, &data->tbus, list) { + if (arm_smmu_power_on(tbu->pwr)) { + dev_err_ratelimited(tbu->dev, + "%s: Failed to power on TBU.\n", + __func__); + continue; + } + base = tbu->base; + + tcr_cntl_val = readq_relaxed(base + TNX_TCR_CNTL); + + /* Write 1 into MATCH_MASK_UPD of TNX_TCR_CNTL */ + writeq_relaxed(tcr_cntl_val | TNX_TCR_CNTL_MATCH_MASK_UPD, + base + TNX_TCR_CNTL); + + /* + * Simultaneously write 0 into MATCH_MASK_UPD, 0 into + * ALWAYS_CAPTURE, 0 into MATCH_MASK_VALID, and 1 into + * TBU_OT_CAPTURE_EN of TNX_TCR_CNTL + */ + tcr_cntl_val &= ~(TNX_TCR_CNTL_MATCH_MASK_UPD | + TNX_TCR_CNTL_ALWAYS_CAPTURE | + TNX_TCR_CNTL_MATCH_MASK_VALID); + writeq_relaxed(tcr_cntl_val | TNX_TCR_CNTL_TBU_OT_CAPTURE_EN, + base + TNX_TCR_CNTL); + + /* Poll for CAPTURE1_VALID to become 1 on TNX_TCR_CNTL_2 */ + if (readq_poll_timeout_atomic(base + TNX_TCR_CNTL_2, res, + res & TNX_TCR_CNTL_2_CAP1_VALID, + 0, TBU_DBG_TIMEOUT_US)) { + dev_err_ratelimited(tbu->dev, + "Timeout on TNX snapshot poll\n"); + goto poll_timeout; + } + + /* Read Register CAPTURE1_SNAPSHOT_1 */ + outstanding_tnxs = readq_relaxed(base + CAPTURE1_SNAPSHOT_1); + dev_err_ratelimited(tbu->dev, + "Outstanding Transaction Bitmap: 0x%llx\n", + outstanding_tnxs); +poll_timeout: + /* Write TBU_OT_CAPTURE_EN to 0 of TNX_TCR_CNTL */ + writeq_relaxed(tcr_cntl_val & ~TNX_TCR_CNTL_TBU_OT_CAPTURE_EN, + tbu->base + TNX_TCR_CNTL); + + arm_smmu_power_off(smmu, tbu->pwr); + } + + arm_smmu_power_off(smmu, smmu->pwr); +unlock: + mutex_unlock(&capture_reg_lock); +bug: + BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); +} + static void __arm_smmu_tlb_sync_timeout(struct arm_smmu_device *smmu) { u32 sync_inv_ack, tbu_pwr_status, sync_inv_progress; @@ -5081,8 +5192,12 @@ static void __arm_smmu_tlb_sync_timeout(struct arm_smmu_device *smmu) } } } + + if (tcu_sync_pending) + schedule_work(&smmu->outstanding_tnx_work); + else out: - BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); + BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); } static bool arm_smmu_fwspec_match_smr(struct iommu_fwspec *fwspec, @@ -5613,6 +5728,9 @@ 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); + /* Attempt to register child devices */ ret = device_for_each_child(dev, smmu, qsmmuv500_tbu_register); if (ret) diff --git a/drivers/iommu/arm-smmu.h b/drivers/iommu/arm-smmu.h index 755cdc40ce99..534dd8286356 100644 --- a/drivers/iommu/arm-smmu.h +++ b/drivers/iommu/arm-smmu.h @@ -375,6 +375,8 @@ struct arm_smmu_device { struct arm_smmu_arch_ops *arch_ops; void *archdata; + struct work_struct outstanding_tnx_work; + unsigned long sync_timed_out; }; enum arm_smmu_context_fmt {