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]>
---
 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: %#llx-%#llx\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, ULLONG_MAX);
+            node; node = interval_tree_iter_next(node, 0, ULLONG_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=%#llx-%#llx 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, ULLONG_MAX);
+            node; node = interval_tree_iter_next(node, 0, ULLONG_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


Reply via email to