[PATCH v4 04/13] iommu: of_iommu: Add support for "iommu-ranges" on a device node
From: Vikash Garodia <vikash.garodia@oss.qualcomm.com>
Date: 2026-09-13 18:50:19
Also in:
linux-arm-msm, linux-iommu, linux-media, lkml
Subsystem:
iommu subsystem, the rest · Maintainers:
Joerg Roedel, Will Deacon, Linus Torvalds
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@kernel.org (local) 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". Suggested-by: Rob Herring <robh@kernel.org> Co-developed-by: Vishnu Reddy <redacted> Signed-off-by: Vishnu Reddy <redacted> Signed-off-by: Vikash Garodia <vikash.garodia@oss.qualcomm.com> --- drivers/iommu/of_iommu.c | 115 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 7 deletions(-)
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index a18bb60f6f3dfdba853b6e16dd2a8616f6a731c7..baf74db838d6bc45df2b36c49eb47eeca21b4b7b 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c@@ -190,6 +190,98 @@ 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, "%pOF: failed to parse iommu-ranges\n", + dev->of_node); + break; + } + + if (!length) + continue; + + if (iova + length < iova) { + dev_err(dev, "%pOF: iommu-ranges overflows address space\n", + dev->of_node); + continue; + } + + list_for_each_entry(pos, &ranges, node) + if (pos->start > iova) + break; + + new = kmalloc_obj(*new); + if (!new) + continue; + + new->start = iova; + new->end = iova + length; + 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(®ion->list, list); + } + if (pos->end > next || !pos->end) + next = pos->end; + + list_del(&pos->node); + kfree(pos); + } + + if (!next) + return; + + region = iommu_alloc_resv_region(next, ~(phys_addr_t)0 - next + 1, + prot, IOMMU_RESV_RESERVED, GFP_KERNEL); + if (region) + list_add_tail(®ion->list, list); +} + /** * of_iommu_get_resv_regions - reserved region driver helper for device tree * @dev: device for which to get reserved regions
@@ -214,10 +306,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 +322,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 +354,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 +364,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