iommu/arm-smmu-qcom: Add debug support for TLB sync timeouts
authorSai Prakash Ranjan <quic_saipraka@quicinc.com>
Fri, 8 Jul 2022 09:42:30 +0000 (15:12 +0530)
committerWill Deacon <will@kernel.org>
Fri, 8 Jul 2022 13:15:52 +0000 (14:15 +0100)
TLB sync timeouts can be due to various reasons such as TBU power down
or pending TCU/TBU invalidation/sync and so on. Debugging these often
require dumping of some implementation defined registers to know the
status of TBU/TCU operations and some of these registers are not
accessible in non-secure world such as from kernel and requires SMC
calls to read them in the secure world. So, add this debug support
to dump implementation defined registers for TLB sync timeout issues.

Signed-off-by: Sai Prakash Ranjan <quic_saipraka@quicinc.com>
Acked-by: Robin Murphy <robin.murphy@arm.com>
Link: https://lore.kernel.org/r/20220708094230.4349-1-quic_saipraka@quicinc.com
Signed-off-by: Will Deacon <will@kernel.org>
drivers/iommu/Kconfig
drivers/iommu/arm/arm-smmu/Makefile
drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c [new file with mode: 0644]
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h [new file with mode: 0644]
drivers/iommu/arm/arm-smmu/arm-smmu.c
drivers/iommu/arm/arm-smmu/arm-smmu.h

index c79a0df..5c5cb5b 100644 (file)
@@ -363,6 +363,16 @@ config ARM_SMMU_QCOM
          When running on a Qualcomm platform that has the custom variant
          of the ARM SMMU, this needs to be built into the SMMU driver.
 
+config ARM_SMMU_QCOM_DEBUG
+       bool "ARM SMMU QCOM implementation defined debug support"
+       depends on ARM_SMMU_QCOM
+       help
+         Support for implementation specific debug features in ARM SMMU
+         hardware found in QTI platforms.
+
+         Say Y here to enable debug for issues such as TLB sync timeouts
+         which requires implementation defined register dumps.
+
 config ARM_SMMU_V3
        tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support"
        depends on ARM64
index b0cc01a..2a5a95e 100644 (file)
@@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_IOMMU) += qcom_iommu.o
 obj-$(CONFIG_ARM_SMMU) += arm_smmu.o
 arm_smmu-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-nvidia.o
 arm_smmu-$(CONFIG_ARM_SMMU_QCOM) += arm-smmu-qcom.o
+arm_smmu-$(CONFIG_ARM_SMMU_QCOM_DEBUG) += arm-smmu-qcom-debug.o
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
new file mode 100644 (file)
index 0000000..6eed8e6
--- /dev/null
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/of_device.h>
+#include <linux/qcom_scm.h>
+#include <linux/ratelimit.h>
+
+#include "arm-smmu.h"
+#include "arm-smmu-qcom.h"
+
+enum qcom_smmu_impl_reg_offset {
+       QCOM_SMMU_TBU_PWR_STATUS,
+       QCOM_SMMU_STATS_SYNC_INV_TBU_ACK,
+       QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR,
+};
+
+struct qcom_smmu_config {
+       const u32 *reg_offset;
+};
+
+void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu)
+{
+       int ret;
+       u32 tbu_pwr_status, sync_inv_ack, sync_inv_progress;
+       struct qcom_smmu *qsmmu = container_of(smmu, struct qcom_smmu, smmu);
+       const struct qcom_smmu_config *cfg;
+       static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
+                                     DEFAULT_RATELIMIT_BURST);
+
+       if (__ratelimit(&rs)) {
+               dev_err(smmu->dev, "TLB sync timed out -- SMMU may be deadlocked\n");
+
+               cfg = qsmmu->cfg;
+               if (!cfg)
+                       return;
+
+               ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_TBU_PWR_STATUS],
+                                       &tbu_pwr_status);
+               if (ret)
+                       dev_err(smmu->dev,
+                               "Failed to read TBU power status: %d\n", ret);
+
+               ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_STATS_SYNC_INV_TBU_ACK],
+                                       &sync_inv_ack);
+               if (ret)
+                       dev_err(smmu->dev,
+                               "Failed to read TBU sync/inv ack status: %d\n", ret);
+
+               ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR],
+                                       &sync_inv_progress);
+               if (ret)
+                       dev_err(smmu->dev,
+                               "Failed to read TCU syn/inv progress: %d\n", ret);
+
+               dev_err(smmu->dev,
+                       "TBU: power_status %#x sync_inv_ack %#x sync_inv_progress %#x\n",
+                       tbu_pwr_status, sync_inv_ack, sync_inv_progress);
+       }
+}
+
+/* Implementation Defined Register Space 0 register offsets */
+static const u32 qcom_smmu_impl0_reg_offset[] = {
+       [QCOM_SMMU_TBU_PWR_STATUS]              = 0x2204,
+       [QCOM_SMMU_STATS_SYNC_INV_TBU_ACK]      = 0x25dc,
+       [QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR]  = 0x2670,
+};
+
+static const struct qcom_smmu_config qcm2290_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sc7180_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sc7280_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sc8180x_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sc8280xp_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm6125_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm6350_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm8150_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm8250_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm8350_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct qcom_smmu_config sm8450_smmu_cfg = {
+       .reg_offset = qcom_smmu_impl0_reg_offset,
+};
+
+static const struct of_device_id __maybe_unused qcom_smmu_impl_debug_match[] = {
+       { .compatible = "qcom,msm8998-smmu-v2" },
+       { .compatible = "qcom,qcm2290-smmu-500", .data = &qcm2290_smmu_cfg },
+       { .compatible = "qcom,sc7180-smmu-500", .data = &sc7180_smmu_cfg },
+       { .compatible = "qcom,sc7280-smmu-500", .data = &sc7280_smmu_cfg},
+       { .compatible = "qcom,sc8180x-smmu-500", .data = &sc8180x_smmu_cfg },
+       { .compatible = "qcom,sc8280xp-smmu-500", .data = &sc8280xp_smmu_cfg },
+       { .compatible = "qcom,sdm630-smmu-v2" },
+       { .compatible = "qcom,sdm845-smmu-500" },
+       { .compatible = "qcom,sm6125-smmu-500", .data = &sm6125_smmu_cfg},
+       { .compatible = "qcom,sm6350-smmu-500", .data = &sm6350_smmu_cfg},
+       { .compatible = "qcom,sm8150-smmu-500", .data = &sm8150_smmu_cfg },
+       { .compatible = "qcom,sm8250-smmu-500", .data = &sm8250_smmu_cfg },
+       { .compatible = "qcom,sm8350-smmu-500", .data = &sm8350_smmu_cfg },
+       { .compatible = "qcom,sm8450-smmu-500", .data = &sm8450_smmu_cfg },
+       { }
+};
+
+const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu)
+{
+       const struct of_device_id *match;
+       const struct device_node *np = smmu->dev->of_node;
+
+       match = of_match_node(qcom_smmu_impl_debug_match, np);
+       if (!match)
+               return NULL;
+
+       return match->data;
+}
index 2d470d8..de25071 100644 (file)
@@ -5,23 +5,40 @@
 
 #include <linux/acpi.h>
 #include <linux/adreno-smmu-priv.h>
+#include <linux/delay.h>
 #include <linux/of_device.h>
 #include <linux/qcom_scm.h>
 
 #include "arm-smmu.h"
+#include "arm-smmu-qcom.h"
 
-struct qcom_smmu {
-       struct arm_smmu_device smmu;
-       bool bypass_quirk;
-       u8 bypass_cbndx;
-       u32 stall_enabled;
-};
+#define QCOM_DUMMY_VAL -1
 
 static struct qcom_smmu *to_qcom_smmu(struct arm_smmu_device *smmu)
 {
        return container_of(smmu, struct qcom_smmu, smmu);
 }
 
+static void qcom_smmu_tlb_sync(struct arm_smmu_device *smmu, int page,
+                               int sync, int status)
+{
+       unsigned int spin_cnt, delay;
+       u32 reg;
+
+       arm_smmu_writel(smmu, page, sync, QCOM_DUMMY_VAL);
+       for (delay = 1; delay < TLB_LOOP_TIMEOUT; delay *= 2) {
+               for (spin_cnt = TLB_SPIN_COUNT; spin_cnt > 0; spin_cnt--) {
+                       reg = arm_smmu_readl(smmu, page, status);
+                       if (!(reg & ARM_SMMU_sTLBGSTATUS_GSACTIVE))
+                               return;
+                       cpu_relax();
+               }
+               udelay(delay);
+       }
+
+       qcom_smmu_tlb_sync_debug(smmu);
+}
+
 static void qcom_adreno_smmu_write_sctlr(struct arm_smmu_device *smmu, int idx,
                u32 reg)
 {
@@ -375,6 +392,7 @@ static const struct arm_smmu_impl qcom_smmu_impl = {
        .def_domain_type = qcom_smmu_def_domain_type,
        .reset = qcom_smmu500_reset,
        .write_s2cr = qcom_smmu_write_s2cr,
+       .tlb_sync = qcom_smmu_tlb_sync,
 };
 
 static const struct arm_smmu_impl qcom_adreno_smmu_impl = {
@@ -383,6 +401,7 @@ static const struct arm_smmu_impl qcom_adreno_smmu_impl = {
        .reset = qcom_smmu500_reset,
        .alloc_context_bank = qcom_adreno_smmu_alloc_context_bank,
        .write_sctlr = qcom_adreno_smmu_write_sctlr,
+       .tlb_sync = qcom_smmu_tlb_sync,
 };
 
 static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu,
@@ -399,6 +418,7 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu,
                return ERR_PTR(-ENOMEM);
 
        qsmmu->smmu.impl = impl;
+       qsmmu->cfg = qcom_smmu_impl_data(smmu);
 
        return &qsmmu->smmu;
 }
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h
new file mode 100644 (file)
index 0000000..99ec8f8
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _ARM_SMMU_QCOM_H
+#define _ARM_SMMU_QCOM_H
+
+struct qcom_smmu {
+       struct arm_smmu_device smmu;
+       const struct qcom_smmu_config *cfg;
+       bool bypass_quirk;
+       u8 bypass_cbndx;
+       u32 stall_enabled;
+};
+
+#ifdef CONFIG_ARM_SMMU_QCOM_DEBUG
+void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu);
+const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu);
+#else
+static inline void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu) { }
+static inline const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu)
+{
+       return NULL;
+}
+#endif
+
+#endif /* _ARM_SMMU_QCOM_H */
index 2ed3594..41633e5 100644 (file)
@@ -2074,7 +2074,6 @@ err_reset_platform_ops: __maybe_unused;
 static int arm_smmu_device_probe(struct platform_device *pdev)
 {
        struct resource *res;
-       resource_size_t ioaddr;
        struct arm_smmu_device *smmu;
        struct device *dev = &pdev->dev;
        int num_irqs, i, err;
@@ -2098,7 +2097,8 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
        smmu->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
        if (IS_ERR(smmu->base))
                return PTR_ERR(smmu->base);
-       ioaddr = res->start;
+       smmu->ioaddr = res->start;
+
        /*
         * The resource size should effectively match the value of SMMU_TOP;
         * stash that temporarily until we know PAGESIZE to validate it with.
@@ -2178,7 +2178,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
        }
 
        err = iommu_device_sysfs_add(&smmu->iommu, smmu->dev, NULL,
-                                    "smmu.%pa", &ioaddr);
+                                    "smmu.%pa", &smmu->ioaddr);
        if (err) {
                dev_err(dev, "Failed to register iommu in sysfs\n");
                return err;
index 2b9b42f..703fd58 100644 (file)
@@ -278,6 +278,7 @@ struct arm_smmu_device {
        struct device                   *dev;
 
        void __iomem                    *base;
+       phys_addr_t                     ioaddr;
        unsigned int                    numpage;
        unsigned int                    pgshift;