[PATCH v2] iommu/virtio: Add debugfs state dump

LiMing Wu <[email protected]>
Newsgroups dev.linux.lists.virtualization,dev.linux.lists.iommu
Message-ID <[email protected]>
From: Liming Wu <[email protected]>

Expose basic virtio-iommu runtime state through debugfs so that the
relationship between endpoints, IOMMU groups, domains and active mappings
can be inspected from a running guest.

Create per-device files under /sys/kernel/debug/iommu/virtio/:

  status    - negotiated virtio-iommu features and geometry
  endpoints - endpoint, group and attached domain information
  domains   - domain type, endpoint count and mapping count
  mappings  - active IOVA mappings tracked by the driver

Assisted-by: Codex:gpt-5.5
Signed-off-by: Liming Wu <[email protected]>
---
v2:
- Fix i386 build warnings from kernel test robot.
- Use %pad for dma_addr_t geometry addresses.
- Use ULONG_MAX for interval_tree range arguments.

 drivers/iommu/virtio-iommu.c | 317 ++++++++++++++++++++++++++++++++++-
 1 file changed, 316 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
index 587fc13197f1..9f2355bcf144 100644
--- a/drivers/iommu/virtio-iommu.c
+++ b/drivers/iommu/virtio-iommu.c
@@ -8,13 +8,16 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/delay.h>
+#include <linux/debugfs.h>
 #include <linux/dma-map-ops.h>
 #include <linux/freezer.h>
 #include <linux/interval_tree.h>
 #include <linux/iommu.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/of.h>
 #include <linux/pci.h>
+#include <linux/seq_file.h>
 #include <linux/virtio.h>
 #include <linux/virtio_config.h>
 #include <linux/virtio_ids.h>
@@ -52,6 +55,14 @@ struct viommu_dev {
 	/* Supported MAP flags */
 	u32				map_flags;
 	u32				probe_size;
+
+#ifdef CONFIG_IOMMU_DEBUGFS
+	struct dentry			*debugfs_dir;
+	/* Protects debugfs_domains and debugfs_endpoints. */
+	struct mutex			debugfs_lock;
+	struct list_head		debugfs_domains;
+	struct list_head		debugfs_endpoints;
+#endif
 };
 
 struct viommu_mapping {
@@ -70,6 +81,10 @@ struct viommu_domain {
 	struct rb_root_cached		mappings;
 
 	unsigned long			nr_endpoints;
+
+#ifdef CONFIG_IOMMU_DEBUGFS
+	struct list_head		debugfs_node;
+#endif
 };
 
 struct viommu_endpoint {
@@ -77,6 +92,10 @@ struct viommu_endpoint {
 	struct viommu_dev		*viommu;
 	struct viommu_domain		*vdomain;
 	struct list_head		resv_regions;
+
+#ifdef CONFIG_IOMMU_DEBUGFS
+	struct list_head		debugfs_node;
+#endif
 };
 
 struct viommu_request {
@@ -101,6 +120,283 @@ static struct viommu_domain viommu_identity_domain;
 #define to_viommu_domain(domain)	\
 	container_of(domain, struct viommu_domain, domain)
 
+#ifdef CONFIG_IOMMU_DEBUGFS
+static struct dentry *viommu_debugfs_root;
+
+static const char *viommu_domain_type_name(unsigned int type)
+{
+	switch (type) {
+	case IOMMU_DOMAIN_BLOCKED:
+		return "blocked";
+	case IOMMU_DOMAIN_IDENTITY:
+		return "identity";
+	case IOMMU_DOMAIN_UNMANAGED:
+		return "unmanaged";
+	case IOMMU_DOMAIN_DMA:
+		return "dma";
+	case IOMMU_DOMAIN_DMA_FQ:
+		return "dma-fq";
+	case IOMMU_DOMAIN_SVA:
+		return "sva";
+	case IOMMU_DOMAIN_PLATFORM:
+		return "platform";
+	case IOMMU_DOMAIN_NESTED:
+		return "nested";
+	default:
+		return "unknown";
+	}
+}
+
+static void viommu_debugfs_feature(struct seq_file *s,
+				   struct viommu_dev *viommu,
+				   unsigned int feature,
+				   const char *name)
+{
+	seq_printf(s, "  %-24s %s\n", name,
+		   virtio_has_feature(viommu->vdev, feature) ? "yes" : "no");
+}
+
+static int viommu_debugfs_status_show(struct seq_file *s, void *unused)
+{
+	struct viommu_dev *viommu = s->private;
+
+	seq_printf(s, "device: %s\n", virtio_bus_name(viommu->vdev));
+	seq_printf(s, "input_range: %pad-%pad\n",
+		   &viommu->geometry.aperture_start,
+		   &viommu->geometry.aperture_end);
+	seq_printf(s, "page_size_mask: %#llx\n", viommu->pgsize_bitmap);
+	seq_printf(s, "domain_range: %u-%u\n", viommu->first_domain,
+		   viommu->last_domain);
+	seq_printf(s, "identity_domain_id: %u\n",
+		   viommu->identity_domain_id);
+	seq_printf(s, "probe_size: %u\n", viommu->probe_size);
+	seq_printf(s, "map_flags: %#x\n", viommu->map_flags);
+	seq_puts(s, "features:\n");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_MAP_UNMAP,
+			       "map_unmap");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_INPUT_RANGE,
+			       "input_range");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_DOMAIN_RANGE,
+			       "domain_range");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_PROBE, "probe");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_MMIO, "mmio");
+	viommu_debugfs_feature(s, viommu, VIRTIO_IOMMU_F_BYPASS_CONFIG,
+			       "bypass_config");
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(viommu_debugfs_status);
+
+static void viommu_debugfs_print_endpoint(struct seq_file *s,
+					  struct viommu_endpoint *vdev)
+{
+	int i;
+	u32 domain_id = 0;
+	const char *domain_type = "none";
+	struct iommu_group *group;
+	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(vdev->dev);
+
+	if (vdev->vdomain) {
+		domain_type = viommu_domain_type_name(vdev->vdomain->domain.type);
+		if (vdev->vdomain == &viommu_identity_domain)
+			domain_id = vdev->viommu->identity_domain_id;
+		else
+			domain_id = vdev->vdomain->id;
+	}
+
+	group = iommu_group_get(vdev->dev);
+	seq_printf(s, "endpoint=%s group_id=%d domain_type=%s",
+		   dev_name(vdev->dev), group ? iommu_group_id(group) : -1,
+		   domain_type);
+	if (vdev->vdomain)
+		seq_printf(s, " domain_id=%u", domain_id);
+	if (fwspec) {
+		seq_puts(s, " ids=");
+		for (i = 0; i < fwspec->num_ids; i++)
+			seq_printf(s, "%s%u", i ? "," : "", fwspec->ids[i]);
+	}
+	seq_putc(s, '\n');
+
+	iommu_group_put(group);
+}
+
+static int viommu_debugfs_endpoints_show(struct seq_file *s, void *unused)
+{
+	struct viommu_dev *viommu = s->private;
+	struct viommu_endpoint *vdev;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_for_each_entry(vdev, &viommu->debugfs_endpoints, debugfs_node)
+		viommu_debugfs_print_endpoint(s, vdev);
+	mutex_unlock(&viommu->debugfs_lock);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(viommu_debugfs_endpoints);
+
+static unsigned long viommu_debugfs_count_mappings(struct viommu_domain *vdomain)
+{
+	unsigned long count = 0;
+	struct interval_tree_node *node;
+
+	for (node = interval_tree_iter_first(&vdomain->mappings, 0, ULONG_MAX);
+	     node; node = interval_tree_iter_next(node, 0, ULONG_MAX))
+		count++;
+
+	return count;
+}
+
+static void viommu_debugfs_print_domain(struct seq_file *s,
+					struct viommu_domain *vdomain)
+{
+	unsigned long flags;
+	unsigned long mappings;
+
+	spin_lock_irqsave(&vdomain->mappings_lock, flags);
+	mappings = viommu_debugfs_count_mappings(vdomain);
+	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
+
+	seq_printf(s,
+		   "id=%u type=%s endpoints=%lu mappings=%lu aperture=%pad-%pad pgsize_bitmap=%#lx map_flags=%#x\n",
+		   vdomain->id, viommu_domain_type_name(vdomain->domain.type),
+		   vdomain->nr_endpoints, mappings,
+		   &vdomain->domain.geometry.aperture_start,
+		   &vdomain->domain.geometry.aperture_end,
+		   vdomain->domain.pgsize_bitmap, vdomain->map_flags);
+}
+
+static int viommu_debugfs_domains_show(struct seq_file *s, void *unused)
+{
+	struct viommu_dev *viommu = s->private;
+	struct viommu_domain *vdomain;
+
+	mutex_lock(&viommu->debugfs_lock);
+	if (viommu_identity_domain.nr_endpoints) {
+		seq_printf(s, "id=%u type=%s endpoints=%lu mappings=identity\n",
+			   viommu->identity_domain_id,
+			   viommu_domain_type_name(viommu_identity_domain.domain.type),
+			   viommu_identity_domain.nr_endpoints);
+	}
+	list_for_each_entry(vdomain, &viommu->debugfs_domains, debugfs_node)
+		viommu_debugfs_print_domain(s, vdomain);
+	mutex_unlock(&viommu->debugfs_lock);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(viommu_debugfs_domains);
+
+static void viommu_debugfs_print_mapping(struct seq_file *s,
+					 struct viommu_domain *vdomain)
+{
+	unsigned long flags;
+	struct viommu_mapping *mapping;
+	struct interval_tree_node *node;
+
+	spin_lock_irqsave(&vdomain->mappings_lock, flags);
+	for (node = interval_tree_iter_first(&vdomain->mappings, 0, ULONG_MAX);
+	     node; node = interval_tree_iter_next(node, 0, ULONG_MAX)) {
+		mapping = container_of(node, struct viommu_mapping, iova);
+		seq_printf(s,
+			   "domain_id=%u iova=%#lx-%#lx phys=%pa size=%#lx flags=%#x\n",
+			   vdomain->id, mapping->iova.start, mapping->iova.last,
+			   &mapping->paddr,
+			   mapping->iova.last - mapping->iova.start + 1,
+			   mapping->flags);
+	}
+	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
+}
+
+static int viommu_debugfs_mappings_show(struct seq_file *s, void *unused)
+{
+	struct viommu_dev *viommu = s->private;
+	struct viommu_domain *vdomain;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_for_each_entry(vdomain, &viommu->debugfs_domains, debugfs_node)
+		viommu_debugfs_print_mapping(s, vdomain);
+	mutex_unlock(&viommu->debugfs_lock);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(viommu_debugfs_mappings);
+
+static void viommu_debugfs_init(struct viommu_dev *viommu)
+{
+	struct dentry *parent;
+
+	if (!iommu_debugfs_dir)
+		return;
+
+	if (!viommu_debugfs_root)
+		viommu_debugfs_root = debugfs_create_dir("virtio",
+							 iommu_debugfs_dir);
+
+	parent = viommu_debugfs_root ?: iommu_debugfs_dir;
+	viommu->debugfs_dir = debugfs_create_dir(virtio_bus_name(viommu->vdev),
+						 parent);
+	if (IS_ERR_OR_NULL(viommu->debugfs_dir))
+		return;
+
+	debugfs_create_file("status", 0444, viommu->debugfs_dir, viommu,
+			    &viommu_debugfs_status_fops);
+	debugfs_create_file("endpoints", 0444, viommu->debugfs_dir, viommu,
+			    &viommu_debugfs_endpoints_fops);
+	debugfs_create_file("domains", 0444, viommu->debugfs_dir, viommu,
+			    &viommu_debugfs_domains_fops);
+	debugfs_create_file("mappings", 0444, viommu->debugfs_dir, viommu,
+			    &viommu_debugfs_mappings_fops);
+}
+
+static void viommu_debugfs_remove(struct viommu_dev *viommu)
+{
+	debugfs_remove_recursive(viommu->debugfs_dir);
+	viommu->debugfs_dir = NULL;
+}
+
+static void viommu_debugfs_add_domain(struct viommu_domain *vdomain)
+{
+	struct viommu_dev *viommu = vdomain->viommu;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_add_tail(&vdomain->debugfs_node, &viommu->debugfs_domains);
+	mutex_unlock(&viommu->debugfs_lock);
+}
+
+static void viommu_debugfs_remove_domain(struct viommu_domain *vdomain)
+{
+	struct viommu_dev *viommu = vdomain->viommu;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_del(&vdomain->debugfs_node);
+	mutex_unlock(&viommu->debugfs_lock);
+}
+
+static void viommu_debugfs_add_endpoint(struct viommu_endpoint *vdev)
+{
+	struct viommu_dev *viommu = vdev->viommu;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_add_tail(&vdev->debugfs_node, &viommu->debugfs_endpoints);
+	mutex_unlock(&viommu->debugfs_lock);
+}
+
+static void viommu_debugfs_remove_endpoint(struct viommu_endpoint *vdev)
+{
+	struct viommu_dev *viommu = vdev->viommu;
+
+	mutex_lock(&viommu->debugfs_lock);
+	list_del(&vdev->debugfs_node);
+	mutex_unlock(&viommu->debugfs_lock);
+}
+#else
+static void viommu_debugfs_init(struct viommu_dev *viommu) {}
+static void viommu_debugfs_remove(struct viommu_dev *viommu) {}
+static void viommu_debugfs_add_domain(struct viommu_domain *vdomain) {}
+static void viommu_debugfs_remove_domain(struct viommu_domain *vdomain) {}
+static void viommu_debugfs_add_endpoint(struct viommu_endpoint *vdev) {}
+static void viommu_debugfs_remove_endpoint(struct viommu_endpoint *vdev) {}
+#endif
+
 static int viommu_get_req_errno(void *buf, size_t len)
 {
 	struct virtio_iommu_req_tail *tail = buf + len - sizeof(*tail);
@@ -676,6 +972,9 @@ static struct iommu_domain *viommu_domain_alloc_paging(struct device *dev)
 
 	spin_lock_init(&vdomain->mappings_lock);
 	vdomain->mappings = RB_ROOT_CACHED;
+#ifdef CONFIG_IOMMU_DEBUGFS
+	INIT_LIST_HEAD(&vdomain->debugfs_node);
+#endif
 
 	ret = ida_alloc_range(&viommu->domain_ids, viommu->first_domain,
 			      viommu->last_domain, GFP_KERNEL);
@@ -691,6 +990,7 @@ static struct iommu_domain *viommu_domain_alloc_paging(struct device *dev)
 
 	vdomain->map_flags = viommu->map_flags;
 	vdomain->viommu = viommu;
+	viommu_debugfs_add_domain(vdomain);
 
 	return &vdomain->domain;
 }
@@ -702,8 +1002,10 @@ static void viommu_domain_free(struct iommu_domain *domain)
 	/* Free all remaining mappings */
 	viommu_del_mappings(vdomain, 0, ULLONG_MAX);
 
-	if (vdomain->viommu)
+	if (vdomain->viommu) {
+		viommu_debugfs_remove_domain(vdomain);
 		ida_free(&vdomain->viommu->domain_ids, vdomain->id);
+	}
 
 	kfree(vdomain);
 }
@@ -1035,6 +1337,9 @@ static struct iommu_device *viommu_probe_device(struct device *dev)
 	vdev->dev = dev;
 	vdev->viommu = viommu;
 	INIT_LIST_HEAD(&vdev->resv_regions);
+#ifdef CONFIG_IOMMU_DEBUGFS
+	INIT_LIST_HEAD(&vdev->debugfs_node);
+#endif
 	dev_iommu_priv_set(dev, vdev);
 
 	if (viommu->probe_size) {
@@ -1044,6 +1349,8 @@ static struct iommu_device *viommu_probe_device(struct device *dev)
 			goto err_free_dev;
 	}
 
+	viommu_debugfs_add_endpoint(vdev);
+
 	return &viommu->iommu;
 
 err_free_dev:
@@ -1057,6 +1364,7 @@ static void viommu_release_device(struct device *dev)
 {
 	struct viommu_endpoint *vdev = dev_iommu_priv_get(dev);
 
+	viommu_debugfs_remove_endpoint(vdev);
 	viommu_detach_dev(vdev);
 	iommu_put_resv_regions(dev, &vdev->resv_regions);
 	kfree(vdev);
@@ -1170,6 +1478,11 @@ static int viommu_probe(struct virtio_device *vdev)
 	viommu->dev = dev;
 	viommu->vdev = vdev;
 	INIT_LIST_HEAD(&viommu->requests);
+#ifdef CONFIG_IOMMU_DEBUGFS
+	mutex_init(&viommu->debugfs_lock);
+	INIT_LIST_HEAD(&viommu->debugfs_domains);
+	INIT_LIST_HEAD(&viommu->debugfs_endpoints);
+#endif
 
 	ret = viommu_init_vqs(viommu);
 	if (ret)
@@ -1237,6 +1550,7 @@ static int viommu_probe(struct virtio_device *vdev)
 	vdev->priv = viommu;
 
 	iommu_device_register(&viommu->iommu, &viommu_ops, parent_dev);
+	viommu_debugfs_init(viommu);
 
 	dev_info(dev, "input address: %u bits\n",
 		 order_base_2(viommu->geometry.aperture_end));
@@ -1254,6 +1568,7 @@ static void viommu_remove(struct virtio_device *vdev)
 {
 	struct viommu_dev *viommu = vdev->priv;
 
+	viommu_debugfs_remove(viommu);
 	iommu_device_sysfs_remove(&viommu->iommu);
 	iommu_device_unregister(&viommu->iommu);
 
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.