mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] iommu: Reserve PCI host bridge MMIO windows in group reserved regions
@ 2026-09-21  7:02 Guanghui Feng
  2026-09-21 10:39 ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Guanghui Feng
  0 siblings, 1 reply; 24+ messages in thread
From: Guanghui Feng @ 2026-09-21  7:02 UTC (permalink / raw)
  To: robin.murphy, joro, will; +Cc: iommu, linux-kernel, alex, kvm

The DMA IOVA layer reserves PCI host bridge MMIO windows via
iova_reserve_pci_windows() to prevent IOVA allocations from
overlapping with those address ranges.  As commit fade1ec055dc
("iommu/dma: Avoid PCI host bridge windows") explains, a host
bridge may interpret addresses falling within its MMIO windows as
peer-to-peer DMA, leading to faults, data corruption, or DMA
transactions being misrouted to the wrong PCIe device.

However, VFIO type1 obtains reserved regions through
iommu_get_group_resv_regions() and reports the available IOVA
ranges to userspace via VFIO_IOMMU_GET_INFO.  Previously
iommu_get_group_resv_regions() only returned IOMMU driver-level
reserved regions (RMRR, Unity Maps, MSI regions, etc.) and did
not include PCI host bridge MMIO windows.  This allowed userspace
to choose IOVA addresses that overlap with bridge windows.

The problem is especially dangerous when a device sits behind a
PCIe switch and ACS Upstream Forwarding is not fully enabled: the
switch may route DMA TLPs as peer-to-peer traffic to other
downstream devices instead of forwarding them upstream to the
root complex for IOMMU translation.

Fix this by:

  - Introducing iommu_get_pci_resv_windows(), a common helper
    that walks a PCI host bridge's MMIO windows and creates
    IOMMU_RESV_RESERVED region entries for each one.

  - Calling iommu_resv_pci_windows() from
    iommu_get_group_resv_regions() so that all group-level
    consumers (VFIO type1, sysfs reserved_regions) automatically
    receive the PCI window reservations.

  - Refactoring iova_reserve_pci_windows() to reuse
    iommu_get_pci_resv_windows(), eliminating the duplicated
    bridge window enumeration logic.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/dma-iommu.c  | 21 +++++++----
 drivers/iommu/iommu-priv.h | 11 ++++++
 drivers/iommu/iommu.c      | 73 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 58c624513cd4..bccb53043462 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -37,6 +37,7 @@
 
 #include "dma-iommu.h"
 #include "iommu-pages.h"
+#include "iommu-priv.h"
 
 struct iommu_dma_msi_page {
 	struct list_head	list;
@@ -508,18 +509,26 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
 		struct iova_domain *iovad)
 {
 	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct iommu_resv_region *region, *next;
 	struct resource_entry *window;
 	unsigned long lo, hi;
 	phys_addr_t start = 0, end;
+	LIST_HEAD(pci_windows);
+	int ret;
 
-	resource_list_for_each_entry(window, &bridge->windows) {
-		if (resource_type(window->res) != IORESOURCE_MEM)
-			continue;
+	ret = iommu_get_pci_resv_windows(dev, &pci_windows);
 
-		lo = iova_pfn(iovad, window->res->start - window->offset);
-		hi = iova_pfn(iovad, window->res->end - window->offset);
-		reserve_iova(iovad, lo, hi);
+	list_for_each_entry_safe(region, next, &pci_windows, list) {
+		if (!ret) {
+			lo = iova_pfn(iovad, region->start);
+			hi = iova_pfn(iovad, region->start + region->length - 1);
+			reserve_iova(iovad, lo, hi);
+		}
+		list_del(&region->list);
+		kfree(region);
 	}
+	if (ret)
+		return ret;
 
 	/* Get reserved DMA windows from host bridge */
 	list_sort(NULL, &bridge->dma_ranges, iommu_dma_ranges_sort);
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index aaffad5854fc..bc7fab0c827e 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -123,4 +123,15 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+#ifdef CONFIG_PCI
+struct pci_dev;
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head);
+#else
+static inline int iommu_get_pci_resv_windows(struct pci_dev *dev,
+					     struct list_head *head)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..de675ed27753 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,72 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_get_pci_resv_windows - collect PCI host bridge MMIO windows as
+ *                               reserved regions
+ * @dev: PCI device whose host bridge to scan
+ * @head: list head to append iommu_resv_region entries to
+ *
+ * Walks the MMIO windows of @dev's PCI host bridge and creates an
+ * IOMMU_RESV_RESERVED region for each one.  The caller must free the
+ * returned entries with kfree() when done.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct resource_entry *window;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		struct iommu_resv_region *region;
+		phys_addr_t start;
+		size_t length;
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		start = window->res->start - window->offset;
+		length = window->res->end - window->res->start + 1;
+
+		region = iommu_alloc_resv_region(start, length, 0,
+						 IOMMU_RESV_RESERVED,
+						 GFP_KERNEL);
+		if (!region)
+			return -ENOMEM;
+
+		list_add_tail(&region->list, head);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iommu_get_pci_resv_windows);
+
+/*
+ * Reserve PCI host bridge MMIO windows as IOMMU_RESV_RESERVED regions.
+ * This prevents IOVA allocations from overlapping with PCI MMIO address
+ * ranges, which could cause PCIe switches to misroute DMA transactions.
+ *
+ * All PCI devices within the same IOMMU group share the same host bridge,
+ * so we only need to find the first PCI device.
+ *
+ * Caller must hold group->mutex.
+ */
+static int iommu_resv_pci_windows(struct iommu_group *group,
+				  struct list_head *head)
+{
+	struct group_device *gdev;
+
+	for_each_group_device(group, gdev) {
+		if (!dev_is_pci(gdev->dev))
+			continue;
+		return iommu_get_pci_resv_windows(to_pci_dev(gdev->dev),
+						  head);
+	}
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
@@ -969,6 +1035,13 @@ int iommu_get_group_resv_regions(struct iommu_group *group,
 		if (ret)
 			break;
 	}
+
+	/* Reserve PCI host bridge MMIO windows to prevent IOVA conflicts */
+#ifdef CONFIG_PCI
+	if (!ret)
+		ret = iommu_resv_pci_windows(group, head);
+#endif
+
 	mutex_unlock(&group->mutex);
 	return ret;
 }
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA
  2026-09-21  7:02 [PATCH] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
@ 2026-09-21 10:39 ` Guanghui Feng
  2026-09-21 10:39   ` [PATCH v2 1/2] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
                     ` (2 more replies)
  0 siblings, 3 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-09-21 10:39 UTC (permalink / raw)
  To: jgg, kevin.tian, alex, joro, robin.murphy, will; +Cc: iommu, kvm, linux-kernel

The DMA IOVA layer reserves PCI host bridge MMIO windows via
iova_reserve_pci_windows() to keep IOVA allocations from overlapping
those address ranges.  As commit fade1ec055dc ("iommu/dma: Avoid PCI
host bridge windows") explains, a host bridge may interpret addresses
falling within its MMIO windows as peer-to-peer DMA, leading to
faults, data corruption, or DMA transactions being misrouted to the
wrong PCIe device.  This is especially dangerous when a device sits
behind a PCIe switch and ACS Upstream Forwarding is not fully enabled.

Passthrough paths that let userspace pick IOVAs did not honour these
windows: VFIO type1 and iommufd only excluded IOMMU driver-level
reserved regions (RMRR, unity maps, MSI), not PCI host bridge MMIO
windows.

This series closes that gap:

  Patch 1 adds a common helper, iommu_get_pci_resv_windows(), and
  reserves PCI host bridge MMIO windows in
  iommu_get_group_resv_regions() so all group-level consumers (VFIO
  type1, sysfs reserved_regions) avoid them; iova_reserve_pci_windows()
  is refactored to reuse the helper.

  Patch 2 switches iommufd's iopt_table_enforce_dev_resv_regions() to
  iommu_get_group_resv_regions() so IOMMU_IOAS_IOVA_RANGES also avoids
  PCI host bridge MMIO windows, aligning iommufd with VFIO type1.

v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/

Changes since v1:
- v1 was a single patch; v2 is a two-patch series.
- Patch 1: build the reservations with iommu_insert_resv_region()
  instead of list_add_tail(), so the group reserved list stays sorted
  by start address and overlapping same-type regions are merged.  The
  helper now frees its partial list itself on error and leaves the
  caller's list untouched.
- Patch 2 (new): reserve PCI host bridge MMIO windows for the iommufd
  IOAS as well, so IOMMU_IOAS_IOVA_RANGES no longer reports IOVAs that
  overlap PCI MMIO windows.

Notes on earlier automated review feedback:
- The size_t length of each window matches struct iommu_resv_region's
  existing field type; no new truncation is introduced on 64-bit.
- An IOMMU group cannot span multiple PCI host bridges:
  pci_device_group() only groups devices within a single host bridge
  hierarchy, so scanning the first PCI device of the group is enough.

Guanghui Feng (2):
  iommu: Reserve PCI host bridge MMIO windows in group reserved regions
  iommufd: Reserve PCI host bridge MMIO windows in IOAS reserved regions

 drivers/iommu/dma-iommu.c            | 17 ++++--
 drivers/iommu/iommu-priv.h           | 11 ++++
 drivers/iommu/iommu.c                | 85 ++++++++++++++++++++++++++++
 drivers/iommu/iommufd/io_pagetable.c | 15 ++++-
 4 files changed, 121 insertions(+), 7 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 1/2] iommu: Reserve PCI host bridge MMIO windows in group reserved regions
  2026-09-21 10:39 ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Guanghui Feng
@ 2026-09-21 10:39   ` Guanghui Feng
  2026-09-21 10:39   ` [PATCH v2 2/2] iommufd: Reserve PCI host bridge MMIO windows in IOAS " Guanghui Feng
  2026-09-21 11:24   ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Robin Murphy
  2 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-09-21 10:39 UTC (permalink / raw)
  To: jgg, kevin.tian, alex, joro, robin.murphy, will; +Cc: iommu, kvm, linux-kernel

The DMA IOVA layer reserves PCI host bridge MMIO windows via
iova_reserve_pci_windows() to prevent IOVA allocations from
overlapping with those address ranges.  As commit fade1ec055dc
("iommu/dma: Avoid PCI host bridge windows") explains, a host
bridge may interpret addresses falling within its MMIO windows as
peer-to-peer DMA, leading to faults, data corruption, or DMA
transactions being misrouted to the wrong PCIe device.

However, VFIO type1 obtains reserved regions through
iommu_get_group_resv_regions() and reports the available IOVA
ranges to userspace via VFIO_IOMMU_GET_INFO.  Previously
iommu_get_group_resv_regions() only returned IOMMU driver-level
reserved regions (RMRR, Unity Maps, MSI regions, etc.) and did
not include PCI host bridge MMIO windows.  This allowed userspace
to choose IOVA addresses that overlap with bridge windows.

The problem is especially dangerous when a device sits behind a
PCIe switch and ACS Upstream Forwarding is not fully enabled: the
switch may route DMA TLPs as peer-to-peer traffic to other
downstream devices instead of forwarding them upstream to the
root complex for IOMMU translation.

Fix this by:

  - Introducing iommu_get_pci_resv_windows(), a common helper
    that walks a PCI host bridge's MMIO windows and creates
    IOMMU_RESV_RESERVED region entries for each one.

  - Calling iommu_resv_pci_windows() from
    iommu_get_group_resv_regions() so that all group-level
    consumers (VFIO type1, sysfs reserved_regions) automatically
    receive the PCI window reservations.

  - Refactoring iova_reserve_pci_windows() to reuse
    iommu_get_pci_resv_windows(), eliminating the duplicated
    bridge window enumeration logic.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/dma-iommu.c  | 17 +++++---
 drivers/iommu/iommu-priv.h | 11 +++++
 drivers/iommu/iommu.c      | 85 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 58c624513cd4..c97622826d9d 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -37,6 +37,7 @@
 
 #include "dma-iommu.h"
 #include "iommu-pages.h"
+#include "iommu-priv.h"
 
 struct iommu_dma_msi_page {
 	struct list_head	list;
@@ -508,17 +509,23 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
 		struct iova_domain *iovad)
 {
 	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct iommu_resv_region *region, *next;
 	struct resource_entry *window;
 	unsigned long lo, hi;
 	phys_addr_t start = 0, end;
+	LIST_HEAD(pci_windows);
+	int ret;
 
-	resource_list_for_each_entry(window, &bridge->windows) {
-		if (resource_type(window->res) != IORESOURCE_MEM)
-			continue;
+	ret = iommu_get_pci_resv_windows(dev, &pci_windows);
+	if (ret)
+		return ret;
 
-		lo = iova_pfn(iovad, window->res->start - window->offset);
-		hi = iova_pfn(iovad, window->res->end - window->offset);
+	list_for_each_entry_safe(region, next, &pci_windows, list) {
+		lo = iova_pfn(iovad, region->start);
+		hi = iova_pfn(iovad, region->start + region->length - 1);
 		reserve_iova(iovad, lo, hi);
+		list_del(&region->list);
+		kfree(region);
 	}
 
 	/* Get reserved DMA windows from host bridge */
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index aaffad5854fc..bc7fab0c827e 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -123,4 +123,15 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+#ifdef CONFIG_PCI
+struct pci_dev;
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head);
+#else
+static inline int iommu_get_pci_resv_windows(struct pci_dev *dev,
+					     struct list_head *head)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..b77e9818e4a4 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,84 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_get_pci_resv_windows - collect PCI host bridge MMIO windows as
+ *                               reserved regions
+ * @dev: PCI device whose host bridge to scan
+ * @head: list head to append iommu_resv_region entries to
+ *
+ * Walks the MMIO windows of @dev's PCI host bridge and inserts an
+ * IOMMU_RESV_RESERVED region for each one using iommu_insert_resv_region(),
+ * keeping the entries sorted by start address and merging overlapping
+ * regions of the same type.  On success the inserted entries are appended
+ * to @head and the caller must free them with kfree() when done.  On
+ * failure any entries built so far are freed internally and @head is left
+ * unmodified.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct iommu_resv_region *region, *next;
+	struct resource_entry *window;
+	LIST_HEAD(resv_windows);
+	int ret;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		struct iommu_resv_region tmp = {
+			.type = IOMMU_RESV_RESERVED,
+		};
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		tmp.start = window->res->start - window->offset;
+		tmp.length = window->res->end - window->res->start + 1;
+
+		ret = iommu_insert_resv_region(&tmp, &resv_windows);
+		if (ret)
+			goto err_free;
+	}
+
+	list_splice_tail(&resv_windows, head);
+	return 0;
+
+err_free:
+	list_for_each_entry_safe(region, next, &resv_windows, list) {
+		list_del(&region->list);
+		kfree(region);
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_get_pci_resv_windows);
+
+/*
+ * Reserve PCI host bridge MMIO windows as IOMMU_RESV_RESERVED regions.
+ * This prevents IOVA allocations from overlapping with PCI MMIO address
+ * ranges, which could cause PCIe switches to misroute DMA transactions.
+ *
+ * All PCI devices within the same IOMMU group share the same host bridge,
+ * so we only need to find the first PCI device.
+ *
+ * Caller must hold group->mutex.
+ */
+static int iommu_resv_pci_windows(struct iommu_group *group,
+				  struct list_head *head)
+{
+	struct group_device *gdev;
+
+	for_each_group_device(group, gdev) {
+		if (!dev_is_pci(gdev->dev))
+			continue;
+		return iommu_get_pci_resv_windows(to_pci_dev(gdev->dev),
+						  head);
+	}
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
@@ -969,6 +1047,13 @@ int iommu_get_group_resv_regions(struct iommu_group *group,
 		if (ret)
 			break;
 	}
+
+	/* Reserve PCI host bridge MMIO windows to prevent IOVA conflicts */
+#ifdef CONFIG_PCI
+	if (!ret)
+		ret = iommu_resv_pci_windows(group, head);
+#endif
+
 	mutex_unlock(&group->mutex);
 	return ret;
 }
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 2/2] iommufd: Reserve PCI host bridge MMIO windows in IOAS reserved regions
  2026-09-21 10:39 ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Guanghui Feng
  2026-09-21 10:39   ` [PATCH v2 1/2] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
@ 2026-09-21 10:39   ` Guanghui Feng
  2026-09-21 11:24   ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Robin Murphy
  2 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-09-21 10:39 UTC (permalink / raw)
  To: jgg, kevin.tian, alex, joro, robin.murphy, will; +Cc: iommu, kvm, linux-kernel

iommufd builds the reserved IOVA ranges of an IOAS in
iopt_table_enforce_dev_resv_regions() by calling the per-device
iommu_get_resv_regions(), which only returns IOMMU driver-level
reserved regions (RMRR, unity maps, MSI windows, etc.) and does
not include PCI host bridge MMIO windows.  As a result, the ranges
reported to userspace by IOMMU_IOAS_IOVA_RANGES may overlap with a
host bridge's MMIO windows, allowing userspace to map IOVAs that a
PCIe switch might misinterpret as peer-to-peer DMA and misroute to
the wrong device.

VFIO type1 avoids this because it collects reserved regions through
iommu_get_group_resv_regions(), which already reserves PCI host
bridge MMIO windows.

Switch iommufd to iommu_get_group_resv_regions() as well so that
the IOAS reserved set includes PCI host bridge MMIO windows and
IOMMU_IOAS_IOVA_RANGES no longer hands out IOVAs overlapping them,
aligning iommufd behaviour with VFIO type1.  The per-device @dev is
kept as the reservation owner so that per-device detach cleanup via
__iopt_remove_reserved_iova() continues to work unchanged.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/io_pagetable.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 4e447ce74cf6..7198deba378e 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -1582,6 +1582,7 @@ int iopt_table_enforce_dev_resv_regions(struct io_pagetable *iopt,
 					phys_addr_t *sw_msi_start)
 {
 	struct iommu_resv_region *resv;
+	struct iommu_group *group;
 	LIST_HEAD(resv_regions);
 	unsigned int num_hw_msi = 0;
 	unsigned int num_sw_msi = 0;
@@ -1591,8 +1592,16 @@ int iopt_table_enforce_dev_resv_regions(struct io_pagetable *iopt,
 		return -EINVAL;
 
 	down_write(&iopt->iova_rwsem);
-	/* FIXME: drivers allocate memory but there is no failure propagated */
-	iommu_get_resv_regions(dev, &resv_regions);
+
+	group = iommu_group_get(dev);
+	if (!group) {
+		rc = -ENODEV;
+		goto out_unlock;
+	}
+
+	rc = iommu_get_group_resv_regions(group, &resv_regions);
+	if (rc)
+		goto out_free_resv;
 
 	list_for_each_entry(resv, &resv_regions, list) {
 		if (resv->type == IOMMU_RESV_DIRECT_RELAXABLE)
@@ -1624,6 +1633,8 @@ int iopt_table_enforce_dev_resv_regions(struct io_pagetable *iopt,
 	__iopt_remove_reserved_iova(iopt, dev);
 out_free_resv:
 	iommu_put_resv_regions(dev, &resv_regions);
+	iommu_group_put(group);
+out_unlock:
 	up_write(&iopt->iova_rwsem);
 	return rc;
 }
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA
  2026-09-21 10:39 ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Guanghui Feng
  2026-09-21 10:39   ` [PATCH v2 1/2] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
  2026-09-21 10:39   ` [PATCH v2 2/2] iommufd: Reserve PCI host bridge MMIO windows in IOAS " Guanghui Feng
@ 2026-09-21 11:24   ` Robin Murphy
  2026-09-21 11:55     ` Jason Gunthorpe
  2 siblings, 1 reply; 24+ messages in thread
From: Robin Murphy @ 2026-09-21 11:24 UTC (permalink / raw)
  To: Guanghui Feng, jgg, kevin.tian, alex, joro, will; +Cc: iommu, kvm, linux-kernel

On 21/09/2026 11:39 am, Guanghui Feng wrote:
> The DMA IOVA layer reserves PCI host bridge MMIO windows via
> iova_reserve_pci_windows() to keep IOVA allocations from overlapping
> those address ranges.  As commit fade1ec055dc ("iommu/dma: Avoid PCI
> host bridge windows") explains, a host bridge may interpret addresses
> falling within its MMIO windows as peer-to-peer DMA, leading to
> faults, data corruption, or DMA transactions being misrouted to the
> wrong PCIe device.  This is especially dangerous when a device sits
> behind a PCIe switch and ACS Upstream Forwarding is not fully enabled.
> 
> Passthrough paths that let userspace pick IOVAs did not honour these
> windows: VFIO type1 and iommufd only excluded IOMMU driver-level
> reserved regions (RMRR, unity maps, MSI), not PCI host bridge MMIO
> windows.

This was intentional - I forget where the exact discussion happened, but 
at the time we decided that exposing reserved regions for entire windows 
was the wrong thing to do, as being pessimistically inaccurate might be 
OK for the internal DMA API, but restricting userspace is another 
matter, and I think the consensus was that it wasn't really the IOMMU 
layer's job to get deep into PCI details anyway. Such "accidental" P2P 
can only happen within a group that's assigned to VFIO as a whole, so 
IIRC the conclusion was that users should already have enough 
information at the VFIO level to avoid BAR addresses if they need to.

In fact we did briefly do this once before, and it was explicitly undone 
again by cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation 
back into dma specific path."). I think there may also have been a 
practical issue with it breaking some standard Qemu setups.

Thanks,
Robin.

> 
> This series closes that gap:
> 
>    Patch 1 adds a common helper, iommu_get_pci_resv_windows(), and
>    reserves PCI host bridge MMIO windows in
>    iommu_get_group_resv_regions() so all group-level consumers (VFIO
>    type1, sysfs reserved_regions) avoid them; iova_reserve_pci_windows()
>    is refactored to reuse the helper.
> 
>    Patch 2 switches iommufd's iopt_table_enforce_dev_resv_regions() to
>    iommu_get_group_resv_regions() so IOMMU_IOAS_IOVA_RANGES also avoids
>    PCI host bridge MMIO windows, aligning iommufd with VFIO type1.
> 
> v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/
> 
> Changes since v1:
> - v1 was a single patch; v2 is a two-patch series.
> - Patch 1: build the reservations with iommu_insert_resv_region()
>    instead of list_add_tail(), so the group reserved list stays sorted
>    by start address and overlapping same-type regions are merged.  The
>    helper now frees its partial list itself on error and leaves the
>    caller's list untouched.
> - Patch 2 (new): reserve PCI host bridge MMIO windows for the iommufd
>    IOAS as well, so IOMMU_IOAS_IOVA_RANGES no longer reports IOVAs that
>    overlap PCI MMIO windows.
> 
> Notes on earlier automated review feedback:
> - The size_t length of each window matches struct iommu_resv_region's
>    existing field type; no new truncation is introduced on 64-bit.
> - An IOMMU group cannot span multiple PCI host bridges:
>    pci_device_group() only groups devices within a single host bridge
>    hierarchy, so scanning the first PCI device of the group is enough.
> 
> Guanghui Feng (2):
>    iommu: Reserve PCI host bridge MMIO windows in group reserved regions
>    iommufd: Reserve PCI host bridge MMIO windows in IOAS reserved regions
> 
>   drivers/iommu/dma-iommu.c            | 17 ++++--
>   drivers/iommu/iommu-priv.h           | 11 ++++
>   drivers/iommu/iommu.c                | 85 ++++++++++++++++++++++++++++
>   drivers/iommu/iommufd/io_pagetable.c | 15 ++++-
>   4 files changed, 121 insertions(+), 7 deletions(-)
> 


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA
  2026-09-21 11:24   ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Robin Murphy
@ 2026-09-21 11:55     ` Jason Gunthorpe
  2026-10-04 16:22       ` [PATCH v3 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
  0 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-09-21 11:55 UTC (permalink / raw)
  To: Robin Murphy
  Cc: Guanghui Feng, kevin.tian, alex, joro, will, iommu, kvm, linux-kernel

On Mon, Sep 21, 2026 at 12:24:13PM +0100, Robin Murphy wrote:
> On 21/09/2026 11:39 am, Guanghui Feng wrote:
> > The DMA IOVA layer reserves PCI host bridge MMIO windows via
> > iova_reserve_pci_windows() to keep IOVA allocations from overlapping
> > those address ranges.  As commit fade1ec055dc ("iommu/dma: Avoid PCI
> > host bridge windows") explains, a host bridge may interpret addresses
> > falling within its MMIO windows as peer-to-peer DMA, leading to
> > faults, data corruption, or DMA transactions being misrouted to the
> > wrong PCIe device.  This is especially dangerous when a device sits
> > behind a PCIe switch and ACS Upstream Forwarding is not fully enabled.
> > 
> > Passthrough paths that let userspace pick IOVAs did not honour these
> > windows: VFIO type1 and iommufd only excluded IOMMU driver-level
> > reserved regions (RMRR, unity maps, MSI), not PCI host bridge MMIO
> > windows.
> 
> This was intentional - I forget where the exact discussion happened, but at
> the time we decided that exposing reserved regions for entire windows was
> the wrong thing to do, as being pessimistically inaccurate might be OK for
> the internal DMA API, but restricting userspace is another matter, and I
> think the consensus was that it wasn't really the IOMMU layer's job to get
> deep into PCI details anyway. Such "accidental" P2P can only happen within a
> group that's assigned to VFIO as a whole, so IIRC the conclusion was that
> users should already have enough information at the VFIO level to avoid BAR
> addresses if they need to.

Yes, but we never did a a very good job to make the information
visible to the VFIO userspace level, and I don't think anyone ever
implemented some sysfs parsing to try to get it.

It would be a nice improvement to expose these pessimistic ranges to
userspace through iommufd so it can choose to opt into them if it
wants. Some kind of helper that the dma-iommu.c and iommufd can call
perhaps.

Jason

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance
  2026-09-21 11:55     ` Jason Gunthorpe
@ 2026-10-04 16:22       ` Guanghui Feng
  2026-10-04 16:22         ` [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper Guanghui Feng
  2026-10-04 16:22         ` [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
  0 siblings, 2 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-04 16:22 UTC (permalink / raw)
  To: jgg
  Cc: alex, guanghuifeng, iommu, joro, kevin.tian, kvm, linux-kernel,
	robin.murphy, will

When a device sits behind a PCIe switch and ACS Upstream Forwarding is
not fully enabled, DMA TLPs whose IOVA happens to fall within a host
bridge MMIO window may be routed peer-to-peer to another downstream
device instead of upstream to the root complex for IOMMU translation.
This can lead to faults, data corruption, or silent misrouting.

The DMA IOVA layer already avoids this via iova_reserve_pci_windows().
However, passthrough paths that let userspace pick IOVAs (VFIO type1,
iommufd) had no mechanism to learn about these ranges.  Commit
cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into
dma specific path.") intentionally keeps this information out of the
IOMMU reserved-region API to avoid pessimistically restricting
userspace.

Following Jason Gunthorpe's suggestion [1], this series takes an opt-in
approach: the kernel reports the information, userspace decides whether
to avoid it.

  Patch 1 adds iommu_get_pci_resv_windows(), a shared helper that
  enumerates a device's host bridge MMIO windows as sorted, merged
  IOMMU_RESV_RESERVED regions.

  Patch 2 adds the IOMMU_GET_PCI_MMIO_WINDOWS ioctl to iommufd, a
  per-device query that returns these windows to userspace without
  reserving or enforcing them.

[1] https://lore.kernel.org/all/20260921115513.GK11599@ziepe.ca/

v2: https://lore.kernel.org/all/20260921103922.1113752-1-guanghuifeng@linux.alibaba.com/
v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/

Changes since v2:
- Complete redesign per Robin Murphy's and Jason Gunthorpe's review.
  v2 forced PCI window reservation through iommu_get_group_resv_regions()
  which was explicitly rejected (this was tried before and reverted by
  cd2c9fcf5c66).  v3 instead provides an opt-in query interface.
- Patch 1: add iommu_get_pci_resv_windows() as a standalone helper.
  No longer touches dma-iommu.c (the existing direct enumeration in
  iova_reserve_pci_windows() is simpler and allocation-free; refactoring
  it to use the helper would add unnecessary overhead for no functional
  benefit).
- Patch 2: new IOMMU_GET_PCI_MMIO_WINDOWS ioctl replaces the v2
  iommufd enforce-path change.  Reports windows to userspace without
  reserving them.
- io_pagetable.c and dma-iommu.c are unchanged from base.

Changes since v1:
- (Superseded by v2->v3 changes above.)

Guanghui Feng (2):
  iommu: Add iommu_get_pci_resv_windows() helper
  iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl

 drivers/iommu/iommu-priv.h              | 12 +++++
 drivers/iommu/iommu.c                   | 57 ++++++++++++++++++++++
 drivers/iommu/iommufd/device.c          | 64 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 55 +++++++++++++++++++++
 6 files changed, 192 insertions(+)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper
  2026-10-04 16:22       ` [PATCH v3 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
@ 2026-10-04 16:22         ` Guanghui Feng
  2026-10-22 19:35           ` Jason Gunthorpe
  2026-10-04 16:22         ` [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
  1 sibling, 1 reply; 24+ messages in thread
From: Guanghui Feng @ 2026-10-04 16:22 UTC (permalink / raw)
  To: jgg
  Cc: alex, guanghuifeng, iommu, joro, kevin.tian, kvm, linux-kernel,
	robin.murphy, will

Add a common helper that walks a PCI device's host bridge MMIO windows
and returns them as IOMMU_RESV_RESERVED regions in a list.  This
provides a reusable interface for subsystems that need visibility into
PCI host bridge MMIO address ranges, such as iommufd which wants to
expose these ranges to userspace for opt-in IOVA avoidance.

The helper builds its results in a local list using
iommu_insert_resv_region() so entries are sorted by start address and
overlapping same-type regions are merged.  On allocation failure the
partial list is freed internally, leaving the caller's list head
unmodified.

Note: commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
intentionally keeps PCI window reservation out of the IOMMU group-level
reserved region API.  This helper respects that boundary: it does not
inject windows into iommu_get_group_resv_regions().  Consumers that need
this information call the helper directly.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommu-priv.h | 12 ++++++++
 drivers/iommu/iommu.c      | 57 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+)

diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index aaffad5854fc..17f2234dabd4 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -123,4 +123,16 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+struct pci_dev;
+
+#ifdef CONFIG_PCI
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head);
+#else
+static inline int iommu_get_pci_resv_windows(struct pci_dev *dev,
+					     struct list_head *head)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..25bc99114e1e 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,63 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_get_pci_resv_windows - collect PCI host bridge MMIO windows as
+ *                               reserved regions
+ * @dev: PCI device whose host bridge to scan
+ * @head: list head to append iommu_resv_region entries to
+ *
+ * Walks the MMIO windows of @dev's PCI host bridge and inserts an
+ * IOMMU_RESV_RESERVED region for each one using iommu_insert_resv_region(),
+ * keeping the entries sorted by start address and merging overlapping
+ * regions of the same type.  On success the inserted entries are appended
+ * to @head and the caller must free them with kfree() when done.  On
+ * failure any entries built so far are freed internally and @head is left
+ * unmodified.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct iommu_resv_region *region, *next;
+	struct resource_entry *window;
+	LIST_HEAD(resv_windows);
+	int ret;
+
+	if (!bridge)
+		return -ENODEV;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		struct iommu_resv_region tmp = {
+			.type = IOMMU_RESV_RESERVED,
+		};
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		tmp.start = window->res->start - window->offset;
+		tmp.length = window->res->end - window->res->start + 1;
+
+		ret = iommu_insert_resv_region(&tmp, &resv_windows);
+		if (ret)
+			goto err_free;
+	}
+
+	list_splice_tail(&resv_windows, head);
+	return 0;
+
+err_free:
+	list_for_each_entry_safe(region, next, &resv_windows, list) {
+		list_del(&region->list);
+		kfree(region);
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_get_pci_resv_windows);
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl
  2026-10-04 16:22       ` [PATCH v3 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
  2026-10-04 16:22         ` [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper Guanghui Feng
@ 2026-10-04 16:22         ` Guanghui Feng
  2026-10-22 19:33           ` Jason Gunthorpe
  1 sibling, 1 reply; 24+ messages in thread
From: Guanghui Feng @ 2026-10-04 16:22 UTC (permalink / raw)
  To: jgg
  Cc: alex, guanghuifeng, iommu, joro, kevin.tian, kvm, linux-kernel,
	robin.murphy, will

Add a new per-device query ioctl that reports the PCI host bridge MMIO
windows behind a given device.  This allows userspace (e.g. QEMU) to
learn which address ranges a PCIe switch might misinterpret as
peer-to-peer DMA targets, and voluntarily keep its IOVA allocations away
from them.

The kernel does NOT reserve or enforce these ranges -- it only reports
them.  Userspace can choose to opt into avoiding these windows based on
its own topology knowledge and requirements.  This follows the principle
established by commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
that PCI window information should not be forced through the IOMMU
reserved-region API onto userspace.

The ioctl uses the standard "user provides array + count, kernel fills
and returns actual count, -EMSGSIZE if too small" pattern established by
IOMMU_IOAS_IOVA_RANGES.  Per-device granularity is chosen because PCI
host bridge windows are a property of the device's bus topology and are
available immediately after VFIO_DEVICE_BIND_IOMMUFD, before any IOAS
attachment.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/device.c          | 64 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 55 +++++++++++++++++++++
 4 files changed, 123 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index a664c70a6fe7..74a0642ff4fb 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1747,3 +1747,67 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
 	iommufd_put_object(ucmd->ictx, &idev->obj);
 	return rc;
 }
+
+int iommufd_device_get_pci_mmio_windows(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_pci_mmio_window __user *windows;
+	struct iommu_pci_mmio_windows *cmd = ucmd->cmd;
+	struct iommu_resv_region *resv, *next;
+	struct iommufd_device *idev;
+	LIST_HEAD(resv_windows);
+	u32 max_windows;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	idev = iommufd_get_device(ucmd, cmd->dev_id);
+	if (IS_ERR(idev))
+		return PTR_ERR(idev);
+
+	if (!dev_is_pci(idev->dev)) {
+		rc = -EOPNOTSUPP;
+		goto out_put;
+	}
+
+	rc = iommu_get_pci_resv_windows(to_pci_dev(idev->dev), &resv_windows);
+	if (rc)
+		goto out_put;
+
+	max_windows = cmd->num_windows;
+	windows = u64_to_user_ptr(cmd->windows);
+	cmd->num_windows = 0;
+	list_for_each_entry(resv, &resv_windows, list) {
+		if (!resv->length)
+			continue;
+
+		if (cmd->num_windows < max_windows) {
+			struct iommu_pci_mmio_window elm = {
+				.start = resv->start,
+				.last = resv->start + resv->length - 1,
+			};
+
+			if (copy_to_user(&windows[cmd->num_windows], &elm,
+					 sizeof(elm))) {
+				rc = -EFAULT;
+				goto out_free;
+			}
+		}
+		cmd->num_windows++;
+	}
+
+	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+	if (rc)
+		goto out_free;
+	if (cmd->num_windows > max_windows)
+		rc = -EMSGSIZE;
+
+out_free:
+	list_for_each_entry_safe(resv, next, &resv_windows, list) {
+		list_del(&resv->list);
+		kfree(resv);
+	}
+out_put:
+	iommufd_put_object(ucmd->ictx, &idev->obj);
+	return rc;
+}
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eb2e85b27e42..557f205e7836 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -535,6 +535,7 @@ iommufd_device_get_iommu_dev(struct iommufd_device *idev)
 void iommufd_device_pre_destroy(struct iommufd_object *obj);
 void iommufd_device_destroy(struct iommufd_object *obj);
 int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
+int iommufd_device_get_pci_mmio_windows(struct iommufd_ucmd *ucmd);
 
 struct device *iommufd_global_device(void);
 
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 9a921b153162..0d1be82c1ddd 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -449,6 +449,7 @@ union ucmd_buffer {
 	struct iommu_ioas_map map;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
+	struct iommu_pci_mmio_windows pci_mmio_windows;
 	struct iommu_vdevice_alloc vdev;
 	struct iommu_veventq_alloc veventq;
 	struct iommu_vfio_ioas vfio_ioas;
@@ -480,6 +481,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_fault_alloc, out_fault_fd),
 	IOCTL_OP(IOMMU_GET_HW_INFO, iommufd_get_hw_info, struct iommu_hw_info,
 		 __reserved),
+	IOCTL_OP(IOMMU_GET_PCI_MMIO_WINDOWS, iommufd_device_get_pci_mmio_windows,
+		 struct iommu_pci_mmio_windows, windows),
 	IOCTL_OP(IOMMU_HW_QUEUE_ALLOC, iommufd_hw_queue_alloc_ioctl,
 		 struct iommu_hw_queue_alloc, length),
 	IOCTL_OP(IOMMU_HWPT_ALLOC, iommufd_hwpt_alloc, struct iommu_hwpt_alloc,
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..5707eb6130ee 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -58,6 +58,7 @@ enum {
 	IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
+	IOMMUFD_CMD_GET_PCI_MMIO_WINDOWS = 0x96,
 };
 
 /**
@@ -1390,4 +1391,58 @@ struct iommu_hw_queue_alloc {
 	__aligned_u64 length;
 };
 #define IOMMU_HW_QUEUE_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HW_QUEUE_ALLOC)
+
+/**
+ * struct iommu_pci_mmio_window - a PCI host bridge MMIO window
+ * @start: First PCI bus address of the window
+ * @last: Inclusive last PCI bus address of the window
+ *
+ * A memory window claimed by the PCI host bridge that a device sits
+ * behind.  Addresses are PCI bus addresses (i.e. what appears in the
+ * TLP), which differ from CPU physical addresses on platforms where the
+ * host bridge applies an address translation offset.  For a device
+ * using an identity (1:1) IOVA layout the IOVA equals the bus address,
+ * so these are the IOVAs that a PCIe switch could misinterpret as
+ * peer-to-peer DMA and route to the wrong device instead of memory.
+ */
+struct iommu_pci_mmio_window {
+	__aligned_u64 start;
+	__aligned_u64 last;
+};
+
+/**
+ * struct iommu_pci_mmio_windows - ioctl(IOMMU_GET_PCI_MMIO_WINDOWS)
+ * @size: sizeof(struct iommu_pci_mmio_windows)
+ * @flags: Must be 0
+ * @dev_id: The device bound to iommufd to query
+ * @num_windows: Input/output number of MMIO windows
+ * @windows: Pointer to the output array of struct iommu_pci_mmio_window
+ *
+ * Query the memory windows of the PCI host bridge that @dev_id sits
+ * behind.  This is advisory: the kernel does not reserve these ranges, it
+ * only reports them so userspace can choose to keep its IOVA allocations
+ * away from them and avoid accidental peer-to-peer DMA routing.  The
+ * reported windows are pessimistic (the whole host bridge window, not the
+ * precise BAR ranges that may actually conflict).  If a IOAS has devices
+ * behind multiple host bridges, query each device and take the union.
+ *
+ * On input num_windows is the length of the windows array.  On output it
+ * is the total number of windows.  The ioctl will return -EMSGSIZE and set
+ * num_windows to the required value if num_windows is too small.  In this
+ * case the caller should allocate a larger output array and re-issue the
+ * ioctl.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if @dev_id is not a PCI device,
+ * -ENOENT if @dev_id is invalid, -EMSGSIZE if the output array is too
+ * small.
+ */
+struct iommu_pci_mmio_windows {
+	__u32 size;
+	__u32 flags;
+	__u32 dev_id;
+	__u32 num_windows;
+	__aligned_u64 windows;
+};
+#define IOMMU_GET_PCI_MMIO_WINDOWS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_PCI_MMIO_WINDOWS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl
  2026-10-04 16:22         ` [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
@ 2026-10-22 19:33           ` Jason Gunthorpe
  2026-10-25 13:29             ` [PATCH v4 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
  0 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-10-22 19:33 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

On Mon, Oct 05, 2026 at 12:22:13AM +0800, Guanghui Feng wrote:
> +/**
> + * struct iommu_pci_mmio_window - a PCI host bridge MMIO window
> + * @start: First PCI bus address of the window
> + * @last: Inclusive last PCI bus address of the window
> + *
> + * A memory window claimed by the PCI host bridge that a device sits
> + * behind.  Addresses are PCI bus addresses (i.e. what appears in the
> + * TLP), which differ from CPU physical addresses on platforms where the
> + * host bridge applies an address translation offset.  For a device
> + * using an identity (1:1) IOVA layout the IOVA equals the bus address,
> + * so these are the IOVAs that a PCIe switch could misinterpret as
> + * peer-to-peer DMA and route to the wrong device instead of memory.

iommufd has no way to use devices that are not 1:1, don't talk about
this stuff in the kdocs.

> +/**
> + * struct iommu_pci_mmio_windows - ioctl(IOMMU_GET_PCI_MMIO_WINDOWS)
> + * @size: sizeof(struct iommu_pci_mmio_windows)
> + * @flags: Must be 0
> + * @dev_id: The device bound to iommufd to query
> + * @num_windows: Input/output number of MMIO windows
> + * @windows: Pointer to the output array of struct iommu_pci_mmio_window
> + *
> + * Query the memory windows of the PCI host bridge that @dev_id sits
> + * behind.  This is advisory: the kernel does not reserve these ranges, it
> + * only reports them so userspace can choose to keep its IOVA allocations
> + * away from them and avoid accidental peer-to-peer DMA routing.  The
> + * reported windows are pessimistic (the whole host bridge window, not the
> + * precise BAR ranges that may actually conflict).  If a IOAS has devices
> + * behind multiple host bridges, query each device and take the union.

State clearly in the kdoc if the list is strictly non-overlapping, or
the list can have overlapping ranges. It makes a big difference to how
userspace has to process things.

> + * On input num_windows is the length of the windows array.  On output it
> + * is the total number of windows.  The ioctl will return -EMSGSIZE and set
> + * num_windows to the required value if num_windows is too small.  In this
> + * case the caller should allocate a larger output array and re-issue the
> + * ioctl.
> + *
> + * Return: 0 on success, -EOPNOTSUPP if @dev_id is not a PCI device,
> + * -ENOENT if @dev_id is invalid, -EMSGSIZE if the output array is too
> + * small.
> + */
> +struct iommu_pci_mmio_windows {
> +	__u32 size;
> +	__u32 flags;
> +	__u32 dev_id;
> +	__u32 num_windows;
> +	__aligned_u64 windows;

IIRC out_windows is the convention in iommufd

Jason

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper
  2026-10-04 16:22         ` [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper Guanghui Feng
@ 2026-10-22 19:35           ` Jason Gunthorpe
  0 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-10-22 19:35 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

On Mon, Oct 05, 2026 at 12:22:12AM +0800, Guanghui Feng wrote:

> +int iommu_get_pci_resv_windows(struct pci_dev *dev, struct list_head *head)
> +{
> +	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
> +	struct iommu_resv_region *region, *next;
> +	struct resource_entry *window;
> +	LIST_HEAD(resv_windows);
> +	int ret;
> +
> +	if (!bridge)
> +		return -ENODEV;
> +
> +	resource_list_for_each_entry(window, &bridge->windows) {
> +		struct iommu_resv_region tmp = {
> +			.type = IOMMU_RESV_RESERVED,
> +		};

I didn't try to look, but if you are going to add a function to the
iommu core then dma-iommu.c should also use it so we are sharing the
same algorithm.

Maybe a callback function would work better to that end than returning
a linked list?

Jason

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v4 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance
  2026-10-22 19:33           ` Jason Gunthorpe
@ 2026-10-25 13:29             ` Guanghui Feng
  2026-10-25 13:29               ` [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
  2026-10-25 13:30               ` [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
  0 siblings, 2 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-25 13:29 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

When a device sits behind a PCIe switch and ACS Upstream Forwarding is
not fully enabled, DMA TLPs whose IOVA happens to fall within a host
bridge MMIO window may be routed peer-to-peer to another downstream
device instead of upstream to the root complex for IOMMU translation.
This can lead to faults, data corruption, or silent misrouting.

The DMA IOVA layer already avoids this via iova_reserve_pci_windows().
However, passthrough paths that let userspace pick IOVAs (VFIO type1,
iommufd) had no mechanism to learn about these ranges.  Commit
cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into
dma specific path.") intentionally keeps this information out of the
IOMMU reserved-region API to avoid pessimistically restricting
userspace.

Following Jason Gunthorpe's suggestion [1], this series takes an opt-in
approach: the kernel reports the information, userspace decides whether
to avoid it.

  Patch 1 adds iommu_for_each_pci_mmio_window(), a callback-based
  iterator in the IOMMU core that enumerates host bridge MMIO windows
  with zero intermediate allocation.  dma-iommu.c is refactored to use
  it, sharing the same algorithm.

  Patch 2 adds the IOMMU_GET_PCI_MMIO_WINDOWS ioctl to iommufd, a
  per-device query that returns these windows to userspace without
  reserving or enforcing them.

[1] https://lore.kernel.org/all/20260921115513.GK11599@ziepe.ca/

v3: https://lore.kernel.org/all/20261004162213.3787623-1-guanghuifeng@linux.alibaba.com/
v2: https://lore.kernel.org/all/20260921103922.1113752-1-guanghuifeng@linux.alibaba.com/
v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/

Changes since v3:
- Patch 1: redesign helper as a callback-based iterator
  (iommu_for_each_pci_mmio_window) per Jason's review [2].  This
  eliminates intermediate iommu_resv_region allocations, avoids size_t
  truncation on 32-bit PAE (callback receives phys_addr_t directly),
  and allows dma-iommu.c to share the same algorithm with zero overhead.
- Patch 1: dma-iommu.c now uses the shared iterator as Jason requested
  ("if you are going to add a function to the iommu core then
  dma-iommu.c should also use it").
- Patch 2: rename 'windows' field to 'out_windows' per iommufd
  convention.
- Patch 2: remove irrelevant 1:1 IOVA discussion from kdoc (iommufd
  only supports identity-mapped devices).
- Patch 2: explicitly state that windows are reported in host bridge
  resource list order, typically non-overlapping but no sorting
  guarantee.
- Patch 2: handler simplified to use callback directly (no linked list
  allocation/cleanup needed).

[2] https://lore.kernel.org/all/20261022193527.GG3381610@ziepe.ca/

Changes since v2:
- Complete redesign from forced reservation to opt-in query (per Robin
  Murphy's and Jason Gunthorpe's v2 review).

Changes since v1:
- (Superseded by later changes.)

Guanghui Feng (2):
  iommu: Add iommu_for_each_pci_mmio_window() iterator
  iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl

 drivers/iommu/dma-iommu.c               | 29 +++++++----
 drivers/iommu/iommu-priv.h              | 25 ++++++++++
 drivers/iommu/iommu.c                   | 51 ++++++++++++++++++++
 drivers/iommu/iommufd/device.c          | 64 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 59 +++++++++++++++++++++++
 7 files changed, 224 insertions(+), 8 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator
  2026-10-25 13:29             ` [PATCH v4 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
@ 2026-10-25 13:29               ` Guanghui Feng
  2026-10-27 18:29                 ` Jason Gunthorpe
  2026-10-25 13:30               ` [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
  1 sibling, 1 reply; 24+ messages in thread
From: Guanghui Feng @ 2026-10-25 13:29 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a callback-based iterator that walks a PCI device's host bridge
MMIO windows and invokes a caller-supplied function with each window's
bus address range.  This consolidates the bridge window enumeration
logic into a single shared algorithm in the IOMMU core.

Refactor iova_reserve_pci_windows() to use the new iterator, replacing
its open-coded resource_list_for_each_entry loop.  The callback approach
avoids intermediate allocations: the DMA path's callback directly calls
reserve_iova() with the computed PFN range, maintaining the same
zero-allocation overhead as the original open-coded loop.

Using a callback rather than returning a linked list of iommu_resv_region
entries eliminates size_t truncation risk on 32-bit PAE systems (the
callback receives phys_addr_t start/end directly) and removes the need
for allocation failure cleanup paths.

Note: commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
intentionally keeps PCI window reservation out of the IOMMU group-level
reserved region API.  This iterator respects that boundary: it does not
inject windows into iommu_get_group_resv_regions().

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/dma-iommu.c  | 29 ++++++++++++++++------
 drivers/iommu/iommu-priv.h | 25 +++++++++++++++++++
 drivers/iommu/iommu.c      | 51 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 68ee57800fef..d4e4a4c90d8b 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -37,6 +37,7 @@
 
 #include "dma-iommu.h"
 #include "iommu-pages.h"
+#include "iommu-priv.h"
 
 struct iommu_dma_msi_page {
 	struct list_head	list;
@@ -504,6 +505,20 @@ static int iommu_dma_ranges_sort(void *priv, const struct list_head *a,
 	return res_a->res->start > res_b->res->start;
 }
 
+struct iova_pci_resv_data {
+	struct iova_domain *iovad;
+};
+
+static int iova_reserve_pci_window_fn(phys_addr_t start, phys_addr_t end,
+				      void *data)
+{
+	struct iova_pci_resv_data *d = data;
+
+	reserve_iova(d->iovad, iova_pfn(d->iovad, start),
+		     iova_pfn(d->iovad, end));
+	return 0;
+}
+
 static int iova_reserve_pci_windows(struct pci_dev *dev,
 		struct iova_domain *iovad)
 {
@@ -511,15 +526,13 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
 	struct resource_entry *window;
 	unsigned long lo, hi;
 	phys_addr_t start = 0, end;
+	struct iova_pci_resv_data data = { .iovad = iovad };
+	int ret;
 
-	resource_list_for_each_entry(window, &bridge->windows) {
-		if (resource_type(window->res) != IORESOURCE_MEM)
-			continue;
-
-		lo = iova_pfn(iovad, window->res->start - window->offset);
-		hi = iova_pfn(iovad, window->res->end - window->offset);
-		reserve_iova(iovad, lo, hi);
-	}
+	ret = iommu_for_each_pci_mmio_window(dev, iova_reserve_pci_window_fn,
+					     &data);
+	if (ret)
+		return ret;
 
 	/* Get reserved DMA windows from host bridge */
 	list_sort(NULL, &bridge->dma_ranges, iommu_dma_ranges_sort);
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index aaffad5854fc..36a2e4691455 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -123,4 +123,29 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+struct pci_dev;
+
+/**
+ * iommu_pci_mmio_window_fn_t - callback for host bridge MMIO window iteration
+ * @start: First PCI bus address of the window (inclusive)
+ * @end: Last PCI bus address of the window (inclusive)
+ * @data: Opaque pointer from iommu_for_each_pci_mmio_window() caller
+ *
+ * Return: 0 to continue iteration, negative errno to stop and propagate.
+ */
+typedef int (*iommu_pci_mmio_window_fn_t)(phys_addr_t start, phys_addr_t end,
+					  void *data);
+
+#ifdef CONFIG_PCI
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data);
+#else
+static inline int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+						 iommu_pci_mmio_window_fn_t fn,
+						 void *data)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..ec66118d13e9 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,57 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_for_each_pci_mmio_window - iterate over PCI host bridge MMIO windows
+ * @dev: PCI device whose host bridge to scan
+ * @fn: callback invoked with each window's bus address range
+ * @data: opaque pointer passed to @fn
+ *
+ * Walks the MEM windows of @dev's PCI host bridge and invokes
+ * @fn(start, end, data) for each one, where start and end are PCI bus
+ * addresses (inclusive).  Iteration stops early if @fn returns non-zero,
+ * and that value is propagated to the caller.
+ *
+ * This provides a zero-allocation way for both the DMA IOVA layer and
+ * iommufd to enumerate the same host bridge window information using a
+ * shared algorithm.
+ *
+ * Note: commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
+ * intentionally keeps PCI window reservation out of the IOMMU group-level
+ * reserved region API.  This iterator respects that boundary: it does not
+ * inject windows into iommu_get_group_resv_regions().
+ *
+ * Return: 0 on success, negative errno on failure (including from @fn).
+ */
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct resource_entry *window;
+
+	if (!bridge)
+		return -ENODEV;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		phys_addr_t start, end;
+		int ret;
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		start = window->res->start - window->offset;
+		end = window->res->end - window->offset;
+
+		ret = fn(start, end, data);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iommu_for_each_pci_mmio_window);
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl
  2026-10-25 13:29             ` [PATCH v4 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
  2026-10-25 13:29               ` [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
@ 2026-10-25 13:30               ` Guanghui Feng
  2026-10-27 18:34                 ` Jason Gunthorpe
  1 sibling, 1 reply; 24+ messages in thread
From: Guanghui Feng @ 2026-10-25 13:30 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a per-device query ioctl that reports the PCI host bridge MMIO
windows behind a given device.  This allows userspace (e.g. QEMU) to
learn which address ranges a PCIe switch might misinterpret as
peer-to-peer DMA targets, and voluntarily keep its IOVA allocations
away from them.

The kernel does NOT reserve or enforce these ranges -- it only reports
them.  Userspace can choose to opt into avoiding these windows based on
its own topology knowledge and requirements.  This follows the principle
established by commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
that PCI window information should not be forced through the IOMMU
reserved-region API onto userspace.

The ioctl uses the standard "user provides array + count, kernel fills
and returns actual count, -EMSGSIZE if too small" pattern established by
IOMMU_IOAS_IOVA_RANGES.  Per-device granularity is chosen because PCI
host bridge windows are a property of the device's bus topology and are
available immediately after VFIO_DEVICE_BIND_IOMMUFD, before any IOAS
attachment.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/device.c          | 64 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 59 +++++++++++++++++++++++
 4 files changed, 127 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 1b0bdf57ac8a..91d433823c51 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1749,3 +1749,67 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
 	iommufd_put_object(ucmd->ictx, &idev->obj);
 	return rc;
 }
+
+struct pci_mmio_windows_ctx {
+	struct iommu_pci_mmio_window __user *out_windows;
+	u32 max_windows;
+	u32 num_windows;
+};
+
+static int pci_mmio_window_fn(phys_addr_t start, phys_addr_t end, void *data)
+{
+	struct pci_mmio_windows_ctx *ctx = data;
+
+	if (ctx->num_windows < ctx->max_windows) {
+		struct iommu_pci_mmio_window elm = {
+			.start = start,
+			.last = end,
+		};
+
+		if (copy_to_user(&ctx->out_windows[ctx->num_windows], &elm,
+				 sizeof(elm)))
+			return -EFAULT;
+	}
+	ctx->num_windows++;
+	return 0;
+}
+
+int iommufd_device_get_pci_mmio_windows(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_pci_mmio_windows *cmd = ucmd->cmd;
+	struct pci_mmio_windows_ctx ctx;
+	struct iommufd_device *idev;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	idev = iommufd_get_device(ucmd, cmd->dev_id);
+	if (IS_ERR(idev))
+		return PTR_ERR(idev);
+
+	if (!dev_is_pci(idev->dev)) {
+		rc = -EOPNOTSUPP;
+		goto out_put;
+	}
+
+	ctx.out_windows = u64_to_user_ptr(cmd->out_windows);
+	ctx.max_windows = cmd->num_windows;
+	ctx.num_windows = 0;
+
+	rc = iommu_for_each_pci_mmio_window(to_pci_dev(idev->dev),
+					    pci_mmio_window_fn, &ctx);
+	if (rc)
+		goto out_put;
+
+	cmd->num_windows = ctx.num_windows;
+	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+	if (rc)
+		goto out_put;
+	if (ctx.num_windows > ctx.max_windows)
+		rc = -EMSGSIZE;
+
+out_put:
+	iommufd_put_object(ucmd->ictx, &idev->obj);
+	return rc;
+}
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eb2e85b27e42..557f205e7836 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -535,6 +535,7 @@ iommufd_device_get_iommu_dev(struct iommufd_device *idev)
 void iommufd_device_pre_destroy(struct iommufd_object *obj);
 void iommufd_device_destroy(struct iommufd_object *obj);
 int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
+int iommufd_device_get_pci_mmio_windows(struct iommufd_ucmd *ucmd);
 
 struct device *iommufd_global_device(void);
 
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 9a921b153162..7b8ecdd45b76 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -449,6 +449,7 @@ union ucmd_buffer {
 	struct iommu_ioas_map map;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
+	struct iommu_pci_mmio_windows pci_mmio_windows;
 	struct iommu_vdevice_alloc vdev;
 	struct iommu_veventq_alloc veventq;
 	struct iommu_vfio_ioas vfio_ioas;
@@ -480,6 +481,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_fault_alloc, out_fault_fd),
 	IOCTL_OP(IOMMU_GET_HW_INFO, iommufd_get_hw_info, struct iommu_hw_info,
 		 __reserved),
+	IOCTL_OP(IOMMU_GET_PCI_MMIO_WINDOWS, iommufd_device_get_pci_mmio_windows,
+		 struct iommu_pci_mmio_windows, out_windows),
 	IOCTL_OP(IOMMU_HW_QUEUE_ALLOC, iommufd_hw_queue_alloc_ioctl,
 		 struct iommu_hw_queue_alloc, length),
 	IOCTL_OP(IOMMU_HWPT_ALLOC, iommufd_hwpt_alloc, struct iommu_hwpt_alloc,
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 010f47f7d3d2..c4a56a3dc6f9 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -58,6 +58,7 @@ enum {
 	IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
+	IOMMUFD_CMD_GET_PCI_MMIO_WINDOWS = 0x96,
 };
 
 /**
@@ -1398,4 +1399,62 @@ struct iommu_hw_queue_alloc {
 	__aligned_u64 length;
 };
 #define IOMMU_HW_QUEUE_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HW_QUEUE_ALLOC)
+
+/**
+ * struct iommu_pci_mmio_window - a PCI host bridge MMIO window
+ * @start: First PCI bus address of the window
+ * @last: Inclusive last PCI bus address of the window
+ *
+ * A memory window claimed by the PCI host bridge that a device sits
+ * behind.  These are PCI bus addresses (what appears in the TLP).
+ * A PCIe switch could misinterpret DMA to these addresses as
+ * peer-to-peer traffic and route it to the wrong device.
+ */
+struct iommu_pci_mmio_window {
+	__aligned_u64 start;
+	__aligned_u64 last;
+};
+
+/**
+ * struct iommu_pci_mmio_windows - ioctl(IOMMU_GET_PCI_MMIO_WINDOWS)
+ * @size: sizeof(struct iommu_pci_mmio_windows)
+ * @flags: Must be 0
+ * @dev_id: The device bound to iommufd to query
+ * @num_windows: Input/output number of MMIO windows
+ * @out_windows: Pointer to the output array of struct iommu_pci_mmio_window
+ *
+ * Query the memory windows of the PCI host bridge that @dev_id sits
+ * behind.  This is advisory: the kernel does not reserve these ranges,
+ * it only reports them so userspace can choose to keep its IOVA
+ * allocations away from them and avoid accidental peer-to-peer DMA
+ * routing.  The reported windows are pessimistic (the whole host bridge
+ * window, not the precise BAR ranges that may actually conflict).
+ *
+ * Windows are reported in host bridge resource list order.  They are
+ * typically non-overlapping (host bridge address decoders are exclusive)
+ * but no sorting guarantee is made.  Userspace should treat them as a
+ * set and compute the union if needed.
+ *
+ * If an IOAS has devices behind multiple host bridges, query each device
+ * and take the union of the results.
+ *
+ * On input num_windows is the length of the out_windows array.  On
+ * output it is the total number of windows.  The ioctl will return
+ * -EMSGSIZE and set num_windows to the required value if num_windows is
+ * too small.  In this case the caller should allocate a larger output
+ * array and re-issue the ioctl.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if @dev_id is not a PCI device,
+ * -ENOENT if @dev_id is invalid, -EMSGSIZE if the output array is too
+ * small.
+ */
+struct iommu_pci_mmio_windows {
+	__u32 size;
+	__u32 flags;
+	__u32 dev_id;
+	__u32 num_windows;
+	__aligned_u64 out_windows;
+};
+#define IOMMU_GET_PCI_MMIO_WINDOWS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_PCI_MMIO_WINDOWS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator
  2026-10-25 13:29               ` [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
@ 2026-10-27 18:29                 ` Jason Gunthorpe
  0 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-10-27 18:29 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

On Sun, Oct 25, 2026 at 09:29:59PM +0800, Guanghui Feng wrote:
> Add a callback-based iterator that walks a PCI device's host bridge
> MMIO windows and invokes a caller-supplied function with each window's
> bus address range.  This consolidates the bridge window enumeration
> logic into a single shared algorithm in the IOMMU core.
> 
> Refactor iova_reserve_pci_windows() to use the new iterator, replacing
> its open-coded resource_list_for_each_entry loop.  The callback approach
> avoids intermediate allocations: the DMA path's callback directly calls
> reserve_iova() with the computed PFN range, maintaining the same
> zero-allocation overhead as the original open-coded loop.
> 
> Using a callback rather than returning a linked list of iommu_resv_region
> entries eliminates size_t truncation risk on 32-bit PAE systems (the
> callback receives phys_addr_t start/end directly) and removes the need
> for allocation failure cleanup paths.
> 
> Note: commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
> intentionally keeps PCI window reservation out of the IOMMU group-level
> reserved region API.  This iterator respects that boundary: it does not
> inject windows into iommu_get_group_resv_regions().
> 
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> ---
>  drivers/iommu/dma-iommu.c  | 29 ++++++++++++++++------
>  drivers/iommu/iommu-priv.h | 25 +++++++++++++++++++
>  drivers/iommu/iommu.c      | 51 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 97 insertions(+), 8 deletions(-)

Seems OK enough

> @@ -504,6 +505,20 @@ static int iommu_dma_ranges_sort(void *priv, const struct list_head *a,
>  	return res_a->res->start > res_b->res->start;
>  }
>  
> +struct iova_pci_resv_data {
> +	struct iova_domain *iovad;
> +};

We don't need a struct to pass a single pointer

> +struct pci_dev;
> +
> +/**
> + * iommu_pci_mmio_window_fn_t - callback for host bridge MMIO window iteration
> + * @start: First PCI bus address of the window (inclusive)
> + * @end: Last PCI bus address of the window (inclusive)
> + * @data: Opaque pointer from iommu_for_each_pci_mmio_window() caller
> + *
> + * Return: 0 to continue iteration, negative errno to stop and propagate.
> + */
> +typedef int (*iommu_pci_mmio_window_fn_t)(phys_addr_t start, phys_addr_t end,
> +					  void *data);

Don't really need a comment here

> +#ifdef CONFIG_PCI
> +/**
> + * iommu_for_each_pci_mmio_window - iterate over PCI host bridge MMIO windows
> + * @dev: PCI device whose host bridge to scan
> + * @fn: callback invoked with each window's bus address range
> + * @data: opaque pointer passed to @fn
> + *
> + * Walks the MEM windows of @dev's PCI host bridge and invokes
> + * @fn(start, end, data) for each one, where start and end are PCI bus
> + * addresses (inclusive).  Iteration stops early if @fn returns non-zero,
> + * and that value is propagated to the caller.
> + *
> + * This provides a zero-allocation way for both the DMA IOVA layer and
> + * iommufd to enumerate the same host bridge window information using a
> + * shared algorithm.
> + *
> + * Note: commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
> + * intentionally keeps PCI window reservation out of the IOMMU group-level
> + * reserved region API.  This iterator respects that boundary: it does not
> + * inject windows into iommu_get_group_resv_regions().

The last two pargraphs are not very useful

> + *
> + * Return: 0 on success, negative errno on failure (including from @fn).
> + */
> +int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
> +				   iommu_pci_mmio_window_fn_t fn, void *data)
> +{
> +	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
> +	struct resource_entry *window;
> +
> +	if (!bridge)
> +		return -ENODEV;

Can it fail? The existing code doesn't check it so I guess not. Stick
a WARN_ON there if you want to keep it.

> +
> +	resource_list_for_each_entry(window, &bridge->windows) {
> +		phys_addr_t start, end;
> +		int ret;
> +
> +		if (resource_type(window->res) != IORESOURCE_MEM)
> +			continue;
> +
> +		start = window->res->start - window->offset;
> +		end = window->res->end - window->offset;
> +
> +		ret = fn(start, end, data);

Don't think we need the start/end variable either

Jason

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl
  2026-10-25 13:30               ` [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
@ 2026-10-27 18:34                 ` Jason Gunthorpe
  2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  0 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-10-27 18:34 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

On Sun, Oct 25, 2026 at 09:30:00PM +0800, Guanghui Feng wrote:
> +
> +/**
> + * struct iommu_pci_mmio_window - a PCI host bridge MMIO window
> + * @start: First PCI bus address of the window
> + * @last: Inclusive last PCI bus address of the window
> + *
> + * A memory window claimed by the PCI host bridge that a device sits
> + * behind.  These are PCI bus addresses (what appears in the TLP).
> + * A PCIe switch could misinterpret DMA to these addresses as
> + * peer-to-peer traffic and route it to the wrong device.
> + */
> +struct iommu_pci_mmio_window {
> +	__aligned_u64 start;
> +	__aligned_u64 last;
> +};

We already have struct iommu_iova_range, don't need another one.

> +
> +/**
> + * struct iommu_pci_mmio_windows - ioctl(IOMMU_GET_PCI_MMIO_WINDOWS)
> + * @size: sizeof(struct iommu_pci_mmio_windows)
> + * @flags: Must be 0
> + * @dev_id: The device bound to iommufd to query
> + * @num_windows: Input/output number of MMIO windows
> + * @out_windows: Pointer to the output array of struct iommu_pci_mmio_window
> + *
> + * Query the memory windows of the PCI host bridge that @dev_id sits
> + * behind.  This is advisory: the kernel does not reserve these ranges,
> + * it only reports them so userspace can choose to keep its IOVA
> + * allocations away from them and avoid accidental peer-to-peer DMA
> + * routing.  The reported windows are pessimistic (the whole host bridge
> + * window, not the precise BAR ranges that may actually conflict).
> + *
> + * Windows are reported in host bridge resource list order.  They are
> + * typically non-overlapping (host bridge address decoders are exclusive)
> + * but no sorting guarantee is made.  Userspace should treat them as a
> + * set and compute the union if needed.
> + *
> + * If an IOAS has devices behind multiple host bridges, query each device
> + * and take the union of the results.
> + *
> + * On input num_windows is the length of the out_windows array.  On
> + * output it is the total number of windows.  The ioctl will return
> + * -EMSGSIZE and set num_windows to the required value if num_windows is
> + * too small.  In this case the caller should allocate a larger output
> + * array and re-issue the ioctl.
> + *
> + * Return: 0 on success, -EOPNOTSUPP if @dev_id is not a PCI device,
> + * -ENOENT if @dev_id is invalid, -EMSGSIZE if the output array is too
> + * small.
> + */
> +struct iommu_pci_mmio_windows {
> +	__u32 size;
> +	__u32 flags;
> +	__u32 dev_id;
> +	__u32 num_windows;
> +	__aligned_u64 out_windows;
> +};
> +#define IOMMU_GET_PCI_MMIO_WINDOWS \
> +	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_PCI_MMIO_WINDOWS)
>  #endif

I wonder if this should have the word PCI in it..

Maybe this is 'recommended reserved regions' or something?

This is also lacking a way to send the ranges back in so they create a
restriction for the allocator.

Jason


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation
  2026-10-27 18:34                 ` Jason Gunthorpe
@ 2026-10-28  7:19                   ` Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
                                       ` (3 more replies)
  0 siblings, 4 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  7:19 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

This series lets userspace learn which IOVA ranges the kernel
recommends avoiding for a device, and optionally feed them back so
the IOAS allocator enforces them.

The motivating case is a PCI device behind a PCIe switch: DMA using
IOVAs that fall inside the host bridge MMIO windows may be misrouted
as peer-to-peer traffic instead of reaching the root complex for IOMMU
translation.  The DMA IOVA layer already avoids these windows
internally, but passthrough userspace had no way to learn about them.
Per commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
this information must not be forced through the IOMMU reserved-region
API, so the series is fully opt-in:

  Patch 1 adds iommu_for_each_pci_mmio_window(), a callback-based
  iterator in the IOMMU core, and refactors dma-iommu.c to share it.

  Patch 2 adds IOMMU_GET_RECOMMENDED_RESERVATIONS, a per-device query
  returning recommended ranges (currently PCI host bridge MMIO
  windows) as struct iommu_iova_range entries.  Advisory only.

  Patch 3 adds IOMMU_IOAS_RESERVE_IOVAS, letting userspace send
  chosen ranges back in to restrict the IOAS allocator: reserved
  ranges disappear from IOMMU_IOAS_IOVA_RANGES and are rejected by
  mapping.  Overlapping reservations are permitted, so the same
  ranges recommended for several devices behind one host bridge can
  each be reserved independently.

Typical flow: bind device -> query recommendations -> reserve chosen
ranges -> map.  Userspace that ignores the new ioctls sees no
behaviour change.

[1] https://lore.kernel.org/all/20260921115513.GK11599@ziepe.ca/
[2] https://lore.kernel.org/all/20261027182947.GI1685504@ziepe.ca/
[3] https://lore.kernel.org/all/20261027183418.GJ1685504@ziepe.ca/

v4: https://lore.kernel.org/all/20261025133000.205508-1-guanghuifeng@linux.alibaba.com/
v3: https://lore.kernel.org/all/20261004162213.3787623-1-guanghuifeng@linux.alibaba.com/
v2: https://lore.kernel.org/all/20260921103922.1113752-1-guanghuifeng@linux.alibaba.com/
v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/

Changes since v4 (all per Jason's review [2][3]):
- Patch 1: drop the single-pointer wrapper struct in dma-iommu.c and
  pass the iova_domain directly as callback data.
- Patch 1: drop the typedef kdoc and trim the iterator kdoc to the
  essential description.
- Patch 1: WARN_ON(!bridge) instead of a silent check; inline the
  start/end temporaries.
- Patch 2: reuse struct iommu_iova_range instead of adding a new
  per-window struct.
- Patch 2: rename away from PCI-specific naming: the interface is now
  IOMMU_GET_RECOMMENDED_RESERVATIONS reporting recommended reserved
  regions, with PCI host bridge MMIO windows as the first source.
- Patch 3 (new): add IOMMU_IOAS_RESERVE_IOVAS so userspace can send
  the ranges back in to create a restriction for the allocator, as
  requested in [3].  The NULL-owner cleanup in iopt_destroy_table()
  is no longer conditional on CONFIG_IOMMUFD_TEST since production
  userspace can now create such reservations.

Changes since v3:
- Helper redesigned as a callback iterator shared with dma-iommu.c
  (v4 changes).

Changes since v2:
- Redesign from forced reservation to opt-in query (v3 changes).

Guanghui Feng (3):
  iommu: Add iommu_for_each_pci_mmio_window() iterator
  iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl
  iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl

 drivers/iommu/dma-iommu.c               | 22 +++++---
 drivers/iommu/iommu-priv.h              | 17 ++++++
 drivers/iommu/iommu.c                   | 37 +++++++++++++
 drivers/iommu/iommufd/device.c          | 65 ++++++++++++++++++++++
 drivers/iommu/iommufd/io_pagetable.c    |  7 ++-
 drivers/iommu/iommufd/ioas.c            | 49 +++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  2 +
 drivers/iommu/iommufd/main.c            |  7 +++
 include/uapi/linux/iommufd.h            | 71 +++++++++++++++++++++++++
 9 files changed, 267 insertions(+), 10 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v5 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator
  2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
@ 2026-10-28  7:19                     ` Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
                                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  7:19 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a callback-based iterator that walks a PCI device's host bridge
MMIO windows and invokes a caller-supplied function with each window's
bus address range.  This consolidates the bridge window enumeration
logic into a single shared algorithm in the IOMMU core.

Refactor iova_reserve_pci_windows() to use the new iterator, replacing
its open-coded resource_list_for_each_entry loop.  The callback
receives phys_addr_t start/end directly, so the DMA path reserves the
same ranges as before with zero intermediate allocation, and window
lengths never pass through a size_t field (which would truncate on
32-bit PAE systems).

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/dma-iommu.c  | 22 ++++++++++++++--------
 drivers/iommu/iommu-priv.h | 17 +++++++++++++++++
 drivers/iommu/iommu.c      | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 78742aeba932..5b73e71c312e 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -505,6 +505,15 @@ static int iommu_dma_ranges_sort(void *priv, const struct list_head *a,
 	return res_a->res->start > res_b->res->start;
 }
 
+static int iova_reserve_pci_window_fn(phys_addr_t start, phys_addr_t end,
+				      void *data)
+{
+	struct iova_domain *iovad = data;
+
+	reserve_iova(iovad, iova_pfn(iovad, start), iova_pfn(iovad, end));
+	return 0;
+}
+
 static int iova_reserve_pci_windows(struct pci_dev *dev,
 		struct iova_domain *iovad)
 {
@@ -512,15 +521,12 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
 	struct resource_entry *window;
 	unsigned long lo, hi;
 	phys_addr_t start = 0, end;
+	int ret;
 
-	resource_list_for_each_entry(window, &bridge->windows) {
-		if (resource_type(window->res) != IORESOURCE_MEM)
-			continue;
-
-		lo = iova_pfn(iovad, window->res->start - window->offset);
-		hi = iova_pfn(iovad, window->res->end - window->offset);
-		reserve_iova(iovad, lo, hi);
-	}
+	ret = iommu_for_each_pci_mmio_window(dev, iova_reserve_pci_window_fn,
+					     iovad);
+	if (ret)
+		return ret;
 
 	/* Get reserved DMA windows from host bridge */
 	list_sort(NULL, &bridge->dma_ranges, iommu_dma_ranges_sort);
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index 1f9f886b2c10..81c5b5ce67ec 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -127,4 +127,21 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+struct pci_dev;
+
+typedef int (*iommu_pci_mmio_window_fn_t)(phys_addr_t start, phys_addr_t end,
+					  void *data);
+
+#ifdef CONFIG_PCI
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data);
+#else
+static inline int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+						 iommu_pci_mmio_window_fn_t fn,
+						 void *data)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index b486b8bbd1fc..6b0e2b08ceba 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,43 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_for_each_pci_mmio_window - iterate over PCI host bridge MMIO windows
+ * @dev: PCI device whose host bridge to scan
+ * @fn: callback invoked with each window's bus address range (inclusive)
+ * @data: opaque pointer passed to @fn
+ *
+ * Iteration stops early if @fn returns non-zero, and that value is
+ * propagated to the caller.
+ *
+ * Return: 0 on success, negative errno on failure (including from @fn).
+ */
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct resource_entry *window;
+
+	if (WARN_ON(!bridge))
+		return -ENODEV;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		int ret;
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		ret = fn(window->res->start - window->offset,
+			 window->res->end - window->offset, data);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iommu_for_each_pci_mmio_window);
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v5 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl
  2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
@ 2026-10-28  7:19                     ` Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng
  2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  3 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  7:19 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a per-device query ioctl that reports the IOVA ranges the kernel
recommends reserving for a given device.  For PCI devices these are
the MMIO windows of the host bridge the device sits behind: DMA using
IOVAs inside those windows could be misrouted as peer-to-peer traffic
by a PCIe switch instead of reaching the root complex for IOMMU
translation.

The interface is advisory, and deliberately named after the general
concept of recommended reserved regions rather than its first source:
the kernel reports recommendations and userspace decides whether to
act on them, either by feeding them back through
IOMMU_IOAS_RESERVE_IOVAS or by avoiding the ranges in its own IOVA
allocation.  This follows the principle that PCI window information
should not be forced onto userspace through the IOMMU reserved-region
API, as established when the earlier attempt was reverted in commit
cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")

The output reuses struct iommu_iova_range and follows the array +
count + EMSGSIZE protocol established by IOMMU_IOAS_IOVA_RANGES.
Per-device granularity is chosen because the recommendations derive
from the device's bus topology and are available immediately after
VFIO_DEVICE_BIND_IOMMUFD, before any IOAS attachment.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/device.c          | 65 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  4 ++
 include/uapi/linux/iommufd.h            | 38 +++++++++++++++
 4 files changed, 108 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 1b0bdf57ac8a..eedda48012a1 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1749,3 +1749,68 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
 	iommufd_put_object(ucmd->ictx, &idev->obj);
 	return rc;
 }
+
+struct recommended_resv_ctx {
+	struct iommu_iova_range __user *out_ranges;
+	u32 max_ranges;
+	u32 num_ranges;
+};
+
+static int recommended_resv_window_fn(phys_addr_t start, phys_addr_t end,
+				      void *data)
+{
+	struct recommended_resv_ctx *ctx = data;
+
+	if (ctx->num_ranges < ctx->max_ranges) {
+		struct iommu_iova_range elm = {
+			.start = start,
+			.last = end,
+		};
+
+		if (copy_to_user(&ctx->out_ranges[ctx->num_ranges], &elm,
+				 sizeof(elm)))
+			return -EFAULT;
+	}
+	ctx->num_ranges++;
+	return 0;
+}
+
+int iommufd_device_get_recommended_reservations(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_recommended_reservations *cmd = ucmd->cmd;
+	struct recommended_resv_ctx ctx;
+	struct iommufd_device *idev;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	idev = iommufd_get_device(ucmd, cmd->dev_id);
+	if (IS_ERR(idev))
+		return PTR_ERR(idev);
+
+	if (!dev_is_pci(idev->dev)) {
+		rc = -EOPNOTSUPP;
+		goto out_put;
+	}
+
+	ctx.out_ranges = u64_to_user_ptr(cmd->out_ranges);
+	ctx.max_ranges = cmd->num_ranges;
+	ctx.num_ranges = 0;
+
+	rc = iommu_for_each_pci_mmio_window(to_pci_dev(idev->dev),
+					    recommended_resv_window_fn, &ctx);
+	if (rc)
+		goto out_put;
+
+	cmd->num_ranges = ctx.num_ranges;
+	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+	if (rc)
+		goto out_put;
+	if (ctx.num_ranges > ctx.max_ranges)
+		rc = -EMSGSIZE;
+
+out_put:
+	iommufd_put_object(ucmd->ictx, &idev->obj);
+	return rc;
+}
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eb2e85b27e42..8016c3efca78 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -534,6 +534,7 @@ iommufd_device_get_iommu_dev(struct iommufd_device *idev)
 
 void iommufd_device_pre_destroy(struct iommufd_object *obj);
 void iommufd_device_destroy(struct iommufd_object *obj);
+int iommufd_device_get_recommended_reservations(struct iommufd_ucmd *ucmd);
 int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
 
 struct device *iommufd_global_device(void);
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 9a921b153162..85f8ea154218 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -449,6 +449,7 @@ union ucmd_buffer {
 	struct iommu_ioas_map map;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
+	struct iommu_recommended_reservations recommended_reservations;
 	struct iommu_vdevice_alloc vdev;
 	struct iommu_veventq_alloc veventq;
 	struct iommu_vfio_ioas vfio_ioas;
@@ -480,6 +481,9 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_fault_alloc, out_fault_fd),
 	IOCTL_OP(IOMMU_GET_HW_INFO, iommufd_get_hw_info, struct iommu_hw_info,
 		 __reserved),
+	IOCTL_OP(IOMMU_GET_RECOMMENDED_RESERVATIONS,
+		 iommufd_device_get_recommended_reservations,
+		 struct iommu_recommended_reservations, out_ranges),
 	IOCTL_OP(IOMMU_HW_QUEUE_ALLOC, iommufd_hw_queue_alloc_ioctl,
 		 struct iommu_hw_queue_alloc, length),
 	IOCTL_OP(IOMMU_HWPT_ALLOC, iommufd_hwpt_alloc, struct iommu_hwpt_alloc,
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 010f47f7d3d2..45ba7bd7abd5 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -58,6 +58,7 @@ enum {
 	IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
+	IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS = 0x96,
 };
 
 /**
@@ -1398,4 +1399,41 @@ struct iommu_hw_queue_alloc {
 	__aligned_u64 length;
 };
 #define IOMMU_HW_QUEUE_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HW_QUEUE_ALLOC)
+
+/**
+ * struct iommu_recommended_reservations - ioctl(IOMMU_GET_RECOMMENDED_RESERVATIONS)
+ * @size: sizeof(struct iommu_recommended_reservations)
+ * @flags: Must be 0
+ * @dev_id: The device bound to iommufd to query
+ * @num_ranges: Input/output number of recommended ranges
+ * @out_ranges: Pointer to the output array of struct iommu_iova_range
+ *
+ * Query the IOVA ranges the kernel recommends reserving for @dev_id.
+ * For a PCI device these are the MMIO windows of its host bridge,
+ * where a PCIe switch could misroute DMA as peer-to-peer traffic.
+ * Advisory only: userspace may feed the ranges back through
+ * IOMMU_IOAS_RESERVE_IOVAS or handle them itself.
+ *
+ * Ranges are reported in resource order, not guaranteed sorted, and
+ * should be treated as a set.  Query each device and take the union
+ * when multiple devices are in use.
+ *
+ * On input num_ranges is the length of the out_ranges array.  On
+ * output it is the total number of ranges.  The ioctl will return
+ * -EMSGSIZE and set num_ranges to the required value if num_ranges is
+ * too small.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device has no
+ * recommendations, -ENOENT if @dev_id is invalid, -EMSGSIZE if the
+ * output array is too small.
+ */
+struct iommu_recommended_reservations {
+	__u32 size;
+	__u32 flags;
+	__u32 dev_id;
+	__u32 num_ranges;
+	__aligned_u64 out_ranges;
+};
+#define IOMMU_GET_RECOMMENDED_RESERVATIONS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v5 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl
  2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
  2026-10-28  7:19                     ` [PATCH v5 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
@ 2026-10-28  7:19                     ` Guanghui Feng
  2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  3 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  7:19 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add an ioctl allowing userspace to reserve IOVA ranges in an IOAS,
creating a restriction on the address space: reserved ranges are
excluded from IOMMU_IOAS_IOVA_RANGES and rejected by mapping
operations.

This completes the opt-in flow for the kernel's reservation
recommendations: userspace queries IOMMU_GET_RECOMMENDED_RESERVATIONS
for a device, decides which ranges to honour, and feeds them back
through this ioctl so the kernel enforces them on its behalf.  Both
steps are optional; nothing changes for userspace that does not use
them.

The semantics mirror the existing internal users of
iopt_reserve_iova(): ranges conflicting with existing mappings or
allowed IOVAs fail with -EADDRINUSE, and overlapping reservations are
permitted so that recommendations queried for several devices behind
the same host bridge can each be reserved independently.  User
reservations carry a NULL owner and live until the IOAS is destroyed;
accordingly the NULL-owner cleanup in iopt_destroy_table() is no
longer conditional on CONFIG_IOMMUFD_TEST, since production userspace
can now create such reservations.

On failure the leading ranges already inserted stay reserved; because
overlaps are permitted, retrying the whole call is idempotent.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/io_pagetable.c    |  7 +++-
 drivers/iommu/iommufd/ioas.c            | 49 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 33 +++++++++++++++++
 5 files changed, 91 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 5ac736cf5bb5..9a10daf6491e 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -1036,8 +1036,11 @@ void iopt_destroy_table(struct io_pagetable *iopt)
 {
 	struct interval_tree_node *node;
 
-	if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
-		iopt_remove_reserved_iova(iopt, NULL);
+	/*
+	 * Reservations made by userspace through IOMMU_IOAS_RESERVE_IOVAS
+	 * carry a NULL owner and live until the IOAS is destroyed.
+	 */
+	iopt_remove_reserved_iova(iopt, NULL);
 
 	while ((node = interval_tree_iter_first(&iopt->allowed_itree, 0,
 						ULONG_MAX))) {
diff --git a/drivers/iommu/iommufd/ioas.c b/drivers/iommu/iommufd/ioas.c
index 8fb5271a9506..e1ded7e8c5ee 100644
--- a/drivers/iommu/iommufd/ioas.c
+++ b/drivers/iommu/iommufd/ioas.c
@@ -183,6 +183,55 @@ int iommufd_ioas_allow_iovas(struct iommufd_ucmd *ucmd)
 	return rc;
 }
 
+int iommufd_ioas_reserve_iovas(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_ioas_reserve_iovas *cmd = ucmd->cmd;
+	struct iommu_iova_range __user *ranges;
+	struct iommufd_ioas *ioas;
+	u32 i;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
+	if (IS_ERR(ioas))
+		return PTR_ERR(ioas);
+
+	ranges = u64_to_user_ptr(cmd->ranges);
+
+	down_write(&ioas->iopt.iova_rwsem);
+	for (i = 0; i < cmd->num_ranges; i++) {
+		struct iommu_iova_range range;
+
+		rc = copy_from_user(&range, &ranges[i], sizeof(range));
+		if (rc) {
+			rc = -EFAULT;
+			goto out_unlock;
+		}
+		if (range.last < range.start) {
+			rc = -EINVAL;
+			goto out_unlock;
+		}
+
+		rc = iopt_reserve_iova(&ioas->iopt, range.start, range.last,
+				       NULL);
+		if (rc)
+			goto out_unlock;
+	}
+	up_write(&ioas->iopt.iova_rwsem);
+
+	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+
+	iommufd_put_object(ucmd->ictx, &ioas->obj);
+	return rc;
+
+out_unlock:
+	up_write(&ioas->iopt.iova_rwsem);
+	iommufd_put_object(ucmd->ictx, &ioas->obj);
+	return rc;
+}
+
 static int conv_iommu_prot(u32 map_flags)
 {
 	/*
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 8016c3efca78..361223372cf3 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -356,6 +356,7 @@ int iommufd_ioas_map(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_change_process(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_copy(struct iommufd_ucmd *ucmd);
+int iommufd_ioas_reserve_iovas(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd);
 #ifdef CONFIG_IOMMUFD_NOIOMMU
 int iommufd_ioas_noiommu_get_pa(struct iommufd_ucmd *ucmd);
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 85f8ea154218..ea03a2078a37 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -447,6 +447,7 @@ union ucmd_buffer {
 	struct iommu_ioas_noiommu_get_pa noiommu_get_pa;
 	struct iommu_ioas_iova_ranges iova_ranges;
 	struct iommu_ioas_map map;
+	struct iommu_ioas_reserve_iovas ioas_reserve_iovas;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
 	struct iommu_recommended_reservations recommended_reservations;
@@ -509,6 +510,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_ioas_map_file, iova),
 	IOCTL_OP(IOMMU_IOAS_NOIOMMU_GET_PA, iommufd_ioas_noiommu_get_pa, struct iommu_ioas_noiommu_get_pa,
 		 out_phys),
+	IOCTL_OP(IOMMU_IOAS_RESERVE_IOVAS, iommufd_ioas_reserve_iovas,
+		 struct iommu_ioas_reserve_iovas, ranges),
 	IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap,
 		 length),
 	IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option, val64),
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 45ba7bd7abd5..75d2421c0f80 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -59,6 +59,7 @@ enum {
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
 	IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS = 0x96,
+	IOMMUFD_CMD_IOAS_RESERVE_IOVAS = 0x97,
 };
 
 /**
@@ -1436,4 +1437,36 @@ struct iommu_recommended_reservations {
 };
 #define IOMMU_GET_RECOMMENDED_RESERVATIONS \
 	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS)
+
+/**
+ * struct iommu_ioas_reserve_iovas - ioctl(IOMMU_IOAS_RESERVE_IOVAS)
+ * @size: sizeof(struct iommu_ioas_reserve_iovas)
+ * @flags: Must be 0
+ * @ioas_id: IOAS ID to reserve ranges in
+ * @num_ranges: Number of ranges in the input array
+ * @ranges: Pointer to the input array of struct iommu_iova_range
+ *
+ * Reserve IOVA ranges in an IOAS, excluding them from mapping and from
+ * IOMMU_IOAS_IOVA_RANGES.  Intended for opting into the ranges
+ * reported by IOMMU_GET_RECOMMENDED_RESERVATIONS.
+ *
+ * Reserve before mapping: a range overlapping an existing mapping or
+ * an allowed IOVA range fails with -EADDRINUSE.  Overlapping
+ * reservations are permitted, so ranges recommended for multiple
+ * devices (e.g. behind the same host bridge) can each be reserved.
+ * A range with last < start fails with -EINVAL.  Ranges are processed
+ * in order and earlier ranges stay reserved on failure; retrying the
+ * whole call is safe.  Reservations live until the IOAS is destroyed.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+struct iommu_ioas_reserve_iovas {
+	__u32 size;
+	__u32 flags;
+	__u32 ioas_id;
+	__u32 num_ranges;
+	__aligned_u64 ranges;
+};
+#define IOMMU_IOAS_RESERVE_IOVAS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_RESERVE_IOVAS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation
  2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
                                       ` (2 preceding siblings ...)
  2026-10-28  7:19                     ` [PATCH v5 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng
@ 2026-10-28  9:04                     ` Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
                                         ` (2 more replies)
  3 siblings, 3 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  9:04 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

This series lets userspace learn which IOVA ranges the kernel
recommends avoiding for a device, and optionally feed them back so
the IOAS allocator enforces them.

The motivating case is a PCI device behind a PCIe switch: DMA using
IOVAs that fall inside the host bridge MMIO windows may be misrouted
as peer-to-peer traffic instead of reaching the root complex for IOMMU
translation.  The DMA IOVA layer already avoids these windows
internally, but passthrough userspace had no way to learn about them.
Per commit cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")
this information must not be forced through the IOMMU reserved-region
API, so the series is fully opt-in:

  Patch 1 adds iommu_for_each_pci_mmio_window(), a callback-based
  iterator in the IOMMU core, and refactors dma-iommu.c to share it.

  Patch 2 adds IOMMU_GET_RECOMMENDED_RESERVATIONS, a per-device query
  returning recommended ranges (currently PCI host bridge MMIO
  windows) as struct iommu_iova_range entries.  Advisory only.

  Patch 3 adds IOMMU_IOAS_RESERVE_IOVAS, letting userspace send
  chosen ranges back in to restrict the IOAS allocator: reserved
  ranges disappear from IOMMU_IOAS_IOVA_RANGES and are rejected by
  mapping.  Overlapping reservations are permitted, so the same
  ranges recommended for several devices behind one host bridge can
  each be reserved independently.

Typical flow: bind device -> query recommendations -> reserve chosen
ranges -> map.  Userspace that ignores the new ioctls sees no
behaviour change.

[1] https://lore.kernel.org/all/20260921115513.GK11599@ziepe.ca/
[2] https://lore.kernel.org/all/20261027182947.GI1685504@ziepe.ca/
[3] https://lore.kernel.org/all/20261027183418.GJ1685504@ziepe.ca/

v5: https://lore.kernel.org/all/20261028071906.2325378-1-guanghuifeng@linux.alibaba.com/
v4: https://lore.kernel.org/all/20261025133000.205508-1-guanghuifeng@linux.alibaba.com/
v3: https://lore.kernel.org/all/20261004162213.3787623-1-guanghuifeng@linux.alibaba.com/
v2: https://lore.kernel.org/all/20260921103922.1113752-1-guanghuifeng@linux.alibaba.com/
v1: https://lore.kernel.org/all/20260921070234.897736-1-guanghuifeng@linux.alibaba.com/

Changes since v5:
- Patch 3: reject ranges with start or last >= ULONG_MAX with
  -EOVERFLOW, matching the existing checks in IOMMU_IOAS_MAP/UNMAP/
  COPY; on 32-bit kernels such ranges would be silently truncated
  when passed to iopt_reserve_iova(), and a wrapped pair could even
  insert an inverted node into the reserved interval tree (reported
  by the Sashiko AI review of v5).
- Patch 3: drop the iommufd_ucmd_respond() call - all fields of
  struct iommu_ioas_reserve_iovas are inputs and the handler never
  modifies them, matching iommufd_ioas_allow_iovas() which also
  returns directly (reported by the Sashiko AI review of v5).

Changes since v4 (all per Jason's review [2][3]):
- Patch 1: drop the single-pointer wrapper struct in dma-iommu.c and
  pass the iova_domain directly as callback data.
- Patch 1: drop the typedef kdoc and trim the iterator kdoc to the
  essential description.
- Patch 1: WARN_ON(!bridge) instead of a silent check; inline the
  start/end temporaries.
- Patch 2: reuse struct iommu_iova_range instead of adding a new
  per-window struct.
- Patch 2: rename away from PCI-specific naming: the interface is now
  IOMMU_GET_RECOMMENDED_RESERVATIONS reporting recommended reserved
  regions, with PCI host bridge MMIO windows as the first source.
- Patch 3 (new): add IOMMU_IOAS_RESERVE_IOVAS so userspace can send
  the ranges back in to create a restriction for the allocator, as
  requested in [3].  The NULL-owner cleanup in iopt_destroy_table()
  is no longer conditional on CONFIG_IOMMUFD_TEST since production
  userspace can now create such reservations.

Changes since v3:
- Helper redesigned as a callback iterator shared with dma-iommu.c
  (v4 changes).

Changes since v2:
- Redesign from forced reservation to opt-in query (v3 changes).

Guanghui Feng (3):
  iommu: Add iommu_for_each_pci_mmio_window() iterator
  iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl
  iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl

 drivers/iommu/dma-iommu.c               | 22 +++++---
 drivers/iommu/iommu-priv.h              | 17 ++++++
 drivers/iommu/iommu.c                   | 37 +++++++++++++
 drivers/iommu/iommufd/device.c          | 65 ++++++++++++++++++++++
 drivers/iommu/iommufd/io_pagetable.c    |  7 ++-
 drivers/iommu/iommufd/ioas.c            | 51 +++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  2 +
 drivers/iommu/iommufd/main.c            |  7 +++
 include/uapi/linux/iommufd.h            | 73 +++++++++++++++++++++++++
 9 files changed, 271 insertions(+), 10 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v6 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator
  2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
@ 2026-10-28  9:04                       ` Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng
  2 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  9:04 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a callback-based iterator that walks a PCI device's host bridge
MMIO windows and invokes a caller-supplied function with each window's
bus address range.  This consolidates the bridge window enumeration
logic into a single shared algorithm in the IOMMU core.

Refactor iova_reserve_pci_windows() to use the new iterator, replacing
its open-coded resource_list_for_each_entry loop.  The callback
receives phys_addr_t start/end directly, so the DMA path reserves the
same ranges as before with zero intermediate allocation, and window
lengths never pass through a size_t field (which would truncate on
32-bit PAE systems).

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/dma-iommu.c  | 22 ++++++++++++++--------
 drivers/iommu/iommu-priv.h | 17 +++++++++++++++++
 drivers/iommu/iommu.c      | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 78742aeba932..5b73e71c312e 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -505,6 +505,15 @@ static int iommu_dma_ranges_sort(void *priv, const struct list_head *a,
 	return res_a->res->start > res_b->res->start;
 }
 
+static int iova_reserve_pci_window_fn(phys_addr_t start, phys_addr_t end,
+				      void *data)
+{
+	struct iova_domain *iovad = data;
+
+	reserve_iova(iovad, iova_pfn(iovad, start), iova_pfn(iovad, end));
+	return 0;
+}
+
 static int iova_reserve_pci_windows(struct pci_dev *dev,
 		struct iova_domain *iovad)
 {
@@ -512,15 +521,12 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
 	struct resource_entry *window;
 	unsigned long lo, hi;
 	phys_addr_t start = 0, end;
+	int ret;
 
-	resource_list_for_each_entry(window, &bridge->windows) {
-		if (resource_type(window->res) != IORESOURCE_MEM)
-			continue;
-
-		lo = iova_pfn(iovad, window->res->start - window->offset);
-		hi = iova_pfn(iovad, window->res->end - window->offset);
-		reserve_iova(iovad, lo, hi);
-	}
+	ret = iommu_for_each_pci_mmio_window(dev, iova_reserve_pci_window_fn,
+					     iovad);
+	if (ret)
+		return ret;
 
 	/* Get reserved DMA windows from host bridge */
 	list_sort(NULL, &bridge->dma_ranges, iommu_dma_ranges_sort);
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index 1f9f886b2c10..81c5b5ce67ec 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -127,4 +127,21 @@ static inline void iommu_debug_init(void)
 
 #endif /* CONFIG_IOMMU_DEBUG_PAGEALLOC */
 
+struct pci_dev;
+
+typedef int (*iommu_pci_mmio_window_fn_t)(phys_addr_t start, phys_addr_t end,
+					  void *data);
+
+#ifdef CONFIG_PCI
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data);
+#else
+static inline int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+						 iommu_pci_mmio_window_fn_t fn,
+						 void *data)
+{
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
 #endif /* __LINUX_IOMMU_PRIV_H */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index b486b8bbd1fc..6b0e2b08ceba 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -945,6 +945,43 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
 	return ret;
 }
 
+#ifdef CONFIG_PCI
+/**
+ * iommu_for_each_pci_mmio_window - iterate over PCI host bridge MMIO windows
+ * @dev: PCI device whose host bridge to scan
+ * @fn: callback invoked with each window's bus address range (inclusive)
+ * @data: opaque pointer passed to @fn
+ *
+ * Iteration stops early if @fn returns non-zero, and that value is
+ * propagated to the caller.
+ *
+ * Return: 0 on success, negative errno on failure (including from @fn).
+ */
+int iommu_for_each_pci_mmio_window(struct pci_dev *dev,
+				   iommu_pci_mmio_window_fn_t fn, void *data)
+{
+	struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+	struct resource_entry *window;
+
+	if (WARN_ON(!bridge))
+		return -ENODEV;
+
+	resource_list_for_each_entry(window, &bridge->windows) {
+		int ret;
+
+		if (resource_type(window->res) != IORESOURCE_MEM)
+			continue;
+
+		ret = fn(window->res->start - window->offset,
+			 window->res->end - window->offset, data);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iommu_for_each_pci_mmio_window);
+#endif /* CONFIG_PCI */
+
 int iommu_get_group_resv_regions(struct iommu_group *group,
 				 struct list_head *head)
 {
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v6 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl
  2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
@ 2026-10-28  9:04                       ` Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng
  2 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  9:04 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add a per-device query ioctl that reports the IOVA ranges the kernel
recommends reserving for a given device.  For PCI devices these are
the MMIO windows of the host bridge the device sits behind: DMA using
IOVAs inside those windows could be misrouted as peer-to-peer traffic
by a PCIe switch instead of reaching the root complex for IOMMU
translation.

The interface is advisory, and deliberately named after the general
concept of recommended reserved regions rather than its first source:
the kernel reports recommendations and userspace decides whether to
act on them, either by feeding them back through
IOMMU_IOAS_RESERVE_IOVAS or by avoiding the ranges in its own IOVA
allocation.  This follows the principle that PCI window information
should not be forced onto userspace through the IOMMU reserved-region
API, as established when the earlier attempt was reverted in commit
cd2c9fcf5c66 ("iommu/dma: Move PCI window region reservation back into dma specific path.")

The output reuses struct iommu_iova_range and follows the array +
count + EMSGSIZE protocol established by IOMMU_IOAS_IOVA_RANGES.
Per-device granularity is chosen because the recommendations derive
from the device's bus topology and are available immediately after
VFIO_DEVICE_BIND_IOMMUFD, before any IOAS attachment.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/device.c          | 65 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  4 ++
 include/uapi/linux/iommufd.h            | 38 +++++++++++++++
 4 files changed, 108 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 1b0bdf57ac8a..eedda48012a1 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1749,3 +1749,68 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
 	iommufd_put_object(ucmd->ictx, &idev->obj);
 	return rc;
 }
+
+struct recommended_resv_ctx {
+	struct iommu_iova_range __user *out_ranges;
+	u32 max_ranges;
+	u32 num_ranges;
+};
+
+static int recommended_resv_window_fn(phys_addr_t start, phys_addr_t end,
+				      void *data)
+{
+	struct recommended_resv_ctx *ctx = data;
+
+	if (ctx->num_ranges < ctx->max_ranges) {
+		struct iommu_iova_range elm = {
+			.start = start,
+			.last = end,
+		};
+
+		if (copy_to_user(&ctx->out_ranges[ctx->num_ranges], &elm,
+				 sizeof(elm)))
+			return -EFAULT;
+	}
+	ctx->num_ranges++;
+	return 0;
+}
+
+int iommufd_device_get_recommended_reservations(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_recommended_reservations *cmd = ucmd->cmd;
+	struct recommended_resv_ctx ctx;
+	struct iommufd_device *idev;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	idev = iommufd_get_device(ucmd, cmd->dev_id);
+	if (IS_ERR(idev))
+		return PTR_ERR(idev);
+
+	if (!dev_is_pci(idev->dev)) {
+		rc = -EOPNOTSUPP;
+		goto out_put;
+	}
+
+	ctx.out_ranges = u64_to_user_ptr(cmd->out_ranges);
+	ctx.max_ranges = cmd->num_ranges;
+	ctx.num_ranges = 0;
+
+	rc = iommu_for_each_pci_mmio_window(to_pci_dev(idev->dev),
+					    recommended_resv_window_fn, &ctx);
+	if (rc)
+		goto out_put;
+
+	cmd->num_ranges = ctx.num_ranges;
+	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+	if (rc)
+		goto out_put;
+	if (ctx.num_ranges > ctx.max_ranges)
+		rc = -EMSGSIZE;
+
+out_put:
+	iommufd_put_object(ucmd->ictx, &idev->obj);
+	return rc;
+}
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eb2e85b27e42..8016c3efca78 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -534,6 +534,7 @@ iommufd_device_get_iommu_dev(struct iommufd_device *idev)
 
 void iommufd_device_pre_destroy(struct iommufd_object *obj);
 void iommufd_device_destroy(struct iommufd_object *obj);
+int iommufd_device_get_recommended_reservations(struct iommufd_ucmd *ucmd);
 int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
 
 struct device *iommufd_global_device(void);
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 9a921b153162..85f8ea154218 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -449,6 +449,7 @@ union ucmd_buffer {
 	struct iommu_ioas_map map;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
+	struct iommu_recommended_reservations recommended_reservations;
 	struct iommu_vdevice_alloc vdev;
 	struct iommu_veventq_alloc veventq;
 	struct iommu_vfio_ioas vfio_ioas;
@@ -480,6 +481,9 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_fault_alloc, out_fault_fd),
 	IOCTL_OP(IOMMU_GET_HW_INFO, iommufd_get_hw_info, struct iommu_hw_info,
 		 __reserved),
+	IOCTL_OP(IOMMU_GET_RECOMMENDED_RESERVATIONS,
+		 iommufd_device_get_recommended_reservations,
+		 struct iommu_recommended_reservations, out_ranges),
 	IOCTL_OP(IOMMU_HW_QUEUE_ALLOC, iommufd_hw_queue_alloc_ioctl,
 		 struct iommu_hw_queue_alloc, length),
 	IOCTL_OP(IOMMU_HWPT_ALLOC, iommufd_hwpt_alloc, struct iommu_hwpt_alloc,
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 010f47f7d3d2..45ba7bd7abd5 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -58,6 +58,7 @@ enum {
 	IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
+	IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS = 0x96,
 };
 
 /**
@@ -1398,4 +1399,41 @@ struct iommu_hw_queue_alloc {
 	__aligned_u64 length;
 };
 #define IOMMU_HW_QUEUE_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HW_QUEUE_ALLOC)
+
+/**
+ * struct iommu_recommended_reservations - ioctl(IOMMU_GET_RECOMMENDED_RESERVATIONS)
+ * @size: sizeof(struct iommu_recommended_reservations)
+ * @flags: Must be 0
+ * @dev_id: The device bound to iommufd to query
+ * @num_ranges: Input/output number of recommended ranges
+ * @out_ranges: Pointer to the output array of struct iommu_iova_range
+ *
+ * Query the IOVA ranges the kernel recommends reserving for @dev_id.
+ * For a PCI device these are the MMIO windows of its host bridge,
+ * where a PCIe switch could misroute DMA as peer-to-peer traffic.
+ * Advisory only: userspace may feed the ranges back through
+ * IOMMU_IOAS_RESERVE_IOVAS or handle them itself.
+ *
+ * Ranges are reported in resource order, not guaranteed sorted, and
+ * should be treated as a set.  Query each device and take the union
+ * when multiple devices are in use.
+ *
+ * On input num_ranges is the length of the out_ranges array.  On
+ * output it is the total number of ranges.  The ioctl will return
+ * -EMSGSIZE and set num_ranges to the required value if num_ranges is
+ * too small.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device has no
+ * recommendations, -ENOENT if @dev_id is invalid, -EMSGSIZE if the
+ * output array is too small.
+ */
+struct iommu_recommended_reservations {
+	__u32 size;
+	__u32 flags;
+	__u32 dev_id;
+	__u32 num_ranges;
+	__aligned_u64 out_ranges;
+};
+#define IOMMU_GET_RECOMMENDED_RESERVATIONS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v6 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl
  2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
  2026-10-28  9:04                       ` [PATCH v6 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
@ 2026-10-28  9:04                       ` Guanghui Feng
  2 siblings, 0 replies; 24+ messages in thread
From: Guanghui Feng @ 2026-10-28  9:04 UTC (permalink / raw)
  To: jgg; +Cc: alex, iommu, joro, kevin.tian, kvm, linux-kernel, robin.murphy, will

Add an ioctl allowing userspace to reserve IOVA ranges in an IOAS,
creating a restriction on the address space: reserved ranges are
excluded from IOMMU_IOAS_IOVA_RANGES and rejected by mapping
operations.

This completes the opt-in flow for the kernel's reservation
recommendations: userspace queries IOMMU_GET_RECOMMENDED_RESERVATIONS
for a device, decides which ranges to honour, and feeds them back
through this ioctl so the kernel enforces them on its behalf.  Both
steps are optional; nothing changes for userspace that does not use
them.

The semantics mirror the existing internal users of
iopt_reserve_iova(): ranges conflicting with existing mappings or
allowed IOVAs fail with -EADDRINUSE, and overlapping reservations are
permitted so that recommendations queried for several devices behind
the same host bridge can each be reserved independently.  User
reservations carry a NULL owner and live until the IOAS is destroyed;
accordingly the NULL-owner cleanup in iopt_destroy_table() is no
longer conditional on CONFIG_IOMMUFD_TEST, since production userspace
can now create such reservations.

On failure the leading ranges already inserted stay reserved; because
overlaps are permitted, retrying the whole call is idempotent.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommufd/io_pagetable.c    |  7 +++-
 drivers/iommu/iommufd/ioas.c            | 51 +++++++++++++++++++++++++
 drivers/iommu/iommufd/iommufd_private.h |  1 +
 drivers/iommu/iommufd/main.c            |  3 ++
 include/uapi/linux/iommufd.h            | 35 +++++++++++++++++
 5 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 5ac736cf5bb5..9a10daf6491e 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -1036,8 +1036,11 @@ void iopt_destroy_table(struct io_pagetable *iopt)
 {
 	struct interval_tree_node *node;
 
-	if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
-		iopt_remove_reserved_iova(iopt, NULL);
+	/*
+	 * Reservations made by userspace through IOMMU_IOAS_RESERVE_IOVAS
+	 * carry a NULL owner and live until the IOAS is destroyed.
+	 */
+	iopt_remove_reserved_iova(iopt, NULL);
 
 	while ((node = interval_tree_iter_first(&iopt->allowed_itree, 0,
 						ULONG_MAX))) {
diff --git a/drivers/iommu/iommufd/ioas.c b/drivers/iommu/iommufd/ioas.c
index 8fb5271a9506..1a2361850bd2 100644
--- a/drivers/iommu/iommufd/ioas.c
+++ b/drivers/iommu/iommufd/ioas.c
@@ -183,6 +183,57 @@ int iommufd_ioas_allow_iovas(struct iommufd_ucmd *ucmd)
 	return rc;
 }
 
+int iommufd_ioas_reserve_iovas(struct iommufd_ucmd *ucmd)
+{
+	struct iommu_ioas_reserve_iovas *cmd = ucmd->cmd;
+	struct iommu_iova_range __user *ranges;
+	struct iommufd_ioas *ioas;
+	u32 i;
+	int rc;
+
+	if (cmd->flags)
+		return -EOPNOTSUPP;
+
+	ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
+	if (IS_ERR(ioas))
+		return PTR_ERR(ioas);
+
+	ranges = u64_to_user_ptr(cmd->ranges);
+
+	down_write(&ioas->iopt.iova_rwsem);
+	for (i = 0; i < cmd->num_ranges; i++) {
+		struct iommu_iova_range range;
+
+		rc = copy_from_user(&range, &ranges[i], sizeof(range));
+		if (rc) {
+			rc = -EFAULT;
+			goto out_unlock;
+		}
+		if (range.last < range.start) {
+			rc = -EINVAL;
+			goto out_unlock;
+		}
+		if (range.start >= ULONG_MAX || range.last >= ULONG_MAX) {
+			rc = -EOVERFLOW;
+			goto out_unlock;
+		}
+
+		rc = iopt_reserve_iova(&ioas->iopt, range.start, range.last,
+				       NULL);
+		if (rc)
+			goto out_unlock;
+	}
+	up_write(&ioas->iopt.iova_rwsem);
+
+	iommufd_put_object(ucmd->ictx, &ioas->obj);
+	return 0;
+
+out_unlock:
+	up_write(&ioas->iopt.iova_rwsem);
+	iommufd_put_object(ucmd->ictx, &ioas->obj);
+	return rc;
+}
+
 static int conv_iommu_prot(u32 map_flags)
 {
 	/*
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 8016c3efca78..361223372cf3 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -356,6 +356,7 @@ int iommufd_ioas_map(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_change_process(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_copy(struct iommufd_ucmd *ucmd);
+int iommufd_ioas_reserve_iovas(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd);
 #ifdef CONFIG_IOMMUFD_NOIOMMU
 int iommufd_ioas_noiommu_get_pa(struct iommufd_ucmd *ucmd);
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 85f8ea154218..ea03a2078a37 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -447,6 +447,7 @@ union ucmd_buffer {
 	struct iommu_ioas_noiommu_get_pa noiommu_get_pa;
 	struct iommu_ioas_iova_ranges iova_ranges;
 	struct iommu_ioas_map map;
+	struct iommu_ioas_reserve_iovas ioas_reserve_iovas;
 	struct iommu_ioas_unmap unmap;
 	struct iommu_option option;
 	struct iommu_recommended_reservations recommended_reservations;
@@ -509,6 +510,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
 		 struct iommu_ioas_map_file, iova),
 	IOCTL_OP(IOMMU_IOAS_NOIOMMU_GET_PA, iommufd_ioas_noiommu_get_pa, struct iommu_ioas_noiommu_get_pa,
 		 out_phys),
+	IOCTL_OP(IOMMU_IOAS_RESERVE_IOVAS, iommufd_ioas_reserve_iovas,
+		 struct iommu_ioas_reserve_iovas, ranges),
 	IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap,
 		 length),
 	IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option, val64),
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 45ba7bd7abd5..f6591c42ad5c 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -59,6 +59,7 @@ enum {
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
 	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
 	IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS = 0x96,
+	IOMMUFD_CMD_IOAS_RESERVE_IOVAS = 0x97,
 };
 
 /**
@@ -1436,4 +1437,38 @@ struct iommu_recommended_reservations {
 };
 #define IOMMU_GET_RECOMMENDED_RESERVATIONS \
 	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_RECOMMENDED_RESERVATIONS)
+
+/**
+ * struct iommu_ioas_reserve_iovas - ioctl(IOMMU_IOAS_RESERVE_IOVAS)
+ * @size: sizeof(struct iommu_ioas_reserve_iovas)
+ * @flags: Must be 0
+ * @ioas_id: IOAS ID to reserve ranges in
+ * @num_ranges: Number of ranges in the input array
+ * @ranges: Pointer to the input array of struct iommu_iova_range
+ *
+ * Reserve IOVA ranges in an IOAS, excluding them from mapping and from
+ * IOMMU_IOAS_IOVA_RANGES.  Intended for opting into the ranges
+ * reported by IOMMU_GET_RECOMMENDED_RESERVATIONS.
+ *
+ * Reserve before mapping: a range overlapping an existing mapping or
+ * an allowed IOVA range fails with -EADDRINUSE.  Overlapping
+ * reservations are permitted, so ranges recommended for multiple
+ * devices (e.g. behind the same host bridge) can each be reserved.
+ * A range with last < start fails with -EINVAL.  On 32-bit kernels
+ * a range exceeding the native address width fails with -EOVERFLOW.
+ * Ranges are processed in order and earlier ranges stay reserved on
+ * failure; retrying the whole call is safe.  Reservations live until
+ * the IOAS is destroyed.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+struct iommu_ioas_reserve_iovas {
+	__u32 size;
+	__u32 flags;
+	__u32 ioas_id;
+	__u32 num_ranges;
+	__aligned_u64 ranges;
+};
+#define IOMMU_IOAS_RESERVE_IOVAS \
+	_IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_RESERVE_IOVAS)
 #endif
-- 
2.43.7


^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-10-28  9:05 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21  7:02 [PATCH] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
2026-09-21 10:39 ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Guanghui Feng
2026-09-21 10:39   ` [PATCH v2 1/2] iommu: Reserve PCI host bridge MMIO windows in group reserved regions Guanghui Feng
2026-09-21 10:39   ` [PATCH v2 2/2] iommufd: Reserve PCI host bridge MMIO windows in IOAS " Guanghui Feng
2026-09-21 11:24   ` [PATCH v2 0/2] iommu: Reserve PCI host bridge MMIO windows for IOVA Robin Murphy
2026-09-21 11:55     ` Jason Gunthorpe
2026-10-04 16:22       ` [PATCH v3 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
2026-10-04 16:22         ` [PATCH v3 1/2] iommu: Add iommu_get_pci_resv_windows() helper Guanghui Feng
2026-10-22 19:35           ` Jason Gunthorpe
2026-10-04 16:22         ` [PATCH v3 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
2026-10-22 19:33           ` Jason Gunthorpe
2026-10-25 13:29             ` [PATCH v4 0/2] iommu/iommufd: Expose PCI host bridge MMIO windows for opt-in IOVA avoidance Guanghui Feng
2026-10-25 13:29               ` [PATCH v4 1/2] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
2026-10-27 18:29                 ` Jason Gunthorpe
2026-10-25 13:30               ` [PATCH v4 2/2] iommufd: Add IOMMU_GET_PCI_MMIO_WINDOWS ioctl Guanghui Feng
2026-10-27 18:34                 ` Jason Gunthorpe
2026-10-28  7:19                   ` [PATCH v5 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
2026-10-28  7:19                     ` [PATCH v5 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
2026-10-28  7:19                     ` [PATCH v5 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
2026-10-28  7:19                     ` [PATCH v5 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng
2026-10-28  9:04                     ` [PATCH v6 0/3] iommufd: Recommended reservations query and opt-in IOAS reservation Guanghui Feng
2026-10-28  9:04                       ` [PATCH v6 1/3] iommu: Add iommu_for_each_pci_mmio_window() iterator Guanghui Feng
2026-10-28  9:04                       ` [PATCH v6 2/3] iommufd: Add IOMMU_GET_RECOMMENDED_RESERVATIONS ioctl Guanghui Feng
2026-10-28  9:04                       ` [PATCH v6 3/3] iommufd: Add IOMMU_IOAS_RESERVE_IOVAS ioctl Guanghui Feng

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®