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;