From 9d67a36c0cc84712443bec2155a5d255a53ca3ce Mon Sep 17 00:00:00 2001 From: Vijayanand Jitta Date: Fri, 14 Jun 2019 14:35:52 +0530 Subject: [PATCH 1/3] iommu: arm-smmu: add testbus support for smmuv500 add a debugfs interface to support testbus also add an interface for dumping testbus information, module parameters tcu_testbus_sel and tbu_testbus_sel can be used for selecting different testbuses to dump on tcu and tbu respectively. Change-Id: I1bf420db053458b2ce1f43dc544c0776a3f4d176 Signed-off-by: Vijayanand Jitta [isaacm@codeaurora.org: introduced CONFIG_ARM_SMMU_TESTBUS_DEBUGFS] Signed-off-by: Isaac J. Manjarres [guptap@codeaurora.org: resolved merge conflict] Signed-off-by: Prakash Gupta --- drivers/iommu/Kconfig | 19 ++ drivers/iommu/Makefile | 2 +- drivers/iommu/arm-smmu-debug.c | 193 +++++++++++++++++++ drivers/iommu/arm-smmu-debug.h | 99 ++++++++++ drivers/iommu/arm-smmu-qcom.c | 338 +++++++++++++++++++++++++++++++++ drivers/iommu/arm-smmu.c | 9 + 6 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 drivers/iommu/arm-smmu-debug.c create mode 100644 drivers/iommu/arm-smmu-debug.h diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index d9e4adfa536a..f0b0789d8505 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -529,6 +529,15 @@ config ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT 'arm-smmu.disable_bypass' will continue to override this config. +config ARM_SMMU_TESTBUS_DEBUGFS + bool "Expose testbus control debugfs nodes" + depends on ARM_SMMU && IOMMU_DEBUGFS + help + Support for exposing debugfs nodes to set testbus select values + for selecting a testbus to inspect for a particular TCU/TBU + on an SMMU. This also exposes debugfs nodes to read testbus output + after the output has been selected. + config ARM_SMMU_V3 tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support" depends on ARM64 @@ -563,6 +572,16 @@ config IOMMU_TLBSYNC_DEBUG If unsure, say N here. +config ARM_SMMU_TESTBUS_DUMP + bool "ARM SMMU testbus dump" + depends on ARM_SMMU + help + Enables testbus dump collection on arm smmu right after TLB + sync timeout failure. + Note to use this only on debug builds. + + If unsure, say N here. + config QCOM_LAZY_MAPPING tristate "Reference counted iommu-mapping support" depends on ION diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index c785bcea6461..ae4dbcbb5056 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -17,7 +17,7 @@ obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o amd_iommu_quirks.o obj-$(CONFIG_AMD_IOMMU_DEBUGFS) += amd_iommu_debugfs.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU) += qcom-arm-smmu-mod.o -qcom-arm-smmu-mod-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-qcom.o +qcom-arm-smmu-mod-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-qcom.o arm-smmu-debug.o obj-$(CONFIG_ARM_SMMU_V3) += arm-smmu-v3.o obj-$(CONFIG_DMAR_TABLE) += dmar.o obj-$(CONFIG_INTEL_IOMMU) += intel-iommu.o intel-pasid.o diff --git a/drivers/iommu/arm-smmu-debug.c b/drivers/iommu/arm-smmu-debug.c new file mode 100644 index 000000000000..c6b6816bd491 --- /dev/null +++ b/drivers/iommu/arm-smmu-debug.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include "arm-smmu.h" +#include "arm-smmu-debug.h" + + +u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, + bool write, u32 val) +{ + if (write) { + writel_relaxed(val, tbu_base + DEBUG_TESTBUS_SEL_TBU); + /* Make sure tbu select register is written to */ + wmb(); + } else { + return readl_relaxed(tbu_base + DEBUG_TESTBUS_SEL_TBU); + } + return 0; +} + +u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base) +{ + return readl_relaxed(tbu_base + DEBUG_TESTBUS_TBU); +} + +u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, + void __iomem *tcu_base, enum tcu_testbus testbus, + bool write, u32 val) +{ + int offset; + + if (testbus == CLK_TESTBUS) { + base = tcu_base; + offset = ARM_SMMU_TESTBUS_SEL_HLOS1_NS; + } else { + offset = ARM_SMMU_TESTBUS_SEL; + } + + if (write) { + writel_relaxed(val, base + offset); + /* Make sure tcu select register is written to */ + wmb(); + } else { + return readl_relaxed(base + offset); + } + + return 0; +} + +u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base) +{ + return readl_relaxed(base + ARM_SMMU_TESTBUS); +} + +static void arm_smmu_debug_dump_tbu_qns4_testbus(struct device *dev, + void __iomem *tbu_base) +{ + int i; + u32 reg; + + for (i = 0 ; i < TBU_QNS4_BRIDGE_SIZE; ++i) { + reg = arm_smmu_debug_tbu_testbus_select(tbu_base, READ, 0); + reg = (reg & ~TBU_QNS4_BRIDGE_MASK) | i << 0; + arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, reg); + dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%llx\n", + arm_smmu_debug_tbu_testbus_select(tbu_base, + READ, 0), i, + arm_smmu_debug_tbu_testbus_output(tbu_base)); + } +} + +static void arm_smmu_debug_program_tbu_testbus(void __iomem *tbu_base, + int tbu_testbus) +{ + u32 reg; + + reg = arm_smmu_debug_tbu_testbus_select(tbu_base, READ, 0); + reg = (reg & ~TCU_PTW_QUEUE_MASK) | tbu_testbus; + arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, reg); +} + +void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, + int tbu_testbus_sel) +{ + if (tbu_testbus_sel & TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL) { + dev_info(dev, "Dumping TBU clk gate controller:\n"); + arm_smmu_debug_program_tbu_testbus(tbu_base, + TBU_CLK_GATE_CONTROLLER_TESTBUS); + dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", + arm_smmu_debug_tbu_testbus_select(tbu_base, + READ, 0), + arm_smmu_debug_tbu_testbus_output(tbu_base)); + } + + if (tbu_testbus_sel & TBU_QNS4_A2Q_TESTBUS_SEL) { + dev_info(dev, "Dumping TBU qns4 a2q test bus:\n"); + arm_smmu_debug_program_tbu_testbus(tbu_base, + TBU_QNS4_A2Q_TESTBUS); + arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base); + } + + if (tbu_testbus_sel & TBU_QNS4_Q2A_TESTBUS_SEL) { + dev_info(dev, "Dumping qns4 q2a test bus:\n"); + arm_smmu_debug_program_tbu_testbus(tbu_base, + TBU_QNS4_Q2A_TESTBUS); + arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base); + } + + if (tbu_testbus_sel & TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL) { + dev_info(dev, "Dumping multi master qchannel:\n"); + arm_smmu_debug_program_tbu_testbus(tbu_base, + TBU_MULTIMASTER_QCHANNEL_TESTBUS); + dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", + arm_smmu_debug_tbu_testbus_select(tbu_base, + READ, 0), + arm_smmu_debug_tbu_testbus_output(tbu_base)); + } +} + +static void arm_smmu_debug_program_tcu_testbus(struct device *dev, + void __iomem *base, void __iomem *tcu_base, + unsigned long mask, int start, int end, int shift, + bool print) +{ + u32 reg; + int i; + + for (i = start; i < end; i++) { + reg = arm_smmu_debug_tcu_testbus_select(base, tcu_base, + PTW_AND_CACHE_TESTBUS, READ, 0); + reg &= mask; + reg |= i << shift; + arm_smmu_debug_tcu_testbus_select(base, tcu_base, + PTW_AND_CACHE_TESTBUS, WRITE, reg); + if (print) + dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n", + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), + i, arm_smmu_debug_tcu_testbus_output(base)); + } +} + +void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base, + void __iomem *tcu_base, int tcu_testbus_sel) +{ + int i; + + if (tcu_testbus_sel & TCU_CACHE_TESTBUS_SEL) { + dev_info(dev, "Dumping TCU cache testbus:\n"); + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + TCU_CACHE_TESTBUS, 0, 1, 0, false); + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + ~TCU_PTW_QUEUE_MASK, 0, + TCU_CACHE_LOOKUP_QUEUE_SIZE, + 2, true); + } + + if (tcu_testbus_sel & TCU_PTW_TESTBUS_SEL) { + dev_info(dev, "Dumping TCU PTW test bus:\n"); + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, 1, + TCU_PTW_TESTBUS, TCU_PTW_TESTBUS + 1, 0, false); + + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + ~TCU_PTW_INTERNAL_STATES_MASK, + 0, TCU_PTW_INTERNAL_STATES, + 2, true); + + for (i = TCU_PTW_QUEUE_START; + i < TCU_PTW_QUEUE_START + TCU_PTW_QUEUE_SIZE; ++i) { + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + ~TCU_PTW_QUEUE_MASK, i, i + 1, 2, true); + arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + ~TCU_PTW_TESTBUS_SEL2_MASK, TCU_PTW_TESTBUS_SEL2, + TCU_PTW_TESTBUS_SEL2 + 1, 0, false); + dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n", + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), + i, arm_smmu_debug_tcu_testbus_output(base)); + } + } + + /* program ARM_SMMU_TESTBUS_SEL_HLOS1_NS to select TCU clk testbus*/ + arm_smmu_debug_tcu_testbus_select(base, tcu_base, + CLK_TESTBUS, WRITE, TCU_CLK_TESTBUS_SEL); + dev_info(dev, "Programming Tcu clk gate controller: testbus_sel: 0x%lx\n", + arm_smmu_debug_tcu_testbus_select(base, tcu_base, + CLK_TESTBUS, READ, 0)); +} diff --git a/drivers/iommu/arm-smmu-debug.h b/drivers/iommu/arm-smmu-debug.h new file mode 100644 index 000000000000..c978354b27b0 --- /dev/null +++ b/drivers/iommu/arm-smmu-debug.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#define ARM_SMMU_TESTBUS_SEL 0x25E4 +#define ARM_SMMU_TESTBUS 0x25E8 +#define ARM_SMMU_TESTBUS_SEL_HLOS1_NS 0x8 +#define DEBUG_TESTBUS_SEL_TBU 0x50 +#define DEBUG_TESTBUS_TBU 0x58 + +#define TCU_PTW_TESTBUS BIT(8) +#define TCU_CACHE_TESTBUS ~TCU_PTW_TESTBUS +#define TCU_PTW_TESTBUS_SEL BIT(1) +#define TCU_PTW_INTERNAL_STATES 3 +#define TCU_PTW_INTERNAL_STATES_MASK GENMASK(7, 2) +#define TCU_PTW_TESTBUS_SEL2 3 +#define TCU_PTW_TESTBUS_SEL2_MASK GENMASK(1, 0) +#define TCU_PTW_QUEUE_START 32 +#define TCU_PTW_QUEUE_SIZE 32 +#define TCU_PTW_QUEUE_MASK GENMASK(7, 0) +#define TCU_CACHE_TESTBUS_SEL 0x1 +#define TCU_CACHE_LOOKUP_QUEUE_SIZE 32 +#define TCU_CLK_TESTBUS_SEL 0x200 + + +#define TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL 0x1 +#define TBU_QNS4_A2Q_TESTBUS_SEL BIT(1) +#define TBU_QNS4_Q2A_TESTBUS_SEL BIT(2) +#define TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL BIT(3) +#define TBU_CLK_GATE_CONTROLLER_TESTBUS BIT(6) +#define TBU_QNS4_A2Q_TESTBUS BIT(7) +#define TBU_QNS4_Q2A_TESTBUS (BIT(5) | BIT(7)) +#define TBU_MULTIMASTER_QCHANNEL_TESTBUS GENMASK(7, 6) +#define TBU_QNS4_BRIDGE_SIZE 32 +#define TBU_QNS4_BRIDGE_MASK GENMASK(4, 0) + +extern int tbu_testbus_sel; +extern int tcu_testbus_sel; + +enum tcu_testbus { + PTW_AND_CACHE_TESTBUS, + CLK_TESTBUS, +}; + +enum testbus_sel { + SEL_TCU, + SEL_TBU, +}; + +enum testbus_ops { + TESTBUS_SELECT, + TESTBUS_OUTPUT, +}; + +#if IS_ENABLED(CONFIG_ARM_SMMU) + +u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, + bool write, u32 val); +u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base); +u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, + void __iomem *tcu_base, enum tcu_testbus testbus, + bool write, u32 val); +u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base); +void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, + int tbu_testbus_sel); +void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base, + void __iomem *tcu_base, int tcu_testbus_sel); + +#else +static inline u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, + bool write, u32 val) +{ + return 0; +} +static inline u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base) +{ + return 0; +} +u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, + void __iomem *tcu_base, enum tcu_testbus testbus, + bool write, u32 val) +{ +} +static inline u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base) +{ + return 0; +} +static inline void arm_smmu_debug_dump_tbu_testbus(struct device *dev, + void __iomem *tbu_base, int tbu_testbus_sel) +{ +} +static inline void arm_smmu_debug_dump_tcu_testbus(struct device *dev, + void __iomem *base, void __iomem *tcu_base, + int tcu_testbus_sel) +{ +} +#endif + diff --git a/drivers/iommu/arm-smmu-qcom.c b/drivers/iommu/arm-smmu-qcom.c index 8ac95ade0cbf..8f839af05c8a 100644 --- a/drivers/iommu/arm-smmu-qcom.c +++ b/drivers/iommu/arm-smmu-qcom.c @@ -14,6 +14,9 @@ #include #include "arm-smmu.h" +#include "arm-smmu-debug.h" +#include +#include #define ARM_SMMU_IMPL_DEF1 6 @@ -421,6 +424,331 @@ static struct qsmmuv500_tbu_device *qsmmuv500_find_tbu( * snapshot capture feature. */ static DEFINE_MUTEX(capture_reg_lock); +static DEFINE_SPINLOCK(testbus_lock); + +#ifdef CONFIG_ARM_SMMU_TESTBUS_DEBUGFS +static struct dentry *debugfs_testbus_dir; + +static ssize_t arm_smmu_debug_testbus_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset, + enum testbus_sel tbu, enum testbus_ops ops) + +{ + char buf[100]; + ssize_t retval; + size_t buflen; + int buf_len = sizeof(buf); + + if (*offset) + return 0; + + memset(buf, 0, buf_len); + + if (tbu == SEL_TBU) { + struct qsmmuv500_tbu_device *tbu = file->private_data; + void __iomem *tbu_base = tbu->base; + long val; + + arm_smmu_power_on(tbu->pwr); + if (ops == TESTBUS_SELECT) + val = arm_smmu_debug_tbu_testbus_select(tbu_base, + READ, 0); + else + val = arm_smmu_debug_tbu_testbus_output(tbu_base); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + + scnprintf(buf, buf_len, "0x%0x\n", val); + } else { + + struct arm_smmu_device *smmu = file->private_data; + struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu); + void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0); + void __iomem *tcu_base = data->tcu_base; + + arm_smmu_power_on(smmu->pwr); + + if (ops == TESTBUS_SELECT) { + scnprintf(buf, buf_len, "TCU clk testbus sel: 0x%0x\n", + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, CLK_TESTBUS, READ, 0)); + scnprintf(buf + strlen(buf), buf_len - strlen(buf), + "TCU testbus sel : 0x%0x\n", + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, PTW_AND_CACHE_TESTBUS, + READ, 0)); + } else { + scnprintf(buf, buf_len, "0x%0x\n", + arm_smmu_debug_tcu_testbus_output(base)); + } + + arm_smmu_power_off(smmu, smmu->pwr); + } + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err_ratelimited("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; + retval = buflen; + } + + return retval; +} + +static ssize_t arm_smmu_debug_tcu_testbus_sel_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *offset) +{ + struct arm_smmu_device *smmu = file->private_data; + struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu); + void __iomem *tcu_base = data->tcu_base; + void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0); + char *comma; + char buf[100]; + u64 sel, val; + + if (count >= 100) { + pr_err_ratelimited("Value too large\n"); + return -EINVAL; + } + + memset(buf, 0, 100); + + if (copy_from_user(buf, ubuf, count)) { + pr_err_ratelimited("Couldn't copy from user\n"); + return -EFAULT; + } + + comma = strnchr(buf, count, ','); + if (!comma) + goto invalid_format; + + /* split up the words */ + *comma = '\0'; + + if (kstrtou64(buf, 0, &sel)) + goto invalid_format; + + if (sel != 1 && sel != 2) + goto invalid_format; + + if (kstrtou64(comma + 1, 0, &val)) + goto invalid_format; + + arm_smmu_power_on(smmu->pwr); + + if (sel == 1) + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, CLK_TESTBUS, WRITE, val); + else if (sel == 2) + arm_smmu_debug_tcu_testbus_select(base, + tcu_base, PTW_AND_CACHE_TESTBUS, WRITE, val); + + arm_smmu_power_off(smmu, smmu->pwr); + + return count; + +invalid_format: + pr_err_ratelimited("Invalid format. Expected: <1, testbus select> for tcu CLK testbus (or) <2, testbus select> for tcu PTW/CACHE testbuses\n"); + return -EINVAL; +} + +static ssize_t arm_smmu_debug_tcu_testbus_sel_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + return arm_smmu_debug_testbus_read(file, ubuf, + count, offset, SEL_TCU, TESTBUS_SELECT); +} + +static const struct file_operations arm_smmu_debug_tcu_testbus_sel_fops = { + .open = simple_open, + .write = arm_smmu_debug_tcu_testbus_sel_write, + .read = arm_smmu_debug_tcu_testbus_sel_read, +}; + +static ssize_t arm_smmu_debug_tcu_testbus_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + return arm_smmu_debug_testbus_read(file, ubuf, + count, offset, SEL_TCU, TESTBUS_OUTPUT); +} + +static const struct file_operations arm_smmu_debug_tcu_testbus_fops = { + .open = simple_open, + .read = arm_smmu_debug_tcu_testbus_read, +}; + +static int qsmmuv500_tcu_testbus_init(struct arm_smmu_device *smmu) +{ + struct dentry *testbus_dir; + + if (!iommu_debugfs_dir) + return 0; + + if (!debugfs_testbus_dir) { + debugfs_testbus_dir = debugfs_create_dir("testbus", + iommu_debugfs_dir); + if (IS_ERR(debugfs_testbus_dir)) { + pr_err_ratelimited("Couldn't create iommu/testbus debugfs directory\n"); + return -ENODEV; + } + } + + testbus_dir = debugfs_create_dir(dev_name(smmu->dev), + debugfs_testbus_dir); + + if (IS_ERR(testbus_dir)) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s debugfs directory\n", + dev_name(smmu->dev)); + goto err; + } + + if (IS_ERR(debugfs_create_file("tcu_testbus_sel", 0400, + testbus_dir, smmu, + &arm_smmu_debug_tcu_testbus_sel_fops))) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s/tcu_testbus_sel debugfs file\n", + dev_name(smmu->dev)); + goto err_rmdir; + } + + if (IS_ERR(debugfs_create_file("tcu_testbus_output", 0400, + testbus_dir, smmu, + &arm_smmu_debug_tcu_testbus_fops))) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s/tcu_testbus_output debugfs file\n", + dev_name(smmu->dev)); + goto err_rmdir; + } + + return 0; +err_rmdir: + debugfs_remove_recursive(testbus_dir); +err: + return 0; +} + +static ssize_t arm_smmu_debug_tbu_testbus_sel_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *offset) +{ + struct qsmmuv500_tbu_device *tbu = file->private_data; + void __iomem *tbu_base = tbu->base; + u64 val; + + if (kstrtoull_from_user(ubuf, count, 0, &val)) { + pr_err_ratelimited("Invalid format for tbu testbus select\n"); + return -EINVAL; + } + + arm_smmu_power_on(tbu->pwr); + arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, val); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + + return count; +} + +static ssize_t arm_smmu_debug_tbu_testbus_sel_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + return arm_smmu_debug_testbus_read(file, ubuf, + count, offset, SEL_TBU, TESTBUS_SELECT); +} + +static const struct file_operations arm_smmu_debug_tbu_testbus_sel_fops = { + .open = simple_open, + .write = arm_smmu_debug_tbu_testbus_sel_write, + .read = arm_smmu_debug_tbu_testbus_sel_read, +}; + +static ssize_t arm_smmu_debug_tbu_testbus_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + return arm_smmu_debug_testbus_read(file, ubuf, + count, offset, SEL_TBU, TESTBUS_OUTPUT); +} + +static const struct file_operations arm_smmu_debug_tbu_testbus_fops = { + .open = simple_open, + .read = arm_smmu_debug_tbu_testbus_read, +}; + +static int qsmmuv500_tbu_testbus_init(struct qsmmuv500_tbu_device *tbu) +{ + struct dentry *testbus_dir; + + if (!iommu_debugfs_dir) + return 0; + + if (!debugfs_testbus_dir) { + debugfs_testbus_dir = debugfs_create_dir("testbus", + iommu_debugfs_dir); + if (IS_ERR(debugfs_testbus_dir)) { + pr_err_ratelimited("Couldn't create iommu/testbus debugfs directory\n"); + return -ENODEV; + } + } + + testbus_dir = debugfs_create_dir(dev_name(tbu->dev), + debugfs_testbus_dir); + + if (IS_ERR(testbus_dir)) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s debugfs directory\n", + dev_name(tbu->dev)); + goto err; + } + + if (IS_ERR(debugfs_create_file("tbu_testbus_sel", 0400, + testbus_dir, tbu, + &arm_smmu_debug_tbu_testbus_sel_fops))) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s/tbu_testbus_sel debugfs file\n", + dev_name(tbu->dev)); + goto err_rmdir; + } + + if (IS_ERR(debugfs_create_file("tbu_testbus_output", 0400, + testbus_dir, tbu, + &arm_smmu_debug_tbu_testbus_fops))) { + pr_err_ratelimited("Couldn't create iommu/testbus/%s/tbu_testbus_output debugfs file\n", + dev_name(tbu->dev)); + goto err_rmdir; + } + + return 0; +err_rmdir: + debugfs_remove_recursive(testbus_dir); +err: + return 0; +} +#else +static int qsmmuv500_tcu_testbus_init(struct arm_smmu_device *smmu) +{ + return 0; +} + +static int qsmmuv500_tbu_testbus_init(struct qsmmuv500_tbu_device *tbu) +{ + return 0; +} +#endif + +static void arm_smmu_testbus_dump(struct arm_smmu_device *smmu, u16 sid) +{ + if (smmu->model == QCOM_SMMUV500 && + IS_ENABLED(CONFIG_ARM_SMMU_TESTBUS_DUMP)) { + struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu); + struct qsmmuv500_tbu_device *tbu; + + tbu = qsmmuv500_find_tbu(smmu, sid); + spin_lock(&testbus_lock); + if (tbu) + arm_smmu_debug_dump_tbu_testbus(tbu->dev, + tbu->base, + tbu_testbus_sel); + else + arm_smmu_debug_dump_tcu_testbus(smmu->dev, + smmu->base, + data->tcu_base, + tcu_testbus_sel); + spin_unlock(&testbus_lock); + } +} static void qsmmuv500_log_outstanding_transactions(struct work_struct *work) { @@ -583,8 +911,14 @@ static void qsmmuv500_tlb_sync_timeout(struct arm_smmu_device *smmu) tbu_sync_pending ? "check pending transactions on TBU" : "check for TBU power status"); + arm_smmu_testbus_dump(smmu, + (u16)(tbu_id << TBUID_SHIFT)); } } + + /*dump TCU testbus*/ + arm_smmu_testbus_dump(smmu, U16_MAX); + } if (tcu_sync_pending) { @@ -1064,6 +1398,8 @@ static int qsmmuv500_tbu_register(struct device *dev, void *cookie) INIT_LIST_HEAD(&tbu->list); tbu->smmu = &data->smmu; list_add(&tbu->list, &data->tbus); + + qsmmuv500_tbu_testbus_init(tbu); return 0; } @@ -1156,6 +1492,8 @@ struct arm_smmu_device *qsmmuv500_impl_init(struct arm_smmu_device *smmu) data->smmu = *smmu; data->smmu.impl = &qsmmuv500_impl; + qsmmuv500_tcu_testbus_init(&data->smmu); + ret = qsmmuv500_read_actlr_tbl(data); if (ret) return ERR_PTR(ret); diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 4cec4216e3b3..baa6e23e9c44 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -110,6 +110,15 @@ struct arm_smmu_cb { #define ARM_SMMU_CB_VMID(smmu, cfg) ((u16)(smmu)->cavium_id_base + \ (cfg)->cbndx + 1) +#define TCU_TESTBUS_SEL_ALL 0x3 +#define TBU_TESTBUS_SEL_ALL 0xf + +int tbu_testbus_sel = TBU_TESTBUS_SEL_ALL; +int tcu_testbus_sel = TCU_TESTBUS_SEL_ALL; + +module_param_named(tcu_testbus_sel, tcu_testbus_sel, int, 0644); +module_param_named(tbu_testbus_sel, tbu_testbus_sel, int, 0644); + struct arm_smmu_pte_info { void *virt_addr; size_t size; From d989e632a503b8f9f62405bcc15dcf288047d770 Mon Sep 17 00:00:00 2001 From: Prakash Gupta Date: Wed, 22 Jul 2020 16:17:56 +0530 Subject: [PATCH 2/3] defconfig: arm64: enable SMMU TESTBUS configs Enable ARM_SMMU_TESTBUS_DEBUGFS and CONFIG_ARM_SMMU_TESTBUS_DUMP config. Change-Id: I5470b79f888c5358646abf358e97dcf257014d10 Signed-off-by: Prakash Gupta --- arch/arm64/configs/vendor/holi_debug.config | 2 ++ arch/arm64/configs/vendor/lahaina_consolidate.config | 2 ++ 2 files changed, 4 insertions(+) diff --git a/arch/arm64/configs/vendor/holi_debug.config b/arch/arm64/configs/vendor/holi_debug.config index d11865de8349..1ba023ec229a 100644 --- a/arch/arm64/configs/vendor/holi_debug.config +++ b/arch/arm64/configs/vendor/holi_debug.config @@ -1,5 +1,7 @@ CONFIG_LOCALVERSION="-qgki-debug" CONFIG_IOMMU_TLBSYNC_DEBUG=y +CONFIG_ARM_SMMU_TESTBUS_DUMP=y +CONFIG_ARM_SMMU_TESTBUS_DEBUGFS=y CONFIG_IO_PGTABLE_PAGE_ACCOUNTING=y CONFIG_CMA_DEBUGFS=y CONFIG_CMA_DEBUG=y diff --git a/arch/arm64/configs/vendor/lahaina_consolidate.config b/arch/arm64/configs/vendor/lahaina_consolidate.config index 9f790b89d018..94c768a88688 100644 --- a/arch/arm64/configs/vendor/lahaina_consolidate.config +++ b/arch/arm64/configs/vendor/lahaina_consolidate.config @@ -2,6 +2,8 @@ CONFIG_LOCALVERSION="-qgki-consolidate" CONFIG_IOMMU_TLBSYNC_DEBUG=y CONFIG_IOMMU_DEBUG=y CONFIG_IOMMU_TESTS=y +CONFIG_ARM_SMMU_TESTBUS_DEBUGFS=y +CONFIG_ARM_SMMU_TESTBUS_DUMP=y CONFIG_IO_PGTABLE_PAGE_ACCOUNTING=y CONFIG_CMA_DEBUGFS=y CONFIG_CMA_DEBUG=y From 47b2cb7ab2ea0e0a0506a3b4bd7bc78ed502d95d Mon Sep 17 00:00:00 2001 From: Faiyaz Mohammed Date: Mon, 27 Jul 2020 12:39:23 +0530 Subject: [PATCH 3/3] iommu: arm-smmu: update access to SMMU TCU testbus registers with scm call Update access to SMMU TCU testbus and testbus_sel registers using scm. Change-Id: I5a125070977ffa061a8ee94fdbd9f56603c00a82 Signed-off-by: Faiyaz Mohammed [guptap@codeaurora.org: resolved trival merge conflicts] Signed-off-by: Prakash Gupta --- drivers/iommu/arm-smmu-debug.c | 100 ++++++++++++++++++++++----------- drivers/iommu/arm-smmu-debug.h | 16 +++--- drivers/iommu/arm-smmu-qcom.c | 16 +++--- 3 files changed, 82 insertions(+), 50 deletions(-) diff --git a/drivers/iommu/arm-smmu-debug.c b/drivers/iommu/arm-smmu-debug.c index c6b6816bd491..0220da38d817 100644 --- a/drivers/iommu/arm-smmu-debug.c +++ b/drivers/iommu/arm-smmu-debug.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ #include @@ -8,6 +8,7 @@ #include #include "arm-smmu.h" #include "arm-smmu-debug.h" +#include u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, @@ -28,33 +29,59 @@ u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base) return readl_relaxed(tbu_base + DEBUG_TESTBUS_TBU); } -u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, +u32 arm_smmu_debug_tcu_testbus_select(phys_addr_t phys_addr, void __iomem *tcu_base, enum tcu_testbus testbus, bool write, u32 val) { int offset; + u32 testbus_sel; + int ret = 0; if (testbus == CLK_TESTBUS) { - base = tcu_base; offset = ARM_SMMU_TESTBUS_SEL_HLOS1_NS; + if (write) { + writel_relaxed(val, tcu_base + offset); + /* Make sure tcu select register is written to */ + wmb(); + } else { + return readl_relaxed(tcu_base + offset); + } } else { offset = ARM_SMMU_TESTBUS_SEL; - } + if (write) { + ret = qcom_scm_io_writel((phys_addr + offset), val); + if (ret) + pr_err_ratelimited("SCM write of TESTBUS SEL fails: %d\n", + ret); - if (write) { - writel_relaxed(val, base + offset); - /* Make sure tcu select register is written to */ - wmb(); - } else { - return readl_relaxed(base + offset); + /* Make sure tcu select register is written to */ + wmb(); + } else { + ret = qcom_scm_io_readl(phys_addr + offset, + &testbus_sel); + if (ret) + pr_err_ratelimited("SCM write of TESTBUS SEL fails: %d\n", + ret); + else + return testbus_sel; + } } return 0; } -u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base) +u32 arm_smmu_debug_tcu_testbus_output(phys_addr_t phys_addr) { - return readl_relaxed(base + ARM_SMMU_TESTBUS); + u32 testbus_output; + int ret; + + ret = qcom_scm_io_readl(phys_addr + ARM_SMMU_TESTBUS, &testbus_output); + if (!ret) + return testbus_output; + + pr_err_ratelimited("SCM write of TESTBUS output fails: %d\n", ret); + + return 0; } static void arm_smmu_debug_dump_tbu_qns4_testbus(struct device *dev, @@ -123,7 +150,7 @@ void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, } static void arm_smmu_debug_program_tcu_testbus(struct device *dev, - void __iomem *base, void __iomem *tcu_base, + phys_addr_t phys_addr, void __iomem *tcu_base, unsigned long mask, int start, int end, int shift, bool print) { @@ -131,30 +158,30 @@ static void arm_smmu_debug_program_tcu_testbus(struct device *dev, int i; for (i = start; i < end; i++) { - reg = arm_smmu_debug_tcu_testbus_select(base, tcu_base, + reg = arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0); reg &= mask; reg |= i << shift; - arm_smmu_debug_tcu_testbus_select(base, tcu_base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, PTW_AND_CACHE_TESTBUS, WRITE, reg); if (print) dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n", - arm_smmu_debug_tcu_testbus_select(base, - tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), - i, arm_smmu_debug_tcu_testbus_output(base)); + arm_smmu_debug_tcu_testbus_select(phys_addr, + tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), i, + arm_smmu_debug_tcu_testbus_output(phys_addr)); } } -void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base, - void __iomem *tcu_base, int tcu_testbus_sel) +void arm_smmu_debug_dump_tcu_testbus(struct device *dev, phys_addr_t phys_addr, + void __iomem *tcu_base, int tcu_testbus_sel) { int i; if (tcu_testbus_sel & TCU_CACHE_TESTBUS_SEL) { dev_info(dev, "Dumping TCU cache testbus:\n"); - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, tcu_base, TCU_CACHE_TESTBUS, 0, 1, 0, false); - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, tcu_base, ~TCU_PTW_QUEUE_MASK, 0, TCU_CACHE_LOOKUP_QUEUE_SIZE, 2, true); @@ -162,32 +189,37 @@ void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base, if (tcu_testbus_sel & TCU_PTW_TESTBUS_SEL) { dev_info(dev, "Dumping TCU PTW test bus:\n"); - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, 1, + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, tcu_base, 1, TCU_PTW_TESTBUS, TCU_PTW_TESTBUS + 1, 0, false); - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, tcu_base, ~TCU_PTW_INTERNAL_STATES_MASK, 0, TCU_PTW_INTERNAL_STATES, 2, true); for (i = TCU_PTW_QUEUE_START; i < TCU_PTW_QUEUE_START + TCU_PTW_QUEUE_SIZE; ++i) { - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, - ~TCU_PTW_QUEUE_MASK, i, i + 1, 2, true); - arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, - ~TCU_PTW_TESTBUS_SEL2_MASK, TCU_PTW_TESTBUS_SEL2, - TCU_PTW_TESTBUS_SEL2 + 1, 0, false); + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, + tcu_base, + ~TCU_PTW_QUEUE_MASK, + i, i + 1, 2, true); + arm_smmu_debug_program_tcu_testbus(dev, phys_addr, + tcu_base, + ~TCU_PTW_TESTBUS_SEL2_MASK, + TCU_PTW_TESTBUS_SEL2, + TCU_PTW_TESTBUS_SEL2 + 1, 0, + false); dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n", - arm_smmu_debug_tcu_testbus_select(base, - tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), - i, arm_smmu_debug_tcu_testbus_output(base)); + arm_smmu_debug_tcu_testbus_select(phys_addr, + tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0), i, + arm_smmu_debug_tcu_testbus_output(phys_addr)); } } /* program ARM_SMMU_TESTBUS_SEL_HLOS1_NS to select TCU clk testbus*/ - arm_smmu_debug_tcu_testbus_select(base, tcu_base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, CLK_TESTBUS, WRITE, TCU_CLK_TESTBUS_SEL); dev_info(dev, "Programming Tcu clk gate controller: testbus_sel: 0x%lx\n", - arm_smmu_debug_tcu_testbus_select(base, tcu_base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, CLK_TESTBUS, READ, 0)); } diff --git a/drivers/iommu/arm-smmu-debug.h b/drivers/iommu/arm-smmu-debug.h index c978354b27b0..a1837bdad9b0 100644 --- a/drivers/iommu/arm-smmu-debug.h +++ b/drivers/iommu/arm-smmu-debug.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2020, The Linux Foundation. All rights reserved. */ #define ARM_SMMU_TESTBUS_SEL 0x25E4 @@ -58,13 +58,13 @@ enum testbus_ops { u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, bool write, u32 val); u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base); -u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, +u32 arm_smmu_debug_tcu_testbus_select(phys_addr_t phys_addr, void __iomem *tcu_base, enum tcu_testbus testbus, bool write, u32 val); -u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base); +u32 arm_smmu_debug_tcu_testbus_output(phys_addr_t phys_addr); void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, int tbu_testbus_sel); -void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base, +void arm_smmu_debug_dump_tcu_testbus(struct device *dev, phys_addr_t phys_addr, void __iomem *tcu_base, int tcu_testbus_sel); #else @@ -77,12 +77,12 @@ static inline u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base) { return 0; } -u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base, - void __iomem *tcu_base, enum tcu_testbus testbus, +u32 arm_smmu_debug_tcu_testbus_select(phys_addr_t phys_addr, + void __iomem *tcu_base, enum tcu_testbus testbus, bool write, u32 val) { } -static inline u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base) +static inline u32 arm_smmu_debug_tcu_testbus_output(phys_addr_t phys_addr) { return 0; } @@ -91,7 +91,7 @@ static inline void arm_smmu_debug_dump_tbu_testbus(struct device *dev, { } static inline void arm_smmu_debug_dump_tcu_testbus(struct device *dev, - void __iomem *base, void __iomem *tcu_base, + phys_addr_t phys_addr, void __iomem *tcu_base, int tcu_testbus_sel) { } diff --git a/drivers/iommu/arm-smmu-qcom.c b/drivers/iommu/arm-smmu-qcom.c index 8f839af05c8a..3f3c6ef822c7 100644 --- a/drivers/iommu/arm-smmu-qcom.c +++ b/drivers/iommu/arm-smmu-qcom.c @@ -462,23 +462,23 @@ static ssize_t arm_smmu_debug_testbus_read(struct file *file, struct arm_smmu_device *smmu = file->private_data; struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu); - void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0); + phys_addr_t phys_addr = smmu->phys_addr; void __iomem *tcu_base = data->tcu_base; arm_smmu_power_on(smmu->pwr); if (ops == TESTBUS_SELECT) { scnprintf(buf, buf_len, "TCU clk testbus sel: 0x%0x\n", - arm_smmu_debug_tcu_testbus_select(base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, CLK_TESTBUS, READ, 0)); scnprintf(buf + strlen(buf), buf_len - strlen(buf), "TCU testbus sel : 0x%0x\n", - arm_smmu_debug_tcu_testbus_select(base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0)); } else { scnprintf(buf, buf_len, "0x%0x\n", - arm_smmu_debug_tcu_testbus_output(base)); + arm_smmu_debug_tcu_testbus_output(phys_addr)); } arm_smmu_power_off(smmu, smmu->pwr); @@ -501,7 +501,7 @@ static ssize_t arm_smmu_debug_tcu_testbus_sel_write(struct file *file, struct arm_smmu_device *smmu = file->private_data; struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu); void __iomem *tcu_base = data->tcu_base; - void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0); + phys_addr_t phys_addr = smmu->phys_addr; char *comma; char buf[100]; u64 sel, val; @@ -537,10 +537,10 @@ static ssize_t arm_smmu_debug_tcu_testbus_sel_write(struct file *file, arm_smmu_power_on(smmu->pwr); if (sel == 1) - arm_smmu_debug_tcu_testbus_select(base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, CLK_TESTBUS, WRITE, val); else if (sel == 2) - arm_smmu_debug_tcu_testbus_select(base, + arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, PTW_AND_CACHE_TESTBUS, WRITE, val); arm_smmu_power_off(smmu, smmu->pwr); @@ -743,7 +743,7 @@ static void arm_smmu_testbus_dump(struct arm_smmu_device *smmu, u16 sid) tbu_testbus_sel); else arm_smmu_debug_dump_tcu_testbus(smmu->dev, - smmu->base, + smmu->phys_addr, data->tcu_base, tcu_testbus_sel); spin_unlock(&testbus_lock);