[PATCH v3 04/13] iommu: of_iommu: Add support for "iommu-ranges" on a device node

From: Vikash Garodia

Date: Wed Sep 09 2026 - 13:32:28 EST


Reserving an IOVA address using "iommu-addresses" requires it to be
placed in the reserved-memory node. But when "iommu-addresses" is the
only property being described and there is no backing "reg" (i.e. no
actual reserved system memory), it does not really belong under
/reserved-memory. A device IOVA range is specific to its own address
space, and does not describe the system physical memory to make it
qualify under reserved-memory. Given this, place "iommu-addresses"
inside reserved-memory only when it is paired with a "reg", otherwise,
define it within the device own node. It was discussed here:
https://lore.kernel.org/all/662f7093-fb0a-4564-9ca0-98e03e68ba9a@xxxxxxxxxx

Existing "iommu-addresses" property expects a phandle, which does
not make sense when the property is defined within the device node.
Introduce a new property, "iommu-ranges", to specify the device
specific IOVA ranges when there is no backing "reg".

This patch depends-on:
https://github.com/devicetree-org/dt-schema/pull/207

Suggested-by: Rob Herring <robh@xxxxxxxxxx>
Co-developed-by: Vishnu Reddy <busanna.reddy@xxxxxxxxxxxxxxxx>
Signed-off-by: Vishnu Reddy <busanna.reddy@xxxxxxxxxxxxxxxx>
Signed-off-by: Vikash Garodia <vikash.garodia@xxxxxxxxxxxxxxxx>
---
drivers/iommu/of_iommu.c | 107 +++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 100 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index a18bb60f6f3dfdba853b6e16dd2a8616f6a731c7..1b014666d6b8ee3beadbd77e2ec8a24459468652 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -190,6 +190,90 @@ iommu_resv_region_get_type(struct device *dev,
return IOMMU_RESV_RESERVED;
}

+/**
+ * of_iommu_derive_resv_regions - derive reserved regions which
+ * are outside of iommu-ranges
+ * @dev: device for which to get reserved regions
+ * @list: reserved region list
+ *
+ * A device can describe its own usable IOVA ranges directly on its node
+ * via "iommu-ranges". Everything not under those ranges is derived
+ * as a reserved region so the IOMMU allocator won't use it. Entries may
+ * appear in any order in the property.
+ */
+static void of_iommu_derive_resv_regions(struct device *dev, struct list_head *list)
+{
+ struct of_iommu_range {
+ struct list_head node;
+ phys_addr_t start;
+ phys_addr_t end;
+ } *pos, *new, *next_range;
+ int size, prot = IOMMU_READ | IOMMU_WRITE;
+ struct iommu_resv_region *region;
+ const __be32 *maps, *end;
+ phys_addr_t next = 0;
+ LIST_HEAD(ranges);
+
+ maps = of_get_property(dev->of_node, "iommu-ranges", &size);
+ if (!maps)
+ return;
+
+ end = maps + size / sizeof(__be32);
+
+ while (maps < end) {
+ phys_addr_t iova;
+ size_t length;
+
+ maps = of_translate_dma_region(dev->of_node, maps, &iova, &length);
+ if (!maps) {
+ dev_err(dev, "failed to parse iommu-ranges\n");
+ break;
+ }
+
+ if (!length)
+ continue;
+
+ list_for_each_entry(pos, &ranges, node)
+ if (pos->start > iova)
+ break;
+
+ new = kmalloc_obj(*new);
+ if (!new) {
+ dev_err(dev, "kmalloc_obj() failed\n");
+ continue;
+ }
+
+ new->start = iova;
+ new->end = iova + length - 1;
+ list_add_tail(&new->node, &pos->node);
+ }
+
+ if (list_empty(&ranges))
+ return;
+
+ if (of_dma_is_coherent(dev->of_node))
+ prot |= IOMMU_CACHE;
+
+ list_for_each_entry_safe(pos, next_range, &ranges, node) {
+ if (pos->start > next) {
+ region = iommu_alloc_resv_region(next, pos->start - next, prot,
+ IOMMU_RESV_RESERVED, GFP_KERNEL);
+ if (region)
+ list_add_tail(&region->list, list);
+ }
+ if (pos->end + 1 > next)
+ next = pos->end + 1;
+
+ list_del(&pos->node);
+ kfree(pos);
+ }
+
+ region = iommu_alloc_resv_region(next, ~(phys_addr_t)0 - next + 1,
+ prot, IOMMU_RESV_RESERVED, GFP_KERNEL);
+ if (region)
+ list_add_tail(&region->list, list);
+}
+
/**
* of_iommu_get_resv_regions - reserved region driver helper for device tree
* @dev: device for which to get reserved regions
@@ -214,10 +298,14 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)

memset(&phys, 0, sizeof(phys));

+ maps = of_get_property(it.node, "iommu-addresses", &size);
+ if (!maps)
+ continue;
+
/*
- * The "reg" property is optional and can be omitted by reserved-memory regions
- * that represent reservations in the IOVA space, which are regions that should
- * not be mapped.
+ * "iommu-addresses" must be used in combination with a "reg" that provides
+ * the physical address and size of this memory region, for an identity 1:1
+ * IOVA mapping to that physical memory.
*/
if (of_property_present(it.node, "reg")) {
err = of_address_to_resource(it.node, 0, &phys);
@@ -226,11 +314,11 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
it.node, err);
continue;
}
- }
-
- maps = of_get_property(it.node, "iommu-addresses", &size);
- if (!maps)
+ } else {
+ dev_err(dev, "%pOF: iommu-addresses requires a reg property\n",
+ it.node);
continue;
+ }

end = maps + size / sizeof(__be32);

@@ -258,6 +346,9 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
}
type = iommu_resv_region_get_type(dev, &phys, iova, length);

+ if (type != IOMMU_RESV_DIRECT)
+ continue;
+
region = iommu_alloc_resv_region(iova, length, prot, type,
GFP_KERNEL);
if (region)
@@ -265,6 +356,8 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
}
}
}
+
+ of_iommu_derive_resv_regions(dev, list);
#endif
}
EXPORT_SYMBOL(of_iommu_get_resv_regions);

--
2.34.1