* [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
@ 2026-03-28 10:17 ` Qinxin Xia
2026-03-30 10:46 ` Nicolin Chen
2026-03-28 10:17 ` [RFC PATCH v2 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
` (10 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-03-28 10:17 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm
Add basic debugfs framework for ARM SMMUv3 driver.This creates the
root directory structure and provides capability display functionality.
The debugfs hierarchy is organized as:
/sys/kernel/debug/iommu/arm_smmu_v3/
└── smmu<ioaddr>/
└── capabilities
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
drivers/iommu/Kconfig | 11 ++
drivers/iommu/arm/arm-smmu-v3/Makefile | 1 +
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 163 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 15 ++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 15 ++
5 files changed, 205 insertions(+)
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index f86262b11416..f28f09adba03 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -93,6 +93,17 @@ config IOMMU_DEBUGFS
debug/iommu directory, and then populate a subdirectory with
entries as required.
+config ARM_SMMU_V3_DEBUGFS
+ bool "ARM SMMUv3 DebugFS support"
+ depends on ARM_SMMU_V3 && IOMMU_DEBUGFS
+ help
+ Expose ARM SMMUv3 internal state via debugfs for debugging and
+ diagnostics. This creates /sys/kernel/debug/iommu/arm_smmu_v3/
+ with detailed information about SMMU configuration, stream tables,
+ and context descriptors.
+
+ Say N unless you are debugging SMMU issues.
+
choice
prompt "IOMMU default domain type"
depends on IOMMU_API
diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile
index 493a659cc66b..787538fb7054 100644
--- a/drivers/iommu/arm/arm-smmu-v3/Makefile
+++ b/drivers/iommu/arm/arm-smmu-v3/Makefile
@@ -4,5 +4,6 @@ arm_smmu_v3-y := arm-smmu-v3.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o
arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o
+arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_DEBUGFS) += arm-smmu-v3-debugfs.o
obj-$(CONFIG_ARM_SMMU_V3_KUNIT_TEST) += arm-smmu-v3-test.o
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
new file mode 100644
index 000000000000..c764b28e5cfb
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM SMMUv3 DebugFS Support
+ *
+ * Directory Structure:
+ * /sys/kernel/debug/iommu/arm_smmu_v3/
+ * └── smmu<ioaddr>/
+ * └── capabilities # SMMU feature capabilities and configuration
+ *
+ * The capabilities file provides detailed information about:
+ * - translation stage support (Stage1/Stage2)
+ * - System coherency, ATS, and PRI feature availability
+ * - Stream table size and command/event queue depths
+ *
+ * Copyright (C) 2026 HiSilicon Limited.
+ * Author: Qinxin Xia <xiaqinxin@huawei.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include "arm-smmu-v3.h"
+
+static struct dentry *smmu_debugfs_root;
+static DEFINE_MUTEX(arm_smmu_debugfs_lock);
+
+/**
+ * smmu_debugfs_capabilities_show() - Display SMMU capabilities
+ * @seq: seq_file to write to
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_capabilities_show(struct seq_file *seq, void *unused)
+{
+ struct arm_smmu_device *smmu = seq->private;
+
+ if (!smmu) {
+ seq_puts(seq, "SMMU not available\n");
+ return 0;
+ }
+
+ seq_puts(seq, "SMMUv3 Capabilities:\n");
+ seq_printf(seq, " Stage1 Translation: %s\n",
+ smmu->features & ARM_SMMU_FEAT_TRANS_S1 ? "Yes" : "No");
+ seq_printf(seq, " Stage2 Translation: %s\n",
+ smmu->features & ARM_SMMU_FEAT_TRANS_S2 ? "Yes" : "No");
+ seq_printf(seq, " Coherent Walk: %s\n",
+ smmu->features & ARM_SMMU_FEAT_COHERENCY ? "Yes" : "No");
+ seq_printf(seq, " ATS Support: %s\n",
+ smmu->features & ARM_SMMU_FEAT_ATS ? "Yes" : "No");
+ seq_printf(seq, " PRI Support: %s\n",
+ smmu->features & ARM_SMMU_FEAT_PRI ? "Yes" : "No");
+ seq_printf(seq, " Stream Table Size: %d\n", 1 << smmu->sid_bits);
+ seq_printf(seq, " Command Queue Depth: %d\n",
+ 1 << smmu->cmdq.q.llq.max_n_shift);
+ seq_printf(seq, " Event Queue Depth: %d\n",
+ 1 << smmu->evtq.q.llq.max_n_shift);
+
+ return 0;
+}
+
+static int smmu_debugfs_capabilities_open(struct inode *inode, struct file *file)
+{
+ struct arm_smmu_device *smmu = inode->i_private;
+
+ if (!smmu || !get_device(smmu->dev))
+ return -ENODEV;
+
+ return single_open(file, smmu_debugfs_capabilities_show, smmu);
+}
+
+static int smmu_debugfs_capabilities_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct arm_smmu_device *smmu = seq->private;
+
+ single_release(inode, file);
+ if (smmu)
+ put_device(smmu->dev);
+
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_capabilities_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_capabilities_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_capabilities_release,
+};
+
+/**
+ * arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
+ * @smmu: SMMU device to setup debugfs for
+ * @name: SMMU device name
+ *
+ * This function creates the basic debugfs directory structure for an SMMU device.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
+{
+ struct arm_smmu_debugfs *debugfs;
+ struct dentry *smmu_dir;
+
+ /* Create root directory if it doesn't exist */
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ if (!smmu_debugfs_root) {
+ /* Once created, it will not be removed */
+ smmu_debugfs_root = debugfs_create_dir("arm_smmu_v3",
+ iommu_debugfs_dir);
+ if (!smmu_debugfs_root)
+ return -ENOMEM;
+ }
+ }
+
+ /* Allocate debugfs structure */
+ debugfs = kzalloc_obj(*debugfs);
+ if (!debugfs)
+ return -ENOMEM;
+
+ /* Create SMMU instance directory */
+ smmu_dir = debugfs_create_dir(name, smmu_debugfs_root);
+ if (!smmu_dir) {
+ kfree(debugfs);
+ smmu->debugfs = NULL;
+ return -ENOMEM;
+ }
+
+ debugfs->smmu_dir = smmu_dir;
+ smmu->debugfs = debugfs;
+
+ /* Create capabilities file */
+ debugfs_create_file("capabilities", 0444, smmu_dir, smmu,
+ &smmu_debugfs_capabilities_fops);
+
+ dev_dbg(smmu->dev, "debugfs initialized for %s\n", name);
+ return 0;
+}
+
+/**
+ * arm_smmu_debugfs_remove() - Clean up debugfs entries for an SMMU device
+ * @smmu: SMMU device
+ *
+ * This function removes the debugfs directories created by setup.
+ */
+void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu)
+{
+ struct arm_smmu_debugfs *debugfs;
+
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ debugfs = smmu->debugfs;
+ if (!debugfs)
+ return;
+
+ /* Remove the entire SMMU instance directory */
+ debugfs_remove_recursive(debugfs->smmu_dir);
+
+ /* Free the debugfs structure */
+ kfree(debugfs);
+ smmu->debugfs = NULL;
+ }
+}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 4d00d796f078..cbb3fccc501b 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4904,6 +4904,15 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
/* Check for RMRs and install bypass STEs if any */
arm_smmu_rmr_install_bypass_ste(smmu);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ char name[32];
+
+ snprintf(name, sizeof(name), "smmu3.%pa", &ioaddr);
+ ret = arm_smmu_debugfs_setup(smmu, name);
+ if (ret)
+ dev_warn(dev, "Failed to create debugfs!\n");
+#endif
+
/* Reset the device */
ret = arm_smmu_device_reset(smmu);
if (ret)
@@ -4926,6 +4935,9 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
err_free_sysfs:
iommu_device_sysfs_remove(&smmu->iommu);
err_disable:
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove(smmu);
+#endif
arm_smmu_device_disable(smmu);
err_free_iopf:
iopf_queue_free(smmu->evtq.iopf);
@@ -4938,6 +4950,9 @@ static void arm_smmu_device_remove(struct platform_device *pdev)
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove(smmu);
+#endif
arm_smmu_device_disable(smmu);
iopf_queue_free(smmu->evtq.iopf);
ida_destroy(&smmu->vmid_map);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 3c6d65d36164..a54d72fb9e07 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -733,6 +733,16 @@ struct arm_smmu_impl_ops {
const struct iommu_user_data *user_data);
};
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+struct arm_smmu_debugfs {
+ struct dentry *smmu_dir;
+ /* Reserved for future extensions */
+};
+
+int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name);
+void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu);
+#endif
+
/* An SMMUv3 instance */
struct arm_smmu_device {
struct device *dev;
@@ -803,6 +813,11 @@ struct arm_smmu_device {
struct rb_root streams;
struct mutex streams_mutex;
+
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ /* DebugFS Info */
+ struct arm_smmu_debugfs *debugfs;
+#endif
};
struct arm_smmu_stream {
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework
2026-03-28 10:17 ` [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
@ 2026-03-30 10:46 ` Nicolin Chen
2026-04-02 3:50 ` Qinxin Xia
0 siblings, 1 reply; 22+ messages in thread
From: Nicolin Chen @ 2026-03-30 10:46 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm
On Sat, Mar 28, 2026 at 06:17:02PM +0800, Qinxin Xia wrote:
> Add basic debugfs framework for ARM SMMUv3 driver.This creates the
Needs a space after "."
> +static int smmu_debugfs_capabilities_show(struct seq_file *seq, void *unused)
> +{
> + struct arm_smmu_device *smmu = seq->private;
> +
> + if (!smmu) {
> + seq_puts(seq, "SMMU not available\n");
> + return 0;
> + }
> +
> + seq_puts(seq, "SMMUv3 Capabilities:\n");
> + seq_printf(seq, " Stage1 Translation: %s\n",
> + smmu->features & ARM_SMMU_FEAT_TRANS_S1 ? "Yes" : "No");
> + seq_printf(seq, " Stage2 Translation: %s\n",
> + smmu->features & ARM_SMMU_FEAT_TRANS_S2 ? "Yes" : "No");
> + seq_printf(seq, " Coherent Walk: %s\n",
> + smmu->features & ARM_SMMU_FEAT_COHERENCY ? "Yes" : "No");
> + seq_printf(seq, " ATS Support: %s\n",
> + smmu->features & ARM_SMMU_FEAT_ATS ? "Yes" : "No");
> + seq_printf(seq, " PRI Support: %s\n",
> + smmu->features & ARM_SMMU_FEAT_PRI ? "Yes" : "No");
> + seq_printf(seq, " Stream Table Size: %d\n", 1 << smmu->sid_bits);
> + seq_printf(seq, " Command Queue Depth: %d\n",
> + 1 << smmu->cmdq.q.llq.max_n_shift);
> + seq_printf(seq, " Event Queue Depth: %d\n",
> + 1 << smmu->evtq.q.llq.max_n_shift);
Nit: should we do all sizes or all depths? Any good reason to mix
them here?
> +/**
> + * arm_smmu_debugfs_remove() - Clean up debugfs entries for an SMMU device
> + * @smmu: SMMU device
> + *
> + * This function removes the debugfs directories created by setup.
> + */
> +void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu)
> +{
> + struct arm_smmu_debugfs *debugfs;
> +
> + scoped_guard(mutex, &arm_smmu_debugfs_lock) {
It could be just normal guard().
> diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> index 4d00d796f078..cbb3fccc501b 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -4904,6 +4904,15 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
> /* Check for RMRs and install bypass STEs if any */
> arm_smmu_rmr_install_bypass_ste(smmu);
>
> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
> + char name[32];
This could be moved to the top, as iommu_device_sysfs_add() can use
it, whether CONFIG_ARM_SMMU_V3_DEBUGFS=y or =n.
> + snprintf(name, sizeof(name), "smmu3.%pa", &ioaddr);
And this could be moved after ioaddr gets a copy from res->start.
>
> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
> +struct arm_smmu_debugfs {
> + struct dentry *smmu_dir;
A personal preference: for new structures, maybe drop those tabs?
> /* An SMMUv3 instance */
> struct arm_smmu_device {
> struct device *dev;
> @@ -803,6 +813,11 @@ struct arm_smmu_device {
>
> struct rb_root streams;
> struct mutex streams_mutex;
> +
> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
> + /* DebugFS Info */
Doesn't seem very useful. I'd drop it.
> + struct arm_smmu_debugfs *debugfs;
> +#endif
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework
2026-03-30 10:46 ` Nicolin Chen
@ 2026-04-02 3:50 ` Qinxin Xia
0 siblings, 0 replies; 22+ messages in thread
From: Qinxin Xia @ 2026-04-02 3:50 UTC (permalink / raw)
To: Nicolin Chen
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm
On 2026/3/30 18:46:57, Nicolin Chen <nicolinc@nvidia.com> wrote:
> On Sat, Mar 28, 2026 at 06:17:02PM +0800, Qinxin Xia wrote:
>> Add basic debugfs framework for ARM SMMUv3 driver.This creates the
>
> Needs a space after "."
>
>> +static int smmu_debugfs_capabilities_show(struct seq_file *seq, void *unused)
>> +{
>> + struct arm_smmu_device *smmu = seq->private;
>> +
>> + if (!smmu) {
>> + seq_puts(seq, "SMMU not available\n");
>> + return 0;
>> + }
>> +
>> + seq_puts(seq, "SMMUv3 Capabilities:\n");
>> + seq_printf(seq, " Stage1 Translation: %s\n",
>> + smmu->features & ARM_SMMU_FEAT_TRANS_S1 ? "Yes" : "No");
>> + seq_printf(seq, " Stage2 Translation: %s\n",
>> + smmu->features & ARM_SMMU_FEAT_TRANS_S2 ? "Yes" : "No");
>> + seq_printf(seq, " Coherent Walk: %s\n",
>> + smmu->features & ARM_SMMU_FEAT_COHERENCY ? "Yes" : "No");
>> + seq_printf(seq, " ATS Support: %s\n",
>> + smmu->features & ARM_SMMU_FEAT_ATS ? "Yes" : "No");
>> + seq_printf(seq, " PRI Support: %s\n",
>> + smmu->features & ARM_SMMU_FEAT_PRI ? "Yes" : "No");
>> + seq_printf(seq, " Stream Table Size: %d\n", 1 << smmu->sid_bits);
>> + seq_printf(seq, " Command Queue Depth: %d\n",
>> + 1 << smmu->cmdq.q.llq.max_n_shift);
>> + seq_printf(seq, " Event Queue Depth: %d\n",
>> + 1 << smmu->evtq.q.llq.max_n_shift);
>
> Nit: should we do all sizes or all depths? Any good reason to mix
> them here?
>
Stream Table is a table, not a queue, so 'Size' is more appropriate;
queues use 'Depth'. But I can change to unify if preferred.
>> +/**
>> + * arm_smmu_debugfs_remove() - Clean up debugfs entries for an SMMU device
>> + * @smmu: SMMU device
>> + *
>> + * This function removes the debugfs directories created by setup.
>> + */
>> +void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu)
>> +{
>> + struct arm_smmu_debugfs *debugfs;
>> +
>> + scoped_guard(mutex, &arm_smmu_debugfs_lock) {
>
> It could be just normal guard().
>
>> diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>> index 4d00d796f078..cbb3fccc501b 100644
>> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>> @@ -4904,6 +4904,15 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
>> /* Check for RMRs and install bypass STEs if any */
>> arm_smmu_rmr_install_bypass_ste(smmu);
>>
>> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
>> + char name[32];
>
> This could be moved to the top, as iommu_device_sysfs_add() can use
> it, whether CONFIG_ARM_SMMU_V3_DEBUGFS=y or =n.
>
>> + snprintf(name, sizeof(name), "smmu3.%pa", &ioaddr);
>
> And this could be moved after ioaddr gets a copy from res->start.
>
>>
>> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
>> +struct arm_smmu_debugfs {
>> + struct dentry *smmu_dir;
>
> A personal preference: for new structures, maybe drop those tabs?
>
>> /* An SMMUv3 instance */
>> struct arm_smmu_device {
>> struct device *dev;
>> @@ -803,6 +813,11 @@ struct arm_smmu_device {
>>
>> struct rb_root streams;
>> struct mutex streams_mutex;
>> +
>> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
>> + /* DebugFS Info */
>
> Doesn't seem very useful. I'd drop it.
>
>> + struct arm_smmu_debugfs *debugfs;
>> +#endif
>
> Nicolin
>
--
Thanks,
Qinxin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH v2 2/5] iommu/arm-smmu-v3: Add register display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
2026-03-28 10:17 ` [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
@ 2026-03-28 10:17 ` Qinxin Xia
2026-03-30 11:25 ` Nicolin Chen
2026-03-28 10:17 ` [RFC PATCH v2 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
` (9 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-03-28 10:17 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm
Add register display functionality to debugfs.This allows reading
and displaying key SMMU register values including control registers
and queue pointers.
The registers file shows:
- CR0, CR1, CR2 control registers
- Command and Event queue pointers
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 78 ++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index c764b28e5cfb..cfd296aebc9f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -5,13 +5,18 @@
* Directory Structure:
* /sys/kernel/debug/iommu/arm_smmu_v3/
* └── smmu<ioaddr>/
- * └── capabilities # SMMU feature capabilities and configuration
+ * ├── capabilities # SMMU feature capabilities and configuration
+ * └── registers # SMMU Key registers
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
* - System coherency, ATS, and PRI feature availability
* - Stream table size and command/event queue depths
*
+ * The registers display provides crucial visibility into:
+ * - CR0, CR1, CR2 control registers
+ * - Command and Event queue pointers
+ *
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -89,6 +94,74 @@ static const struct file_operations smmu_debugfs_capabilities_fops = {
.release = smmu_debugfs_capabilities_release,
};
+/**
+ * smmu_debugfs_registers_show() - Display SMMU register values
+ * @seq: seq_file to write to
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_registers_show(struct seq_file *seq, void *unused)
+{
+ struct arm_smmu_device *smmu = seq->private;
+ void __iomem *base;
+
+ if (!smmu || !smmu->base) {
+ seq_puts(seq, "SMMU not available\n");
+ return 0;
+ }
+
+ base = smmu->base;
+
+ seq_puts(seq, "SMMUv3 Key Registers:\n");
+
+ /* 32-bit control registers */
+ seq_printf(seq, "CR0: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR0));
+ seq_printf(seq, "CR1: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR1));
+ seq_printf(seq, "CR2: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR2));
+
+ /* 32-bit queue pointer registers */
+ seq_printf(seq, "CMDQ_PROD: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_CMDQ_PROD));
+ seq_printf(seq, "CMDQ_CONS: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_CMDQ_CONS));
+ seq_printf(seq, "EVTQ_PROD: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_EVTQ_PROD));
+ seq_printf(seq, "EVTQ_CONS: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_EVTQ_CONS));
+
+ return 0;
+}
+
+static int smmu_debugfs_registers_open(struct inode *inode, struct file *file)
+{
+ struct arm_smmu_device *smmu = inode->i_private;
+
+ if (!smmu || !get_device(smmu->dev))
+ return -ENODEV;
+
+ return single_open(file, smmu_debugfs_registers_show, smmu);
+}
+
+static int smmu_debugfs_registers_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct arm_smmu_device *smmu = seq->private;
+
+ single_release(inode, file);
+ if (smmu)
+ put_device(smmu->dev);
+
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_registers_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_registers_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_registers_release,
+};
+
/**
* arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
* @smmu: SMMU device to setup debugfs for
@@ -134,6 +207,9 @@ int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
debugfs_create_file("capabilities", 0444, smmu_dir, smmu,
&smmu_debugfs_capabilities_fops);
+ debugfs_create_file("registers", 0444, smmu_dir, smmu,
+ &smmu_debugfs_registers_fops);
+
dev_dbg(smmu->dev, "debugfs initialized for %s\n", name);
return 0;
}
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [RFC PATCH v2 2/5] iommu/arm-smmu-v3: Add register display to debugfs
2026-03-28 10:17 ` [RFC PATCH v2 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
@ 2026-03-30 11:25 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-03-30 11:25 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm
On Sat, Mar 28, 2026 at 06:17:03PM +0800, Qinxin Xia wrote:
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
> @@ -5,13 +5,18 @@
> * Directory Structure:
> * /sys/kernel/debug/iommu/arm_smmu_v3/
> * └── smmu<ioaddr>/
> - * └── capabilities # SMMU feature capabilities and configuration
Nit: perhaps the last line in every patch could start with '|-',
so we wouldn't need to update it every time.
> + * ├── capabilities # SMMU feature capabilities and configuration
> + * └── registers # SMMU Key registers
Maybe replace '\t' with whitespaces? The final file doesn't seem
to have this line properly aligned with others also.
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH v2 3/5] iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
2026-03-28 10:17 ` [RFC PATCH v2 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
2026-03-28 10:17 ` [RFC PATCH v2 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
@ 2026-03-28 10:17 ` Qinxin Xia
2026-04-04 5:43 ` Nicolin Chen
2026-03-28 10:17 ` [RFC PATCH v2 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs Qinxin Xia
` (8 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-03-28 10:17 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm
Add Stream Table Entry (STE) display functionality to debugfs.
This allow inspecting STE contents for each device stream including:
- STE validity and configuration
- Stage 1 and Stage 2 context pointers
- Raw STE data
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
└─── ste
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 224 +++++++++++++++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 18 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 17 ++
3 files changed, 256 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index cfd296aebc9f..70623b480d64 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -6,7 +6,10 @@
* /sys/kernel/debug/iommu/arm_smmu_v3/
* └── smmu<ioaddr>/
* ├── capabilities # SMMU feature capabilities and configuration
- * └── registers # SMMU Key registers
+ * ├── registers # SMMU Key registers
+ * └── stream_table
+ * └─── <sid>/ # Stream ID 0
+ * └── ste # Stream Table Entry
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
@@ -17,6 +20,11 @@
* - CR0, CR1, CR2 control registers
* - Command and Event queue pointers
*
+ * The STE Information Displayed:
+ * - STE validity and configuration
+ * - Stage 1 and Stage 2 context pointers
+ * - Raw STE data
+ *
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -162,6 +170,218 @@ static const struct file_operations smmu_debugfs_registers_fops = {
.release = smmu_debugfs_registers_release,
};
+/**
+ * smmu_debugfs_ste_show() - Dump STE details to seq_file
+ * @seq: seq_file to write to
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_ste_show(struct seq_file *seq, void *unused)
+{
+ struct ste_context *ctx = seq->private;
+ struct arm_smmu_master *master = ctx->master;
+ struct arm_smmu_device *smmu;
+ struct arm_smmu_ste *ste;
+ u32 sid, cfg;
+ int i;
+
+ if (!master) {
+ seq_puts(seq, "No SMMU master data\n");
+ return 0;
+ }
+
+ smmu = master->smmu;
+ scoped_guard(mutex, &smmu->streams_mutex) {
+ sid = ctx->sid;
+
+ if (!arm_smmu_sid_in_range(smmu, sid)) {
+ seq_printf(seq, "Invalid Stream ID: %u (max %u)\n",
+ sid, (1 << smmu->sid_bits) - 1);
+ return 0;
+ }
+
+ ste = arm_smmu_get_step_for_sid(smmu, sid);
+ if (!ste) {
+ seq_printf(seq, "STE not available for SID %u\n", sid);
+ return 0;
+ }
+
+ seq_printf(seq, "STE for Stream ID %u\n", sid);
+ seq_printf(seq, " Valid: %s\n",
+ le64_to_cpu(ste->data[0]) & STRTAB_STE_0_V ? "Yes" : "No");
+
+ seq_puts(seq, " Config: ");
+
+ cfg = FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(ste->data[0]));
+
+ switch (cfg) {
+ case STRTAB_STE_0_CFG_BYPASS:
+ seq_puts(seq, "BYPASS\n");
+ break;
+ case STRTAB_STE_0_CFG_S1_TRANS:
+ seq_puts(seq, "only S1_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_S2_TRANS:
+ seq_puts(seq, "only S2_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_NESTED:
+ seq_puts(seq, "S1+S2_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_ABORT:
+ seq_puts(seq, "ABORT\n");
+ break;
+ default:
+ seq_puts(seq, "UNKNOWN\n");
+ }
+
+ if (le64_to_cpu(ste->data[0]) & STRTAB_STE_0_CFG_S1_TRANS) {
+ seq_printf(seq, " S1ContextPtr: 0x%016llx\n",
+ le64_to_cpu(ste->data[1]) & STRTAB_STE_0_S1CTXPTR_MASK);
+ }
+
+ if (le64_to_cpu(ste->data[0]) & STRTAB_STE_0_CFG_S2_TRANS) {
+ seq_printf(seq, " S2ContextPtr: 0x%016llx\n",
+ le64_to_cpu(ste->data[3]) & STRTAB_STE_3_S2TTB_MASK);
+ }
+
+ /* Display raw STE data */
+ seq_puts(seq, " Raw Data:\n");
+ for (i = 0; i < STRTAB_STE_DWORDS; i++)
+ seq_printf(seq, " STE[%d]: 0x%016llx\n", i,
+ le64_to_cpu(ste->data[i]));
+ }
+ return 0;
+}
+
+static int smmu_debugfs_ste_open(struct inode *inode, struct file *file)
+{
+ struct ste_context *ctx = inode->i_private;
+
+ if (!ctx || !get_device(ctx->master->dev))
+ return -ENODEV;
+
+ return single_open(file, smmu_debugfs_ste_show, ctx);
+}
+
+static int smmu_debugfs_ste_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct ste_context *ctx = seq->private;
+
+ single_release(inode, file);
+ if (ctx)
+ put_device(ctx->master->dev);
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_ste_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_ste_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_ste_release,
+};
+
+/**
+ * arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
+ * @dev: device to create entries for
+ * @smmu: SMMU device
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int arm_smmu_debugfs_create_stream_table(struct device *dev,
+ struct arm_smmu_device *smmu)
+{
+ struct dentry *stream_dir, *dev_dir;
+ struct arm_smmu_master *master;
+ struct ste_context *ctx;
+ char name[64];
+ u32 sid;
+ int i;
+
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ if (!smmu->debugfs->stream_dir) {
+ stream_dir = debugfs_create_dir("stream_table",
+ smmu->debugfs->smmu_dir);
+ if (!stream_dir)
+ return -ENOMEM;
+
+ smmu->debugfs->stream_dir = stream_dir;
+ } else {
+ stream_dir = smmu->debugfs->stream_dir;
+ }
+ }
+
+ master = dev_iommu_priv_get(dev);
+ if (!master || !master->num_streams)
+ return -ENODEV;
+
+ for (i = 0; i < master->num_streams; i++) {
+ sid = master->streams[i].id;
+ snprintf(name, sizeof(name), "%u", sid);
+ dev_dir = debugfs_create_dir(name, stream_dir);
+ if (!dev_dir)
+ continue;
+
+ /* Create STE file */
+ ctx = kzalloc_obj(*ctx);
+ ctx->master = master;
+ ctx->sid = sid;
+ spin_lock(&smmu->debugfs->stream_lock);
+ list_add_tail(&ctx->node, &smmu->debugfs->stream_list);
+ spin_unlock(&smmu->debugfs->stream_lock);
+ debugfs_create_file("ste", 0444, dev_dir, ctx,
+ &smmu_debugfs_ste_fops);
+
+ }
+
+ return 0;
+}
+
+/**
+ * arm_smmu_debugfs_remove_stream_table() - Remove debugfs entries for stream table
+ * @dev: device to remove entries for
+ * @smmu: SMMU device
+ *
+ * This function removes the debugfs directories created by
+ * arm_smmu_debugfs_create_stream_table().
+ */
+void arm_smmu_debugfs_remove_stream_table(struct device *dev,
+ struct arm_smmu_device *smmu)
+{
+ struct dentry *stream_dir, *dev_dir;
+ struct arm_smmu_master *master;
+ struct ste_context *ctx, *tmp;
+ char name[64];
+ int i;
+
+ /* Check if stream_table directory exists */
+ if (!smmu->debugfs || !smmu->debugfs->stream_dir)
+ return;
+
+ stream_dir = smmu->debugfs->stream_dir;
+ master = dev_iommu_priv_get(dev);
+ if (!master)
+ return;
+
+ /* Remove directories for each stream ID */
+ for (i = 0; i < master->num_streams; i++) {
+ snprintf(name, sizeof(name), "%u", master->streams[i].id);
+ dev_dir = debugfs_lookup(name, stream_dir);
+ debugfs_remove_recursive(dev_dir);
+ }
+
+ /* Free stream context */
+ spin_lock(&smmu->debugfs->stream_lock);
+ list_for_each_entry_safe(ctx, tmp, &smmu->debugfs->stream_list, node) {
+ if (ctx->master->dev == dev) {
+ list_del(&ctx->node);
+ kfree(ctx);
+ }
+ }
+ spin_unlock(&smmu->debugfs->stream_lock);
+}
+
/**
* arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
* @smmu: SMMU device to setup debugfs for
@@ -201,6 +421,8 @@ int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
}
debugfs->smmu_dir = smmu_dir;
+ INIT_LIST_HEAD(&debugfs->stream_list);
+ spin_lock_init(&debugfs->stream_lock);
smmu->debugfs = debugfs;
/* Create capabilities file */
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index cbb3fccc501b..6be132b2f3f0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2654,7 +2654,10 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
return 0;
}
-static struct arm_smmu_ste *
+#ifndef CONFIG_ARM_SMMU_V3_DEBUGFS
+static
+#endif
+struct arm_smmu_ste *
arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
{
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
@@ -3489,7 +3492,10 @@ struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode)
return dev ? dev_get_drvdata(dev) : NULL;
}
-static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
+#ifndef CONFIG_ARM_SMMU_V3_DEBUGFS
+static
+#endif
+bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
{
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
return arm_smmu_strtab_l1_idx(sid) < smmu->strtab_cfg.l2.num_l1_ents;
@@ -3635,6 +3641,10 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
pci_prepare_ats(to_pci_dev(dev), stu);
}
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_create_stream_table(dev, smmu);
+#endif
+
return &smmu->iommu;
err_free_master:
@@ -3648,10 +3658,14 @@ static void arm_smmu_release_device(struct device *dev)
WARN_ON(master->iopf_refcount);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove_stream_table(dev, master->smmu);
+#endif
arm_smmu_disable_pasid(master);
arm_smmu_remove_master(master);
if (arm_smmu_cdtab_allocated(&master->cd_table))
arm_smmu_free_cd_tables(master);
+
kfree(master);
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index a54d72fb9e07..d9ea803a2e4e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -734,13 +734,30 @@ struct arm_smmu_impl_ops {
};
#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+struct ste_context {
+ struct arm_smmu_master *master;
+ u32 sid;
+ struct list_head node;
+};
+
struct arm_smmu_debugfs {
+ struct list_head stream_list;
+ spinlock_t stream_lock;
struct dentry *smmu_dir;
+ struct dentry *stream_dir;
/* Reserved for future extensions */
};
int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name);
void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu);
+int arm_smmu_debugfs_create_stream_table(struct device *dev,
+ struct arm_smmu_device *smmu);
+void arm_smmu_debugfs_remove_stream_table(struct device *dev,
+ struct arm_smmu_device *smmu);
+
+struct arm_smmu_ste *
+arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid);
+bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid);
#endif
/* An SMMUv3 instance */
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [RFC PATCH v2 3/5] iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
2026-03-28 10:17 ` [RFC PATCH v2 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
@ 2026-04-04 5:43 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-04-04 5:43 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm
On Sat, Mar 28, 2026 at 06:17:04PM +0800, Qinxin Xia wrote:
> +static int smmu_debugfs_ste_show(struct seq_file *seq, void *unused)
> +{
> + struct ste_context *ctx = seq->private;
> + struct arm_smmu_master *master = ctx->master;
> + struct arm_smmu_device *smmu;
> + struct arm_smmu_ste *ste;
> + u32 sid, cfg;
> + int i;
> +
> + if (!master) {
> + seq_puts(seq, "No SMMU master data\n");
> + return 0;
> + }
> +
> + smmu = master->smmu;
> + scoped_guard(mutex, &smmu->streams_mutex) {
Instead:
guard(mutex)(&smmu->streams_mutex);
> + sid = ctx->sid;
> +
> + if (!arm_smmu_sid_in_range(smmu, sid)) {
> + seq_printf(seq, "Invalid Stream ID: %u (max %u)\n",
> + sid, (1 << smmu->sid_bits) - 1);
> + return 0;
> + }
> +
> + ste = arm_smmu_get_step_for_sid(smmu, sid);
> + if (!ste) {
> + seq_printf(seq, "STE not available for SID %u\n", sid);
> + return 0;
> + }
> +
> + seq_printf(seq, "STE for Stream ID %u\n", sid);
> + seq_printf(seq, " Valid: %s\n",
> + le64_to_cpu(ste->data[0]) & STRTAB_STE_0_V ? "Yes" : "No");
> +
> + seq_puts(seq, " Config: ");
> +
> + cfg = FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(ste->data[0]));
> +
> + switch (cfg) {
> + case STRTAB_STE_0_CFG_BYPASS:
> + seq_puts(seq, "BYPASS\n");
> + break;
> + case STRTAB_STE_0_CFG_S1_TRANS:
> + seq_puts(seq, "only S1_TRANS\n");
> + break;
> + case STRTAB_STE_0_CFG_S2_TRANS:
> + seq_puts(seq, "only S2_TRANS\n");
> + break;
> + case STRTAB_STE_0_CFG_NESTED:
> + seq_puts(seq, "S1+S2_TRANS\n");
> + break;
> + case STRTAB_STE_0_CFG_ABORT:
> + seq_puts(seq, "ABORT\n");
> + break;
> + default:
> + seq_puts(seq, "UNKNOWN\n");
> + }
> +
> + if (le64_to_cpu(ste->data[0]) & STRTAB_STE_0_CFG_S1_TRANS) {
> + seq_printf(seq, " S1ContextPtr: 0x%016llx\n",
> + le64_to_cpu(ste->data[1]) & STRTAB_STE_0_S1CTXPTR_MASK);
> + }
> +
> + if (le64_to_cpu(ste->data[0]) & STRTAB_STE_0_CFG_S2_TRANS) {
> + seq_printf(seq, " S2ContextPtr: 0x%016llx\n",
> + le64_to_cpu(ste->data[3]) & STRTAB_STE_3_S2TTB_MASK);
> + }
> +
> + /* Display raw STE data */
> + seq_puts(seq, " Raw Data:\n");
> + for (i = 0; i < STRTAB_STE_DWORDS; i++)
> + seq_printf(seq, " STE[%d]: 0x%016llx\n", i,
> + le64_to_cpu(ste->data[i]));
> + }
> + return 0;
Check the indentation of the return line.
> +/**
> + * arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
> + * @dev: device to create entries for
> + * @smmu: SMMU device
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int arm_smmu_debugfs_create_stream_table(struct device *dev,
> + struct arm_smmu_device *smmu)
Usually @smmu would be the first parameter.
> +{
> + struct dentry *stream_dir, *dev_dir;
> + struct arm_smmu_master *master;
> + struct ste_context *ctx;
> + char name[64];
> + u32 sid;
> + int i;
> +
> + scoped_guard(mutex, &arm_smmu_debugfs_lock) {
> + if (!smmu->debugfs->stream_dir) {
> + stream_dir = debugfs_create_dir("stream_table",
> + smmu->debugfs->smmu_dir);
> + if (!stream_dir)
> + return -ENOMEM;
> +
> + smmu->debugfs->stream_dir = stream_dir;
> + } else {
> + stream_dir = smmu->debugfs->stream_dir;
> + }
> + }
> +
> + master = dev_iommu_priv_get(dev);
> + if (!master || !master->num_streams)
> + return -ENODEV;
> +
> + for (i = 0; i < master->num_streams; i++) {
> + sid = master->streams[i].id;
> + snprintf(name, sizeof(name), "%u", sid);
> + dev_dir = debugfs_create_dir(name, stream_dir);
> + if (!dev_dir)
> + continue;
> +
> + /* Create STE file */
> + ctx = kzalloc_obj(*ctx);
> + ctx->master = master;
> + ctx->sid = sid;
> + spin_lock(&smmu->debugfs->stream_lock);
> + list_add_tail(&ctx->node, &smmu->debugfs->stream_list);
> + spin_unlock(&smmu->debugfs->stream_lock);
May consider an RCU list instead of locking.
> +/**
> + * arm_smmu_debugfs_remove_stream_table() - Remove debugfs entries for stream table
> + * @dev: device to remove entries for
> + * @smmu: SMMU device
Again, @smmu could be the first parameter.
> + * This function removes the debugfs directories created by
> + * arm_smmu_debugfs_create_stream_table().
> + */
> +void arm_smmu_debugfs_remove_stream_table(struct device *dev,
> + struct arm_smmu_device *smmu)
Please double check the indentation. It looks odd on my side.
> -static struct arm_smmu_ste *
> +#ifndef CONFIG_ARM_SMMU_V3_DEBUGFS
> +static
> +#endif
> +struct arm_smmu_ste *
> arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
Could probably move this to the header.
> -static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
> +#ifndef CONFIG_ARM_SMMU_V3_DEBUGFS
> +static
> +#endif
> +bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
Ditto
> @@ -3648,10 +3658,14 @@ static void arm_smmu_release_device(struct device *dev)
>
> WARN_ON(master->iopf_refcount);
>
> +#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
> + arm_smmu_debugfs_remove_stream_table(dev, master->smmu);
> +#endif
> arm_smmu_disable_pasid(master);
> arm_smmu_remove_master(master);
> if (arm_smmu_cdtab_allocated(&master->cd_table))
> arm_smmu_free_cd_tables(master);
> +
> kfree(master);
Meaningless line.
> struct arm_smmu_debugfs {
> + struct list_head stream_list;
> + spinlock_t stream_lock;
> struct dentry *smmu_dir;
> + struct dentry *stream_dir;
> /* Reserved for future extensions */
> };
That's the end of the struct. What do you reserve?
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH v2 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (2 preceding siblings ...)
2026-03-28 10:17 ` [RFC PATCH v2 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
@ 2026-03-28 10:17 ` Qinxin Xia
2026-03-28 10:17 ` [RFC PATCH v2 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs Qinxin Xia
` (7 subsequent siblings)
11 siblings, 0 replies; 22+ messages in thread
From: Qinxin Xia @ 2026-03-28 10:17 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm
Add a symlink named under each stream table entry directory pointing to
the sysfs directory of the actual device. This aids debugging
by providing direct access to device attributes.
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
├─── ste
└─── <dev_name>
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index 70623b480d64..dbcc8fce6d8e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -8,8 +8,9 @@
* ├── capabilities # SMMU feature capabilities and configuration
* ├── registers # SMMU Key registers
* └── stream_table
- * └─── <sid>/ # Stream ID 0
- * └── ste # Stream Table Entry
+ * └─── <sid>/ # Stream ID
+ * ├─── ste # Stream Table Entry
+ * └── <dev_name> # Symlink to device sysfs directory
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
@@ -31,6 +32,7 @@
#include <linux/cleanup.h>
#include <linux/debugfs.h>
+#include <linux/kobject.h>
#include <linux/slab.h>
#include "arm-smmu-v3.h"
@@ -295,6 +297,7 @@ int arm_smmu_debugfs_create_stream_table(struct device *dev,
struct dentry *stream_dir, *dev_dir;
struct arm_smmu_master *master;
struct ste_context *ctx;
+ char *path, *full_path;
char name[64];
u32 sid;
int i;
@@ -333,6 +336,18 @@ int arm_smmu_debugfs_create_stream_table(struct device *dev,
debugfs_create_file("ste", 0444, dev_dir, ctx,
&smmu_debugfs_ste_fops);
+ /* Create a symlink to the device's sysfs directory */
+ path = kobject_get_path(&dev->kobj, GFP_KERNEL);
+ if (!path)
+ continue;
+
+ full_path = kasprintf(GFP_KERNEL, "/sys%s", path);
+ if (full_path) {
+ debugfs_create_symlink(dev_name(dev), dev_dir, full_path);
+ kfree(full_path);
+ }
+
+ kfree(path);
}
return 0;
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [RFC PATCH v2 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (3 preceding siblings ...)
2026-03-28 10:17 ` [RFC PATCH v2 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs Qinxin Xia
@ 2026-03-28 10:17 ` Qinxin Xia
2026-05-20 6:37 ` [PATCH 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (6 subsequent siblings)
11 siblings, 0 replies; 22+ messages in thread
From: Qinxin Xia @ 2026-03-28 10:17 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm
Add Context Descriptor (CD) display functionality to debugfs.
This allow inspecting CD contents for all Substream IDs including:
- CD validity and translation parameters
- TTBR0 and TCR configurations
- Raw CD data
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
├─── ste
├─── cd
└─── <dev_name>
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index dbcc8fce6d8e..501437432809 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -10,6 +10,7 @@
* └── stream_table
* └─── <sid>/ # Stream ID
* ├─── ste # Stream Table Entry
+ * ├── cd # Context Descriptor
* └── <dev_name> # Symlink to device sysfs directory
*
* The capabilities file provides detailed information about:
@@ -26,6 +27,12 @@
* - Stage 1 and Stage 2 context pointers
* - Raw STE data
*
+ * CD Information Displayed:
+ * - T0SZ: Input address space size configuration
+ * - EPD0/EPD1: Stage 1 translation enable flags
+ * - TTBR0: Stage 1 translation table base address
+ * - Raw Data: Complete CD structure in hexadecimal format
+
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -284,6 +291,91 @@ static const struct file_operations smmu_debugfs_ste_fops = {
.release = smmu_debugfs_ste_release,
};
+/**
+ * smmu_debug_dump_cd() - Dump a single Context Descriptor
+ * @seq: seq_file to write to
+ * @cd: pointer to the Context Descriptor to dump
+ */
+static void smmu_debug_dump_cd(struct seq_file *seq, struct arm_smmu_cd *cd)
+{
+ u64 data;
+ int i;
+
+ /* CD 0 */
+ data = le64_to_cpu(cd->data[0]);
+ seq_printf(seq, " T0SZ: 0x%llx\n", data & CTXDESC_CD_0_TCR_T0SZ);
+ seq_printf(seq, " EPD0: %s\n", data & CTXDESC_CD_0_TCR_EPD0 ? "Yes" : "No");
+ seq_printf(seq, " EPD1: %s\n", data & CTXDESC_CD_0_TCR_EPD1 ? "Yes" : "No");
+
+ /* CD 1 */
+ data = le64_to_cpu(cd->data[1]);
+ seq_printf(seq, " TTBR0: 0x%016llx\n", data & CTXDESC_CD_1_TTB0_MASK);
+
+ /* Display raw CD data */
+ seq_puts(seq, " Raw Data:\n");
+ for (i = 0; i < CTXDESC_CD_DWORDS; i++)
+ seq_printf(seq, " CD[%d]: 0x%016llx\n", i,
+ le64_to_cpu(cd->data[i]));
+}
+
+static int smmu_debugfs_cd_show(struct seq_file *seq, void *unused)
+{
+ struct device *dev = seq->private;
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ u32 max_ssids, ssid;
+
+ if (!master) {
+ seq_puts(seq, "No master data\n");
+ return 0;
+ }
+
+ mutex_lock(&arm_smmu_asid_lock);
+ max_ssids = 1 << master->ssid_bits;
+ seq_printf(seq, "Context Descriptors for device (max SSIDs: %u):\n",
+ max_ssids);
+
+ for (ssid = 0; ssid < max_ssids; ssid++) {
+ struct arm_smmu_cd *cd = arm_smmu_get_cd_ptr(master, ssid);
+
+ if (cd && (le64_to_cpu(cd->data[0]) & CTXDESC_CD_0_V)) {
+ seq_printf(seq, "\n--- SSID %u ---\n", ssid);
+ smmu_debug_dump_cd(seq, cd);
+ }
+ }
+
+ mutex_unlock(&arm_smmu_asid_lock);
+ return 0;
+}
+
+static int smmu_debugfs_cd_open(struct inode *inode, struct file *file)
+{
+ struct device *dev = inode->i_private;
+
+ if (!dev || !get_device(dev))
+ return -ENODEV;
+
+ return single_open(file, smmu_debugfs_cd_show, dev);
+}
+
+static int smmu_debugfs_cd_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct device *dev = seq->private;
+
+ single_release(inode, file);
+ if (dev)
+ put_device(dev);
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_cd_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_cd_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_cd_release,
+};
+
/**
* arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
* @dev: device to create entries for
@@ -348,6 +440,8 @@ int arm_smmu_debugfs_create_stream_table(struct device *dev,
}
kfree(path);
+ /* Create CD file to dump all valid Context Descriptors */
+ debugfs_create_file("cd", 0444, dev_dir, dev, &smmu_debugfs_cd_fops);
}
return 0;
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH 0/5] Add debugfs support for ARM SMMUv3
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (4 preceding siblings ...)
2026-03-28 10:17 ` [RFC PATCH v2 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-20 6:37 ` [PATCH 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
` (5 subsequent siblings)
11 siblings, 0 replies; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add a comprehensive debugfs framework to the ARM SMMUv3 driver,
providing visibility into internal hardware state for debugging
and performance analysis. The debugfs entries are organized under
/sys/kernel/debug/iommu/arm_smmu_v3/, with per-SMMU instance directories
and per-device stream table entries.
Each SMMU instance provides:
- capabilities – static SMMU features and queue sizes.
- registers – SMMU key registers.
- stream_table/ – a directory per device with subdirectories per Stream ID.
Each Stream ID subdirectory contains:
- ste – the Stream Table Entry in decoded and raw format.
- cd – all valid Context Descriptors (Stage 1 translation tables) associated with the device.
- a symlink named with the device's BDF/name pointing to its sysfs directory for easy navigation.
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
├── <sid>/
│ ├── ste
│ ├── cd
│ └── <dev_name>
Changes since RFCv2:
Address review comments from Nicolin Chen, kernel test robot, and Sashiko
automated review:
Build & kerneldoc fixes (patches 1-3):
- Add missing @unused parameter description to kerneldoc comments to fix
clang W=1 warnings reported by kernel test robot.
- Fix commit message: "driver.This" -> "driver. This" (patch 1).
Correctness fixes:
- Fix integer overflow: 1 << sid_bits -> 1ULL << sid_bits with %llu format,
preventing undefined behavior when SIDSIZE = 32 (patch 1).
- Fix EVTQ register access: use smmu->page1 instead of smmu->base for
EVTQ_PROD/EVTQ_CONS, which reside in register page 1 (patch 2).
- Fix STE config bit test: use proper cfg comparison instead of bitwise AND
on non-mask enumeration values, and extract S1ContextPtr from data[0]
instead of data[1] (patch 3).
Resource leak fixes (patches 1-5):
- Fix device reference leak: call put_device() when single_open() fails in
all open() callbacks (capabilities, registers, ste, cd).
- Fix dentry reference leak: add missing dput() after debugfs_lookup() in
stream table remove path (patch 3).
- Move kobject_get_path() outside the per-stream loop to avoid repeated
allocation (patch 4).
Locking & concurrency fixes:
- arm_smmu_debugfs_remove(): use scoped_guard() and move
debugfs_remove_recursive() outside the lock to avoid blocking other SMMU
instances on active VFS operations (patch 1).
- smmu_debugfs_ste_show(): change scoped_guard to guard(mutex) since the
entire function body is within the lock scope (patch 3).
- smmu_debugfs_cd_show(): change manual mutex_lock/unlock to
guard(mutex)(&arm_smmu_asid_lock) (patch 5).
Error handling fixes:
- Replace if (!debugfs_create_dir(...)) with IS_ERR() checks throughout,
since debugfs_create_dir() returns ERR_PTR on failure, not NULL
(patches 1, 3).
- Add NULL check for smmu->debugfs before dereference in stream table
create path (patch 3).
- Add NULL check for kzalloc_obj() result (patch 3).
Code style & cleanup:
- Directory tree comment: use ├── for last entry to avoid churn on
subsequent patches (patches 1-5).
- Fix tab/space alignment in directory tree comments (patches 1, 2).
- Reorder function parameters: @smmu first, @dev second (patch 3).
- Fix return statement indentation (patch 3).
- Move function declarations from #ifndef-static pattern to header file
(patch 3).
- Move char name[32] and snprintf() to function top for reuse by
iommu_device_sysfs_add() regardless of CONFIG_ARM_SMMU_V3_DEBUGFS
(patch 1).
- Adopt non-tab-aligned style for new struct members (patches 1, 3).
- Remove useless comments: "DebugFS Info" and "Reserved for future
extensions" (patches 1, 3).
- Remove spurious blank line (patch 3).
- Link: https://lore.kernel.org/all/20260328101706.3448655-1-xiaqinxin@huawei.com/
Qinxin Xia (5):
iommu/arm-smmu-v3: Add basic debugfs framework
iommu/arm-smmu-v3: Add register display to debugfs
iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
iommu/arm-smmu-v3: Add device symlink in stream table debugfs
iommu/arm-smmu-v3: Add Context Descriptor display to debugfs
drivers/iommu/Kconfig | 11 +
drivers/iommu/arm/arm-smmu-v3/Makefile | 1 +
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 609 +++++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 30 +
5 files changed, 675 insertions(+), 3 deletions(-)
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
--
2.51.0
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH 1/5] iommu/arm-smmu-v3: Add basic debugfs framework
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (5 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-27 0:41 ` Nicolin Chen
2026-05-20 6:37 ` [PATCH 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
` (4 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add basic debugfs framework for ARM SMMUv3 driver. This creates the
root directory structure and provides capability display functionality.
The debugfs hierarchy is organized as:
/sys/kernel/debug/iommu/arm_smmu_v3/
└── smmu<ioaddr>/
└── capabilities
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
drivers/iommu/Kconfig | 11 ++
drivers/iommu/arm/arm-smmu-v3/Makefile | 1 +
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 172 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 16 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 13 ++
5 files changed, 212 insertions(+), 1 deletion(-)
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index f86262b11416..f28f09adba03 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -93,6 +93,17 @@ config IOMMU_DEBUGFS
debug/iommu directory, and then populate a subdirectory with
entries as required.
+config ARM_SMMU_V3_DEBUGFS
+ bool "ARM SMMUv3 DebugFS support"
+ depends on ARM_SMMU_V3 && IOMMU_DEBUGFS
+ help
+ Expose ARM SMMUv3 internal state via debugfs for debugging and
+ diagnostics. This creates /sys/kernel/debug/iommu/arm_smmu_v3/
+ with detailed information about SMMU configuration, stream tables,
+ and context descriptors.
+
+ Say N unless you are debugging SMMU issues.
+
choice
prompt "IOMMU default domain type"
depends on IOMMU_API
diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile
index 493a659cc66b..787538fb7054 100644
--- a/drivers/iommu/arm/arm-smmu-v3/Makefile
+++ b/drivers/iommu/arm/arm-smmu-v3/Makefile
@@ -4,5 +4,6 @@ arm_smmu_v3-y := arm-smmu-v3.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o
arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o
+arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_DEBUGFS) += arm-smmu-v3-debugfs.o
obj-$(CONFIG_ARM_SMMU_V3_KUNIT_TEST) += arm-smmu-v3-test.o
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
new file mode 100644
index 000000000000..1fc2cd1651b4
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM SMMUv3 DebugFS Support
+ *
+ * Directory Structure:
+ * /sys/kernel/debug/iommu/arm_smmu_v3/
+ * └── smmu<ioaddr>/
+ * ├── capabilities # SMMU feature capabilities and configuration
+ *
+ * The capabilities file provides detailed information about:
+ * - translation stage support (Stage1/Stage2)
+ * - System coherency, ATS, and PRI feature availability
+ * - Stream table size and command/event queue depths
+ *
+ * Copyright (C) 2026 HiSilicon Limited.
+ * Author: Qinxin Xia <xiaqinxin@huawei.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include "arm-smmu-v3.h"
+
+static struct dentry *smmu_debugfs_root;
+static DEFINE_MUTEX(arm_smmu_debugfs_lock);
+
+/**
+ * smmu_debugfs_capabilities_show() - Display SMMU capabilities
+ * @seq: seq_file to write to
+ * @unused: unused parameter
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_capabilities_show(struct seq_file *seq, void *unused)
+{
+ struct arm_smmu_device *smmu = seq->private;
+
+ if (!smmu) {
+ seq_puts(seq, "SMMU not available\n");
+ return 0;
+ }
+
+ seq_puts(seq, "SMMUv3 Capabilities:\n");
+ seq_printf(seq, " Stage1 Translation: %s\n",
+ smmu->features & ARM_SMMU_FEAT_TRANS_S1 ? "Yes" : "No");
+ seq_printf(seq, " Stage2 Translation: %s\n",
+ smmu->features & ARM_SMMU_FEAT_TRANS_S2 ? "Yes" : "No");
+ seq_printf(seq, " Coherent Walk: %s\n",
+ smmu->features & ARM_SMMU_FEAT_COHERENCY ? "Yes" : "No");
+ seq_printf(seq, " ATS Support: %s\n",
+ smmu->features & ARM_SMMU_FEAT_ATS ? "Yes" : "No");
+ seq_printf(seq, " PRI Support: %s\n",
+ smmu->features & ARM_SMMU_FEAT_PRI ? "Yes" : "No");
+ seq_printf(seq, " Stream Table Size: %llu\n", 1ULL << smmu->sid_bits);
+ seq_printf(seq, " Command Queue Depth: %d\n",
+ 1 << smmu->cmdq.q.llq.max_n_shift);
+ seq_printf(seq, " Event Queue Depth: %d\n",
+ 1 << smmu->evtq.q.llq.max_n_shift);
+
+ return 0;
+}
+
+static int smmu_debugfs_capabilities_open(struct inode *inode, struct file *file)
+{
+ struct arm_smmu_device *smmu = inode->i_private;
+ int ret;
+
+ if (!smmu || !get_device(smmu->dev))
+ return -ENODEV;
+
+ ret = single_open(file, smmu_debugfs_capabilities_show, smmu);
+ if (ret)
+ put_device(smmu->dev);
+
+ return ret;
+}
+
+static int smmu_debugfs_capabilities_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct arm_smmu_device *smmu = seq->private;
+
+ single_release(inode, file);
+ if (smmu)
+ put_device(smmu->dev);
+
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_capabilities_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_capabilities_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_capabilities_release,
+};
+
+/**
+ * arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
+ * @smmu: SMMU device to setup debugfs for
+ * @name: SMMU device name
+ *
+ * This function creates the basic debugfs directory structure for an SMMU device.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
+{
+ struct arm_smmu_debugfs *debugfs;
+ struct dentry *smmu_dir;
+
+ /* Create root directory if it doesn't exist */
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ if (!smmu_debugfs_root) {
+ /* Once created, it will not be removed */
+ smmu_debugfs_root = debugfs_create_dir("arm_smmu_v3",
+ iommu_debugfs_dir);
+ if (IS_ERR(smmu_debugfs_root)) {
+ smmu_debugfs_root = NULL;
+ return -ENOMEM;
+ }
+ }
+ }
+
+ /* Allocate debugfs structure */
+ debugfs = kzalloc_obj(*debugfs);
+ if (!debugfs)
+ return -ENOMEM;
+
+ /* Create SMMU instance directory */
+ smmu_dir = debugfs_create_dir(name, smmu_debugfs_root);
+ if (IS_ERR(smmu_dir)) {
+ kfree(debugfs);
+ smmu->debugfs = NULL;
+ return PTR_ERR(smmu_dir);
+ }
+
+ debugfs->smmu_dir = smmu_dir;
+ smmu->debugfs = debugfs;
+
+ /* Create capabilities file */
+ debugfs_create_file("capabilities", 0444, smmu_dir, smmu,
+ &smmu_debugfs_capabilities_fops);
+
+ dev_dbg(smmu->dev, "debugfs initialized for %s\n", name);
+ return 0;
+}
+
+/**
+ * arm_smmu_debugfs_remove() - Clean up debugfs entries for an SMMU device
+ * @smmu: SMMU device
+ *
+ * This function removes the debugfs directories created by setup.
+ */
+void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu)
+{
+ struct arm_smmu_debugfs *debugfs;
+ struct dentry *smmu_dir;
+
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ debugfs = smmu->debugfs;
+ if (!debugfs)
+ return;
+
+ smmu_dir = debugfs->smmu_dir;
+ kfree(debugfs);
+ smmu->debugfs = NULL;
+ }
+
+ /* Remove outside lock to avoid blocking on active VFS operations */
+ debugfs_remove_recursive(smmu_dir);
+}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index e8d7dbe495f0..929b8ead95b0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -5469,6 +5469,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
resource_size_t ioaddr;
struct arm_smmu_device *smmu;
struct device *dev = &pdev->dev;
+ char name[32];
smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL);
if (!smmu)
@@ -5496,6 +5497,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
return -EINVAL;
}
ioaddr = res->start;
+ snprintf(name, sizeof(name), "smmu3.%pa", &ioaddr);
/*
* Don't map the IMPLEMENTATION DEFINED regions, since they may contain
@@ -5548,6 +5550,12 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
/* Check for RMRs and install bypass STEs if any */
arm_smmu_rmr_install_bypass_ste(smmu);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ ret = arm_smmu_debugfs_setup(smmu, name);
+ if (ret)
+ dev_warn(dev, "Failed to create debugfs!\n");
+#endif
+
/* Reset the device */
ret = arm_smmu_device_reset(smmu);
if (ret)
@@ -5555,7 +5563,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
/* And we're up. Go go go! */
ret = iommu_device_sysfs_add(&smmu->iommu, dev, NULL,
- "smmu3.%pa", &ioaddr);
+ "%s", name);
if (ret)
goto err_disable;
@@ -5570,6 +5578,9 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
err_free_sysfs:
iommu_device_sysfs_remove(&smmu->iommu);
err_disable:
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove(smmu);
+#endif
arm_smmu_device_disable(smmu);
err_free_iopf:
iopf_queue_free(smmu->evtq.iopf);
@@ -5582,6 +5593,9 @@ static void arm_smmu_device_remove(struct platform_device *pdev)
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove(smmu);
+#endif
arm_smmu_device_disable(smmu);
iopf_queue_free(smmu->evtq.iopf);
ida_destroy(&smmu->vmid_map);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index ef42df4753ec..8e1c19b6831c 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -820,6 +820,15 @@ struct arm_smmu_impl_ops {
const struct iommu_user_data *user_data);
};
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+struct arm_smmu_debugfs {
+ struct dentry *smmu_dir;
+};
+
+int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name);
+void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu);
+#endif
+
/* An SMMUv3 instance */
struct arm_smmu_device {
struct device *dev;
@@ -890,6 +899,10 @@ struct arm_smmu_device {
struct rb_root streams;
struct mutex streams_mutex;
+
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ struct arm_smmu_debugfs *debugfs;
+#endif
};
struct arm_smmu_stream {
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 1/5] iommu/arm-smmu-v3: Add basic debugfs framework
2026-05-20 6:37 ` [PATCH 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
@ 2026-05-27 0:41 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 0:41 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Wed, May 20, 2026 at 02:37:07PM +0800, Qinxin Xia wrote:
> +/**
> + * arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
> + * @smmu: SMMU device to setup debugfs for
> + * @name: SMMU device name
Nit: "device name" sounds like dev_name(); maybe sysfs node name?
> + * This function creates the basic debugfs directory structure for an SMMU device.
> + *
> + * Return: 0 on success, negative error code on failure
Usually, a debugfs_setup function uses void as a no-fail function.
So, perhaps it should do the same? The caller only spits a warning
print, which could be a part of revert path of this function if it
is really necessary.
> + */
> +int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
> +{
> + struct arm_smmu_debugfs *debugfs;
> + struct dentry *smmu_dir;
> +
> + /* Create root directory if it doesn't exist */
> + scoped_guard(mutex, &arm_smmu_debugfs_lock) {
> + if (!smmu_debugfs_root) {
> + /* Once created, it will not be removed */
> + smmu_debugfs_root = debugfs_create_dir("arm_smmu_v3",
> + iommu_debugfs_dir);
> + if (IS_ERR(smmu_debugfs_root)) {
> + smmu_debugfs_root = NULL;
> + return -ENOMEM;
> + }
> + }
> + }
> +
> + /* Allocate debugfs structure */
> + debugfs = kzalloc_obj(*debugfs);
> + if (!debugfs)
> + return -ENOMEM;
> +
> + /* Create SMMU instance directory */
> + smmu_dir = debugfs_create_dir(name, smmu_debugfs_root);
> + if (IS_ERR(smmu_dir)) {
> + kfree(debugfs);
> + smmu->debugfs = NULL;
smmu->debugfs is NULL already since it's unset yet.
> +void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu)
> +{
> + struct arm_smmu_debugfs *debugfs;
> + struct dentry *smmu_dir;
> +
> + scoped_guard(mutex, &arm_smmu_debugfs_lock) {
> + debugfs = smmu->debugfs;
Isn't the lock used to fence smmu_debugfs_root?
smmu->debugfs is per-smmu and freed per-smmu. Where is the race?
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 2/5] iommu/arm-smmu-v3: Add register display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (6 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 1/5] iommu/arm-smmu-v3: Add basic debugfs framework Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-27 1:28 ` Nicolin Chen
2026-05-20 6:37 ` [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
` (3 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add register display functionality to debugfs.This allows reading
and displaying key SMMU register values including control registers
and queue pointers.
The registers file shows:
- CR0, CR1, CR2 control registers
- Command and Event queue pointers
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index 1fc2cd1651b4..a541476b7427 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -6,12 +6,17 @@
* /sys/kernel/debug/iommu/arm_smmu_v3/
* └── smmu<ioaddr>/
* ├── capabilities # SMMU feature capabilities and configuration
+ * ├── registers # SMMU Key registers
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
* - System coherency, ATS, and PRI feature availability
* - Stream table size and command/event queue depths
*
+ * The registers display provides crucial visibility into:
+ * - CR0, CR1, CR2 control registers
+ * - Command and Event queue pointers
+ *
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -95,6 +100,80 @@ static const struct file_operations smmu_debugfs_capabilities_fops = {
.release = smmu_debugfs_capabilities_release,
};
+/**
+ * smmu_debugfs_registers_show() - Display SMMU register values
+ * @seq: seq_file to write to
+ * @unused: unused parameter
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_registers_show(struct seq_file *seq, void *unused)
+{
+ struct arm_smmu_device *smmu = seq->private;
+ void __iomem *base;
+
+ if (!smmu || !smmu->base) {
+ seq_puts(seq, "SMMU not available\n");
+ return 0;
+ }
+
+ base = smmu->base;
+
+ seq_puts(seq, "SMMUv3 Key Registers:\n");
+
+ /* 32-bit control registers */
+ seq_printf(seq, "CR0: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR0));
+ seq_printf(seq, "CR1: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR1));
+ seq_printf(seq, "CR2: 0x%08x\n", readl_relaxed(base + ARM_SMMU_CR2));
+
+ /* 32-bit queue pointer registers */
+ seq_printf(seq, "CMDQ_PROD: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_CMDQ_PROD));
+ seq_printf(seq, "CMDQ_CONS: 0x%08x\n",
+ readl_relaxed(base + ARM_SMMU_CMDQ_CONS));
+ seq_printf(seq, "EVTQ_PROD: 0x%08x\n",
+ smmu->page1 ? readl_relaxed(smmu->page1 + ARM_SMMU_EVTQ_PROD) : 0);
+ seq_printf(seq, "EVTQ_CONS: 0x%08x\n",
+ smmu->page1 ? readl_relaxed(smmu->page1 + ARM_SMMU_EVTQ_CONS) : 0);
+
+ return 0;
+}
+
+static int smmu_debugfs_registers_open(struct inode *inode, struct file *file)
+{
+ struct arm_smmu_device *smmu = inode->i_private;
+ int ret;
+
+ if (!smmu || !get_device(smmu->dev))
+ return -ENODEV;
+
+ ret = single_open(file, smmu_debugfs_registers_show, smmu);
+ if (ret)
+ put_device(smmu->dev);
+
+ return ret;
+}
+
+static int smmu_debugfs_registers_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct arm_smmu_device *smmu = seq->private;
+
+ single_release(inode, file);
+ if (smmu)
+ put_device(smmu->dev);
+
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_registers_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_registers_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_registers_release,
+};
+
/**
* arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
* @smmu: SMMU device to setup debugfs for
@@ -142,6 +221,9 @@ int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
debugfs_create_file("capabilities", 0444, smmu_dir, smmu,
&smmu_debugfs_capabilities_fops);
+ debugfs_create_file("registers", 0444, smmu_dir, smmu,
+ &smmu_debugfs_registers_fops);
+
dev_dbg(smmu->dev, "debugfs initialized for %s\n", name);
return 0;
}
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 2/5] iommu/arm-smmu-v3: Add register display to debugfs
2026-05-20 6:37 ` [PATCH 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
@ 2026-05-27 1:28 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 1:28 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Wed, May 20, 2026 at 02:37:08PM +0800, Qinxin Xia wrote:
> +static int smmu_debugfs_registers_open(struct inode *inode, struct file *file)
> +{
> + struct arm_smmu_device *smmu = inode->i_private;
> + int ret;
> +
> + if (!smmu || !get_device(smmu->dev))
> + return -ENODEV;
> +
> + ret = single_open(file, smmu_debugfs_registers_show, smmu);
> + if (ret)
> + put_device(smmu->dev);
> +
> + return ret;
> +}
> +
> +static int smmu_debugfs_registers_release(struct inode *inode, struct file *file)
> +{
> + struct seq_file *seq = file->private_data;
> + struct arm_smmu_device *smmu = seq->private;
> +
> + single_release(inode, file);
> + if (smmu)
> + put_device(smmu->dev);
> +
> + return 0;
> +}
> +
> +static const struct file_operations smmu_debugfs_registers_fops = {
> + .owner = THIS_MODULE,
> + .open = smmu_debugfs_registers_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .release = smmu_debugfs_registers_release,
> +};
These seem a bit redundant; only names and show functions are
different from the cap's fops/open/release?
If so, maybe we can mimic how drivers/usb/host/xhci-debugfs.c
unify things (refer to w_context_files).
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (7 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 2/5] iommu/arm-smmu-v3: Add register display to debugfs Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-27 2:18 ` Nicolin Chen
2026-05-20 6:37 ` [PATCH 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs Qinxin Xia
` (2 subsequent siblings)
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add Stream Table Entry (STE) display functionality to debugfs.
This allow inspecting STE contents for each device stream including:
- STE validity and configuration
- Stage 1 and Stage 2 context pointers
- Raw STE data
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
└─── ste
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 235 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 11 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 17 ++
3 files changed, 261 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index a541476b7427..a5866f34264f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -7,6 +7,9 @@
* └── smmu<ioaddr>/
* ├── capabilities # SMMU feature capabilities and configuration
* ├── registers # SMMU Key registers
+ * ├── stream_table
+ * ├── <sid>/ # Stream ID
+ * ├── ste # Stream Table Entry
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
@@ -17,6 +20,11 @@
* - CR0, CR1, CR2 control registers
* - Command and Event queue pointers
*
+ * The STE Information Displayed:
+ * - STE validity and configuration
+ * - Stage 1 and Stage 2 context pointers
+ * - Raw STE data
+ *
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -174,6 +182,231 @@ static const struct file_operations smmu_debugfs_registers_fops = {
.release = smmu_debugfs_registers_release,
};
+/**
+ * smmu_debugfs_ste_show() - Dump STE details to seq_file
+ * @seq: seq_file to write to
+ * @unused: unused parameter
+ *
+ * Errors are reported via seq_puts, the function always returns 0
+ */
+static int smmu_debugfs_ste_show(struct seq_file *seq, void *unused)
+{
+ struct ste_context *ctx = seq->private;
+ struct arm_smmu_master *master = ctx->master;
+ struct arm_smmu_device *smmu;
+ struct arm_smmu_ste *ste;
+ u32 sid, cfg;
+ int i;
+
+ if (!master) {
+ seq_puts(seq, "No SMMU master data\n");
+ return 0;
+ }
+
+ smmu = master->smmu;
+ guard(mutex)(&smmu->streams_mutex);
+
+ sid = ctx->sid;
+
+ if (!arm_smmu_sid_in_range(smmu, sid)) {
+ seq_printf(seq, "Invalid Stream ID: %u (max %u)\n",
+ sid, (1 << smmu->sid_bits) - 1);
+ return 0;
+ }
+
+ ste = arm_smmu_get_step_for_sid(smmu, sid);
+ if (!ste) {
+ seq_printf(seq, "STE not available for SID %u\n", sid);
+ return 0;
+ }
+
+ seq_printf(seq, "STE for Stream ID %u\n", sid);
+ seq_printf(seq, " Valid: %s\n",
+ le64_to_cpu(ste->data[0]) & STRTAB_STE_0_V ? "Yes" : "No");
+
+ seq_puts(seq, " Config: ");
+
+ cfg = FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(ste->data[0]));
+
+ switch (cfg) {
+ case STRTAB_STE_0_CFG_BYPASS:
+ seq_puts(seq, "BYPASS\n");
+ break;
+ case STRTAB_STE_0_CFG_S1_TRANS:
+ seq_puts(seq, "only S1_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_S2_TRANS:
+ seq_puts(seq, "only S2_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_NESTED:
+ seq_puts(seq, "S1+S2_TRANS\n");
+ break;
+ case STRTAB_STE_0_CFG_ABORT:
+ seq_puts(seq, "ABORT\n");
+ break;
+ default:
+ seq_puts(seq, "UNKNOWN\n");
+ }
+
+ if (cfg == STRTAB_STE_0_CFG_S1_TRANS || cfg == STRTAB_STE_0_CFG_NESTED) {
+ seq_printf(seq, " S1ContextPtr: 0x%016llx\n",
+ le64_to_cpu(ste->data[0]) & STRTAB_STE_0_S1CTXPTR_MASK);
+ }
+
+ if (cfg == STRTAB_STE_0_CFG_S2_TRANS || cfg == STRTAB_STE_0_CFG_NESTED) {
+ seq_printf(seq, " S2TTB: 0x%016llx\n",
+ le64_to_cpu(ste->data[3]) & STRTAB_STE_3_S2TTB_MASK);
+ }
+
+ /* Display raw STE data */
+ seq_puts(seq, " Raw Data:\n");
+ for (i = 0; i < STRTAB_STE_DWORDS; i++)
+ seq_printf(seq, " STE[%d]: 0x%016llx\n", i,
+ le64_to_cpu(ste->data[i]));
+
+ return 0;
+}
+
+static int smmu_debugfs_ste_open(struct inode *inode, struct file *file)
+{
+ struct ste_context *ctx = inode->i_private;
+ int ret;
+
+ if (!ctx || !get_device(ctx->master->dev))
+ return -ENODEV;
+
+ ret = single_open(file, smmu_debugfs_ste_show, ctx);
+ if (ret)
+ put_device(ctx->master->dev);
+
+ return ret;
+}
+
+static int smmu_debugfs_ste_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct ste_context *ctx = seq->private;
+
+ single_release(inode, file);
+ if (ctx)
+ put_device(ctx->master->dev);
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_ste_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_ste_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_ste_release,
+};
+
+/**
+ * arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
+ * @smmu: SMMU device
+ * @dev: device to create entries for
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
+ struct device *dev)
+{
+ struct dentry *stream_dir, *dev_dir;
+ struct arm_smmu_master *master;
+ struct ste_context *ctx;
+ char name[64];
+ u32 sid;
+ int i;
+
+ if (!smmu->debugfs)
+ return -ENODEV;
+
+ scoped_guard(mutex, &arm_smmu_debugfs_lock) {
+ if (!smmu->debugfs->stream_dir) {
+ stream_dir = debugfs_create_dir("stream_table",
+ smmu->debugfs->smmu_dir);
+ if (IS_ERR(stream_dir))
+ return PTR_ERR(stream_dir);
+
+ smmu->debugfs->stream_dir = stream_dir;
+ } else {
+ stream_dir = smmu->debugfs->stream_dir;
+ }
+ }
+
+ master = dev_iommu_priv_get(dev);
+ if (!master || !master->num_streams)
+ return -ENODEV;
+
+ for (i = 0; i < master->num_streams; i++) {
+ sid = master->streams[i].id;
+ snprintf(name, sizeof(name), "%u", sid);
+ dev_dir = debugfs_create_dir(name, stream_dir);
+ if (IS_ERR(dev_dir))
+ continue;
+
+ /* Create STE file */
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ continue;
+
+ ctx->master = master;
+ ctx->sid = sid;
+ spin_lock(&smmu->debugfs->stream_lock);
+ list_add_tail(&ctx->node, &smmu->debugfs->stream_list);
+ spin_unlock(&smmu->debugfs->stream_lock);
+ debugfs_create_file("ste", 0444, dev_dir, ctx,
+ &smmu_debugfs_ste_fops);
+ }
+
+ return 0;
+}
+
+/**
+ * arm_smmu_debugfs_remove_stream_table() - Remove debugfs entries for stream table
+ * @smmu: SMMU device
+ * @dev: device to remove entries for
+ *
+ * This function removes the debugfs directories created by
+ * arm_smmu_debugfs_create_stream_table().
+ */
+void arm_smmu_debugfs_remove_stream_table(struct arm_smmu_device *smmu,
+ struct device *dev)
+{
+ struct dentry *stream_dir, *dev_dir;
+ struct arm_smmu_master *master;
+ struct ste_context *ctx, *tmp;
+ char name[64];
+ int i;
+
+ /* Check if stream_table directory exists */
+ if (!smmu->debugfs || !smmu->debugfs->stream_dir)
+ return;
+
+ stream_dir = smmu->debugfs->stream_dir;
+ master = dev_iommu_priv_get(dev);
+ if (!master)
+ return;
+
+ /* Remove directories for each stream ID */
+ for (i = 0; i < master->num_streams; i++) {
+ snprintf(name, sizeof(name), "%u", master->streams[i].id);
+ dev_dir = debugfs_lookup(name, stream_dir);
+ debugfs_remove_recursive(dev_dir);
+ dput(dev_dir);
+ }
+
+ /* Free stream context */
+ spin_lock(&smmu->debugfs->stream_lock);
+ list_for_each_entry_safe(ctx, tmp, &smmu->debugfs->stream_list, node) {
+ if (ctx->master->dev == dev) {
+ list_del(&ctx->node);
+ kfree(ctx);
+ }
+ }
+ spin_unlock(&smmu->debugfs->stream_lock);
+}
+
/**
* arm_smmu_debugfs_setup() - Initialize debugfs for SMMU device
* @smmu: SMMU device to setup debugfs for
@@ -215,6 +448,8 @@ int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name)
}
debugfs->smmu_dir = smmu_dir;
+ INIT_LIST_HEAD(&debugfs->stream_list);
+ spin_lock_init(&debugfs->stream_lock);
smmu->debugfs = debugfs;
/* Create capabilities file */
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 929b8ead95b0..ca0fa043c2ce 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2992,7 +2992,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
return 0;
}
-static struct arm_smmu_ste *
+struct arm_smmu_ste *
arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
{
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
@@ -4090,7 +4090,7 @@ struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode)
return dev ? dev_get_drvdata(dev) : NULL;
}
-static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
+bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
{
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
return arm_smmu_strtab_l1_idx(sid) < smmu->strtab_cfg.l2.num_l1_ents;
@@ -4269,6 +4269,10 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
pci_prepare_ats(to_pci_dev(dev), stu);
}
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_create_stream_table(smmu, dev);
+#endif
+
return &smmu->iommu;
err_free_master:
@@ -4282,6 +4286,9 @@ static void arm_smmu_release_device(struct device *dev)
WARN_ON(master->iopf_refcount);
+#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+ arm_smmu_debugfs_remove_stream_table(master->smmu, dev);
+#endif
arm_smmu_disable_pasid(master);
arm_smmu_remove_master(master);
if (arm_smmu_cdtab_allocated(&master->cd_table))
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 8e1c19b6831c..9c9be63dc9e2 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -821,12 +821,29 @@ struct arm_smmu_impl_ops {
};
#ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
+struct ste_context {
+ struct arm_smmu_master *master;
+ u32 sid;
+ struct list_head node;
+};
+
struct arm_smmu_debugfs {
+ struct list_head stream_list;
+ spinlock_t stream_lock;
struct dentry *smmu_dir;
+ struct dentry *stream_dir;
};
int arm_smmu_debugfs_setup(struct arm_smmu_device *smmu, const char *name);
void arm_smmu_debugfs_remove(struct arm_smmu_device *smmu);
+int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
+ struct device *dev);
+void arm_smmu_debugfs_remove_stream_table(struct arm_smmu_device *smmu,
+ struct device *dev);
+
+struct arm_smmu_ste *
+arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid);
+bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid);
#endif
/* An SMMUv3 instance */
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
2026-05-20 6:37 ` [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
@ 2026-05-27 2:18 ` Nicolin Chen
2026-05-27 2:43 ` Nicolin Chen
0 siblings, 1 reply; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 2:18 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Wed, May 20, 2026 at 02:37:09PM +0800, Qinxin Xia wrote:
> +static int smmu_debugfs_ste_show(struct seq_file *seq, void *unused)
> +{
> + struct ste_context *ctx = seq->private;
> + struct arm_smmu_master *master = ctx->master;
> + struct arm_smmu_device *smmu;
> + struct arm_smmu_ste *ste;
> + u32 sid, cfg;
> + int i;
> +
> + if (!master) {
> + seq_puts(seq, "No SMMU master data\n");
> + return 0;
> + }
> +
> + smmu = master->smmu;
> + guard(mutex)(&smmu->streams_mutex);
Given the whole thing is fenced with streams_mutex..
> + sid = ctx->sid;
... the point in having the ctx is for the SID?
SID is convertible from the parent folder name. Can you try:
struct file *file = seq->file;
struct dentry *dentry = file->f_path.dentry;
const char *str_sid = dentry->d_parent->d_name.name;
if (kstrtoint(str_sid, 0, &sid)) {
seq_puts(seq, "Error parsing SID string from: %s\n", str_sid);
return 0;
}
?
> +/**
> + * arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
> + * @smmu: SMMU device
> + * @dev: device to create entries for
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
> + struct device *dev)
> +{
> + struct dentry *stream_dir, *dev_dir;
> + struct arm_smmu_master *master;
> + struct ste_context *ctx;
> + char name[64];
> + u32 sid;
> + int i;
[...]
> + master = dev_iommu_priv_get(dev);
> + if (!master || !master->num_streams)
> + return -ENODEV;
The caller has master. Why not just pass it in? Then, you would
have master->smmu and master->dev.
> +/**
> + * arm_smmu_debugfs_remove_stream_table() - Remove debugfs entries for stream table
> + * @smmu: SMMU device
> + * @dev: device to remove entries for
> + *
> + * This function removes the debugfs directories created by
> + * arm_smmu_debugfs_create_stream_table().
> + */
> +void arm_smmu_debugfs_remove_stream_table(struct arm_smmu_device *smmu,
> + struct device *dev)
> +{
> + struct dentry *stream_dir, *dev_dir;
> + struct arm_smmu_master *master;
> + struct ste_context *ctx, *tmp;
> + char name[64];
> + int i;
> +
> + /* Check if stream_table directory exists */
> + if (!smmu->debugfs || !smmu->debugfs->stream_dir)
> + return;
> +
> + stream_dir = smmu->debugfs->stream_dir;
> + master = dev_iommu_priv_get(dev);
> + if (!master)
> + return;
Ditto.
> diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> index 8e1c19b6831c..9c9be63dc9e2 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -821,12 +821,29 @@ struct arm_smmu_impl_ops {
> };
>
> #ifdef CONFIG_ARM_SMMU_V3_DEBUGFS
> +struct ste_context {
> + struct arm_smmu_master *master;
> + u32 sid;
> + struct list_head node;
> +};
Does this need to be in the header?
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry display to debugfs
2026-05-27 2:18 ` Nicolin Chen
@ 2026-05-27 2:43 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 2:43 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Tue, May 26, 2026 at 07:18:54PM -0700, Nicolin Chen wrote:
> On Wed, May 20, 2026 at 02:37:09PM +0800, Qinxin Xia wrote:
> > +int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
> > + struct device *dev)
> > +{
> > + struct dentry *stream_dir, *dev_dir;
> > + struct arm_smmu_master *master;
> > + struct ste_context *ctx;
> > + char name[64];
> > + u32 sid;
> > + int i;
> [...]
> > + master = dev_iommu_priv_get(dev);
> > + if (!master || !master->num_streams)
> > + return -ENODEV;
>
> The caller has master. Why not just pass it in? Then, you would
> have master->smmu and master->dev.
I forgot that this is to fence against concurrent device unplug.
Maybe add a note for the reason of having smmu and dev instead?
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (8 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 3/5] iommu/arm-smmu-v3: Add Stream Table Entry " Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-20 6:37 ` [PATCH 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs Qinxin Xia
2026-05-27 0:07 ` [PATCH 0/5] Add debugfs support for ARM SMMUv3 Nicolin Chen
11 siblings, 0 replies; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add a symlink named under each stream table entry directory pointing to
the sysfs directory of the actual device. This aids debugging
by providing direct access to device attributes.
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
├─── ste
└─── <dev_name>
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index a5866f34264f..9babc7d640fd 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -10,6 +10,7 @@
* ├── stream_table
* ├── <sid>/ # Stream ID
* ├── ste # Stream Table Entry
+ * ├── <dev_name> # Symlink to device sysfs directory
*
* The capabilities file provides detailed information about:
* - translation stage support (Stage1/Stage2)
@@ -31,6 +32,7 @@
#include <linux/cleanup.h>
#include <linux/debugfs.h>
+#include <linux/kobject.h>
#include <linux/slab.h>
#include "arm-smmu-v3.h"
@@ -314,6 +316,7 @@ int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
struct dentry *stream_dir, *dev_dir;
struct arm_smmu_master *master;
struct ste_context *ctx;
+ char *path = NULL, *full_path;
char name[64];
u32 sid;
int i;
@@ -338,6 +341,9 @@ int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
if (!master || !master->num_streams)
return -ENODEV;
+ /* Get device sysfs path once, reuse for all streams */
+ path = kobject_get_path(&dev->kobj, GFP_KERNEL);
+
for (i = 0; i < master->num_streams; i++) {
sid = master->streams[i].id;
snprintf(name, sizeof(name), "%u", sid);
@@ -357,8 +363,19 @@ int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
spin_unlock(&smmu->debugfs->stream_lock);
debugfs_create_file("ste", 0444, dev_dir, ctx,
&smmu_debugfs_ste_fops);
+
+ /* Create a symlink to the device's sysfs directory */
+ if (path) {
+ full_path = kasprintf(GFP_KERNEL, "/sys%s", path);
+ if (full_path) {
+ debugfs_create_symlink(dev_name(dev), dev_dir,
+ full_path);
+ kfree(full_path);
+ }
+ }
}
+ kfree(path);
return 0;
}
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (9 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 4/5] iommu/arm-smmu-v3: Add device symlink in stream table debugfs Qinxin Xia
@ 2026-05-20 6:37 ` Qinxin Xia
2026-05-27 2:40 ` Nicolin Chen
2026-05-27 0:07 ` [PATCH 0/5] Add debugfs support for ARM SMMUv3 Nicolin Chen
11 siblings, 1 reply; 22+ messages in thread
From: Qinxin Xia @ 2026-05-20 6:37 UTC (permalink / raw)
To: robin.murphy, nicolinc, will, jpb
Cc: linux-arm-kernel, iommu, xiaqinxin, wangzhou1, prime.zeng,
fanghao11, jonathan.cameron, wuyifan50, linuxarm, linux-kernel
Add Context Descriptor (CD) display functionality to debugfs.
This allow inspecting CD contents for all Substream IDs including:
- CD validity and translation parameters
- TTBR0 and TCR configurations
- Raw CD data
/sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
└── <sid>/
├─── ste
├─── cd
└─── <dev_name>
Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-debugfs.c | 101 ++++++++++++++++++
1 file changed, 101 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
index 9babc7d640fd..567749de5537 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-debugfs.c
@@ -10,6 +10,7 @@
* ├── stream_table
* ├── <sid>/ # Stream ID
* ├── ste # Stream Table Entry
+ * ├── cd # Context Descriptor
* ├── <dev_name> # Symlink to device sysfs directory
*
* The capabilities file provides detailed information about:
@@ -26,6 +27,12 @@
* - Stage 1 and Stage 2 context pointers
* - Raw STE data
*
+ * CD Information Displayed:
+ * - T0SZ: Input address space size configuration
+ * - EPD0/EPD1: Stage 1 translation enable flags
+ * - TTBR0: Stage 1 translation table base address
+ * - Raw Data: Complete CD structure in hexadecimal format
+ *
* Copyright (C) 2026 HiSilicon Limited.
* Author: Qinxin Xia <xiaqinxin@huawei.com>
*/
@@ -303,6 +310,97 @@ static const struct file_operations smmu_debugfs_ste_fops = {
.release = smmu_debugfs_ste_release,
};
+/**
+ * smmu_debug_dump_cd() - Dump a single Context Descriptor
+ * @seq: seq_file to write to
+ * @cd: pointer to the Context Descriptor to dump
+ */
+static void smmu_debug_dump_cd(struct seq_file *seq, struct arm_smmu_cd *cd)
+{
+ u64 data;
+ int i;
+
+ /* CD 0 */
+ data = le64_to_cpu(cd->data[0]);
+ seq_printf(seq, " T0SZ: 0x%llx\n", data & CTXDESC_CD_0_TCR_T0SZ);
+ seq_printf(seq, " EPD0: %s\n", data & CTXDESC_CD_0_TCR_EPD0 ? "Yes" : "No");
+ seq_printf(seq, " EPD1: %s\n", data & CTXDESC_CD_0_TCR_EPD1 ? "Yes" : "No");
+
+ /* CD 1 */
+ data = le64_to_cpu(cd->data[1]);
+ seq_printf(seq, " TTBR0: 0x%016llx\n", data & CTXDESC_CD_1_TTB0_MASK);
+
+ /* Display raw CD data */
+ seq_puts(seq, " Raw Data:\n");
+ for (i = 0; i < CTXDESC_CD_DWORDS; i++)
+ seq_printf(seq, " CD[%d]: 0x%016llx\n", i,
+ le64_to_cpu(cd->data[i]));
+}
+
+static int smmu_debugfs_cd_show(struct seq_file *seq, void *unused)
+{
+ struct device *dev = seq->private;
+ struct arm_smmu_master *master;
+ u32 max_ssids, ssid;
+
+ guard(mutex)(&arm_smmu_asid_lock);
+
+ master = dev_iommu_priv_get(dev);
+ if (!master) {
+ seq_puts(seq, "No master data\n");
+ return 0;
+ }
+
+ max_ssids = 1 << master->ssid_bits;
+ seq_printf(seq, "Context Descriptors for device (max SSIDs: %u):\n",
+ max_ssids);
+
+ for (ssid = 0; ssid < max_ssids; ssid++) {
+ struct arm_smmu_cd *cd = arm_smmu_get_cd_ptr(master, ssid);
+
+ if (cd && (le64_to_cpu(cd->data[0]) & CTXDESC_CD_0_V)) {
+ seq_printf(seq, "\n--- SSID %u ---\n", ssid);
+ smmu_debug_dump_cd(seq, cd);
+ }
+ }
+
+ return 0;
+}
+
+static int smmu_debugfs_cd_open(struct inode *inode, struct file *file)
+{
+ struct device *dev = inode->i_private;
+ int ret;
+
+ if (!dev || !get_device(dev))
+ return -ENODEV;
+
+ ret = single_open(file, smmu_debugfs_cd_show, dev);
+ if (ret)
+ put_device(dev);
+
+ return ret;
+}
+
+static int smmu_debugfs_cd_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct device *dev = seq->private;
+
+ single_release(inode, file);
+ if (dev)
+ put_device(dev);
+ return 0;
+}
+
+static const struct file_operations smmu_debugfs_cd_fops = {
+ .owner = THIS_MODULE,
+ .open = smmu_debugfs_cd_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = smmu_debugfs_cd_release,
+};
+
/**
* arm_smmu_debugfs_create_stream_table() - Create debugfs entries for stream table
* @smmu: SMMU device
@@ -373,6 +471,9 @@ int arm_smmu_debugfs_create_stream_table(struct arm_smmu_device *smmu,
kfree(full_path);
}
}
+
+ /* Create CD file to dump all valid Context Descriptors */
+ debugfs_create_file("cd", 0444, dev_dir, dev, &smmu_debugfs_cd_fops);
}
kfree(path);
--
2.33.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs
2026-05-20 6:37 ` [PATCH 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs Qinxin Xia
@ 2026-05-27 2:40 ` Nicolin Chen
0 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 2:40 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Wed, May 20, 2026 at 02:37:11PM +0800, Qinxin Xia wrote:
> +static int smmu_debugfs_cd_show(struct seq_file *seq, void *unused)
> +{
> + struct device *dev = seq->private;
> + struct arm_smmu_master *master;
> + u32 max_ssids, ssid;
> +
> + guard(mutex)(&arm_smmu_asid_lock);
> +
> + master = dev_iommu_priv_get(dev);
> + if (!master) {
> + seq_puts(seq, "No master data\n");
> + return 0;
> + }
arm_smmu_asid_lock fences against domain attachments, required
by CD (yes). But, it doesn't fence against a device unplug. So
this might need both streams_mutex and asid_lock.
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 0/5] Add debugfs support for ARM SMMUv3
2026-03-28 10:17 [RFC PATCH v2 0/5] Add debugfs support for ARM SMMUv3 Qinxin Xia
` (10 preceding siblings ...)
2026-05-20 6:37 ` [PATCH 5/5] iommu/arm-smmu-v3: Add Context Descriptor display to debugfs Qinxin Xia
@ 2026-05-27 0:07 ` Nicolin Chen
11 siblings, 0 replies; 22+ messages in thread
From: Nicolin Chen @ 2026-05-27 0:07 UTC (permalink / raw)
To: Qinxin Xia
Cc: robin.murphy, will, jpb, linux-arm-kernel, iommu, wangzhou1,
prime.zeng, fanghao11, jonathan.cameron, wuyifan50, linuxarm,
linux-kernel
On Wed, May 20, 2026 at 02:37:06PM +0800, Qinxin Xia wrote:
> Add a comprehensive debugfs framework to the ARM SMMUv3 driver,
> providing visibility into internal hardware state for debugging
> and performance analysis. The debugfs entries are organized under
> /sys/kernel/debug/iommu/arm_smmu_v3/, with per-SMMU instance directories
> and per-device stream table entries.
>
> Each SMMU instance provides:
> - capabilities – static SMMU features and queue sizes.
> - registers – SMMU key registers.
> - stream_table/ – a directory per device with subdirectories per Stream ID.
>
> Each Stream ID subdirectory contains:
> - ste – the Stream Table Entry in decoded and raw format.
> - cd – all valid Context Descriptors (Stage 1 translation tables) associated with the device.
> - a symlink named with the device's BDF/name pointing to its sysfs directory for easy navigation.
>
> /sys/kernel/debug/iommu/arm_smmu_v3/smmu0/stream_table/
> ├── <sid>/
> │ ├── ste
> │ ├── cd
> │ └── <dev_name>
>
> Changes since RFCv2:
> Address review comments from Nicolin Chen, kernel test robot, and Sashiko
> automated review:
This seems to reuse the same message ID as RFCv2's?
So, it ended up with attaching to RFCv2, on my side (mutt).
Sashiko also failed to review against the new patches:
https://sashiko.dev/#/patchset/20260328101706.3448655-1-xiaqinxin%40huawei.com
Nicolin
^ permalink raw reply [flat|nested] 22+ messages in thread