[PATCH 2/2] iommu/io-pgtable: Add ARM SMMUv3 page table dump support

Qinxin Xia posted 2 patches 1 month, 3 weeks ago
[PATCH 2/2] iommu/io-pgtable: Add ARM SMMUv3 page table dump support
Posted by Qinxin Xia 1 month, 3 weeks ago
This patch implements the debug interface for dumping ARM SMMUv3
page table entries and protection information. The functionality
enables detailed inspection of IOMMU mappings for debugging and
validation purposes.

1. ARM SMMUv3 driver integration:
   - Implemented arm_smmu_dump_iova_prot() callback
   - Registered dump_iova_prot in iommu_domain_ops

2. ARM LPAE io-pgtable implementation:
   - Added arm_lpae_dump_iova_prot() to io_pgtable_ops
   - Defined protection bit descriptors (prot_bits)
   - Created io_pgtable_fmt_names for human-readable format strings
   - Implemented protection flag formatting in dump_prot()

3. Core io-pgtable interface extension:
   - Added dump_iova_prot callback to io_pgtable_ops

The implementation provides detailed output for each IOVA mapping:
<start> - <end>    lvl <level>  stage <format> <protection flags>
Example: 0xffff2000 - 0xffff3000 lvl 2 stage ARM_64_LPAE_S1 AF SH_NS

Signed-off-by: Qinxin Xia <xiaqinxin@huawei.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c |  13 ++
 drivers/iommu/io-pgtable-arm.c              | 169 ++++++++++++++++++++
 include/linux/io-pgtable.h                  |   4 +
 3 files changed, 186 insertions(+)

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 5968043ac802..4128b3307753 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3411,6 +3411,16 @@ arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
 	return ops->iova_to_phys(ops, iova);
 }
 
+#ifdef CONFIG_IO_PTDUMP
+static size_t
+arm_smmu_dump_iova_prot(struct seq_file *s, struct iommu_domain *domain, dma_addr_t iova)
+{
+	struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
+
+	return ops->dump_iova_prot(s, ops, iova);
+}
+#endif
+
 static struct platform_driver arm_smmu_driver;
 
 static
@@ -3702,6 +3712,9 @@ static const struct iommu_ops arm_smmu_ops = {
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
 		.iova_to_phys		= arm_smmu_iova_to_phys,
+#ifdef CONFIG_IO_PTDUMP
+		.dump_iova_prot		= arm_smmu_dump_iova_prot,
+#endif
 		.free			= arm_smmu_domain_free_paging,
 	}
 };
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 7e8e2216c294..5deb03a85aa6 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -754,6 +754,172 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 	return iopte_to_paddr(d.pte, data) | iova;
 }
 
+#ifdef CONFIG_IO_PTDUMP
+#include <linux/seq_file.h>
+
+struct io_ptdump_prot_bits {
+	uint64_t mask;
+	uint64_t val;
+	const char *set;
+	const char *clear;
+};
+
+static const struct io_ptdump_prot_bits prot_bits[] = {
+	{
+		.mask   = ARM_LPAE_PTE_VALID,
+		.val    = ARM_LPAE_PTE_VALID,
+		.set    = "V",
+		.clear  = " ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_XN,
+		.val    = ARM_LPAE_PTE_XN,
+		.set    = "XN",
+		.clear  = "  ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_DBM,
+		.val    = ARM_LPAE_PTE_DBM,
+		.set    = "DBM",
+		.clear  = "   ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_AF,
+		.val    = ARM_LPAE_PTE_AF,
+		.set    = "AF",
+		.clear  = "  ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_SH_NS,
+		.val    = ARM_LPAE_PTE_SH_NS,
+		.set    = "SH_NS",
+		.clear  = "     ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_SH_OS,
+		.val    = ARM_LPAE_PTE_SH_OS,
+		.set    = "SH_OS",
+		.clear  = "     ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_SH_IS,
+		.val    = ARM_LPAE_PTE_SH_IS,
+		.set    = "SH_IS",
+		.clear  = "     ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_NS,
+		.val    = ARM_LPAE_PTE_NS,
+		.set    = "NS",
+		.clear  = "  ",
+	},
+	{
+		.mask   = ARM_LPAE_PTE_NSTABLE,
+		.val    = ARM_LPAE_PTE_NSTABLE,
+		.set    = "NST",
+		.clear  = "   ",
+	},
+};
+
+const char *io_pgtable_fmt_names[] = {
+	"ARM_32_LPAE_S1",
+	"ARM_32_LPAE_S2",
+	"ARM_64_LPAE_S1",
+	"ARM_64_LPAE_S2",
+	"ARM_V7S",
+	"ARM_MALI_LPAE",
+	"AMD_IOMMU_V1",
+	"AMD_IOMMU_V2",
+	"APPLE_DART",
+	"APPLE_DART2",
+};
+
+struct io_ptdump_prot {
+	int lvl;
+	int stage;
+	char *attr;
+	size_t size;
+};
+
+static void dump_prot(struct seq_file *s, arm_lpae_iopte pte)
+{
+	int capacity = 64;
+	const char *attr;
+	int length = 0;
+
+	char *prot = kzalloc(capacity * sizeof(char), GFP_KERNEL);
+
+	if (!prot)
+		return;
+
+	/* Traverse all predefined permission bits */
+	for (size_t i = 0; i < ARRAY_SIZE(prot_bits); i++) {
+		if ((pte & prot_bits[i].mask) == prot_bits[i].val)
+			attr = prot_bits[i].set;
+		else
+			attr = prot_bits[i].clear;
+
+		size_t attr_len = strlen(attr);
+
+		/* Check and extend the buffer */
+		while (length + attr_len > capacity) {
+			capacity *= 2;
+
+			char *temp = krealloc(prot, capacity * sizeof(char), GFP_KERNEL);
+
+			if (!temp) {
+				kfree(prot);
+				return;
+			}
+
+			prot = temp;
+		}
+
+		length += snprintf(prot + length, capacity - length, "%s ", attr);
+
+		/* Security check: prevents abnormal buffer expansion */
+		if (length > PAGE_SIZE) {
+			pr_err("len = %zu, attr = %s, i =%d\n", length, attr, i);
+			kfree(prot);
+			return;
+		}
+	}
+
+	seq_printf(s, "%s", prot);
+	kfree(prot);
+}
+
+static size_t arm_lpae_dump_iova_prot(struct seq_file *s, struct io_pgtable_ops *ops,
+								   unsigned long iova)
+{
+	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+	struct iova_to_phys_data d;
+	int ret;
+
+	struct io_pgtable_walk_data walk_data = {
+		.data = &d,
+		.visit = visit_iova_to_phys,
+		.addr = iova,
+		.end = iova + 1,
+	};
+
+	/* Only the mapped iova will be output */
+	ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
+	if (ret)
+		return ARM_LPAE_GRANULE(data);
+
+	seq_printf(s, "%lx - %lx    lvl %d  stage %s ",
+			   iova, iova + ARM_LPAE_BLOCK_SIZE(d.lvl, data),
+			   d.lvl, io_pgtable_fmt_names[data->iop.fmt]);
+
+	/* TODO: The dump prot is incomplete and will be supplemented later... */
+	dump_prot(s, d.pte);
+	seq_puts(s, "\n");
+
+	return ARM_LPAE_BLOCK_SIZE(d.lvl, data);
+}
+#endif
+
 static int visit_pgtable_walk(struct io_pgtable_walk_data *walk_data, int lvl,
 			      arm_lpae_iopte *ptep, size_t size)
 {
@@ -950,6 +1116,9 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
 		.iova_to_phys	= arm_lpae_iova_to_phys,
+#ifdef CONFIG_IO_PTDUMP
+		.dump_iova_prot = arm_lpae_dump_iova_prot,
+#endif
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
 	};
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 138fbd89b1e6..307c68f0038c 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -217,6 +217,10 @@ struct io_pgtable_ops {
 			      struct iommu_iotlb_gather *gather);
 	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
 				    unsigned long iova);
+#ifdef CONFIG_IO_PTDUMP
+	size_t (*dump_iova_prot)(struct seq_file *s, struct io_pgtable_ops *ops,
+			      unsigned long iova);
+#endif
 	int (*pgtable_walk)(struct io_pgtable_ops *ops, unsigned long iova, void *wd);
 	int (*read_and_clear_dirty)(struct io_pgtable_ops *ops,
 				    unsigned long iova, size_t size,
-- 
2.33.0
Re: [PATCH 2/2] iommu/io-pgtable: Add ARM SMMUv3 page table dump support
Posted by kernel test robot 1 month, 2 weeks ago
Hi Qinxin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on akpm-mm/mm-everything]
[also build test WARNING on linus/master v6.17-rc1 next-20250815]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Qinxin-Xia/iommu-debug-Add-IOMMU-page-table-dump-debug-facility/20250814-173720
base:   https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything
patch link:    https://lore.kernel.org/r/20250814093005.2040511-3-xiaqinxin%40huawei.com
patch subject: [PATCH 2/2] iommu/io-pgtable: Add ARM SMMUv3 page table dump support
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20250815/202508151641.JHlhcDRk-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250815/202508151641.JHlhcDRk-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508151641.JHlhcDRk-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from include/asm-generic/bug.h:22,
                    from arch/s390/include/asm/bug.h:69,
                    from include/linux/bug.h:5,
                    from include/linux/mmdebug.h:5,
                    from arch/s390/include/asm/cmpxchg.h:11,
                    from arch/s390/include/asm/atomic.h:16,
                    from include/linux/atomic.h:7,
                    from drivers/iommu/io-pgtable-arm.c:12:
   drivers/iommu/io-pgtable-arm.c: In function 'dump_prot':
>> include/linux/kern_levels.h:5:25: warning: format '%zu' expects argument of type 'size_t', but argument 2 has type 'int' [-Wformat=]
       5 | #define KERN_SOH        "\001"          /* ASCII Start Of Header */
         |                         ^~~~~~
   include/linux/printk.h:486:25: note: in definition of macro 'printk_index_wrap'
     486 |                 _p_func(_fmt, ##__VA_ARGS__);                           \
         |                         ^~~~
   include/linux/printk.h:557:9: note: in expansion of macro 'printk'
     557 |         printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
         |         ^~~~~~
   include/linux/kern_levels.h:11:25: note: in expansion of macro 'KERN_SOH'
      11 | #define KERN_ERR        KERN_SOH "3"    /* error conditions */
         |                         ^~~~~~~~
   include/linux/printk.h:557:16: note: in expansion of macro 'KERN_ERR'
     557 |         printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
         |                ^~~~~~~~
   drivers/iommu/io-pgtable-arm.c:882:25: note: in expansion of macro 'pr_err'
     882 |                         pr_err("len = %zu, attr = %s, i =%d\n", length, attr, i);
         |                         ^~~~~~
>> include/linux/kern_levels.h:5:25: warning: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Wformat=]
       5 | #define KERN_SOH        "\001"          /* ASCII Start Of Header */
         |                         ^~~~~~
   include/linux/printk.h:486:25: note: in definition of macro 'printk_index_wrap'
     486 |                 _p_func(_fmt, ##__VA_ARGS__);                           \
         |                         ^~~~
   include/linux/printk.h:557:9: note: in expansion of macro 'printk'
     557 |         printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
         |         ^~~~~~
   include/linux/kern_levels.h:11:25: note: in expansion of macro 'KERN_SOH'
      11 | #define KERN_ERR        KERN_SOH "3"    /* error conditions */
         |                         ^~~~~~~~
   include/linux/printk.h:557:16: note: in expansion of macro 'KERN_ERR'
     557 |         printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
         |                ^~~~~~~~
   drivers/iommu/io-pgtable-arm.c:882:25: note: in expansion of macro 'pr_err'
     882 |                         pr_err("len = %zu, attr = %s, i =%d\n", length, attr, i);
         |                         ^~~~~~
   drivers/iommu/io-pgtable-arm.c: In function 'arm_lpae_dump_iova_prot':
>> drivers/iommu/io-pgtable-arm.c:911:32: warning: format '%lx' expects argument of type 'long unsigned int', but argument 4 has type 'long long unsigned int' [-Wformat=]
     911 |         seq_printf(s, "%lx - %lx    lvl %d  stage %s ",
         |                              ~~^
         |                                |
         |                                long unsigned int
         |                              %llx


vim +911 drivers/iommu/io-pgtable-arm.c

   891	
   892	static size_t arm_lpae_dump_iova_prot(struct seq_file *s, struct io_pgtable_ops *ops,
   893									   unsigned long iova)
   894	{
   895		struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
   896		struct iova_to_phys_data d;
   897		int ret;
   898	
   899		struct io_pgtable_walk_data walk_data = {
   900			.data = &d,
   901			.visit = visit_iova_to_phys,
   902			.addr = iova,
   903			.end = iova + 1,
   904		};
   905	
   906		/* Only the mapped iova will be output */
   907		ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
   908		if (ret)
   909			return ARM_LPAE_GRANULE(data);
   910	
 > 911		seq_printf(s, "%lx - %lx    lvl %d  stage %s ",
   912				   iova, iova + ARM_LPAE_BLOCK_SIZE(d.lvl, data),
   913				   d.lvl, io_pgtable_fmt_names[data->iop.fmt]);
   914	
   915		/* TODO: The dump prot is incomplete and will be supplemented later... */
   916		dump_prot(s, d.pte);
   917		seq_puts(s, "\n");
   918	
   919		return ARM_LPAE_BLOCK_SIZE(d.lvl, data);
   920	}
   921	#endif
   922	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki