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/[email protected]
Existing "iommu-addresses" property expects a phandle, which does not make sense when the property is defined within the device node. "iommu-ranges" was introduced to specify the device specific IOVA ranges when there is no backing "reg". Suggested-by: Rob Herring <[email protected]> Reviewed-by: Dmitry Baryshkov <[email protected]> Co-developed-by: Vishnu Reddy <[email protected]> Signed-off-by: Vishnu Reddy <[email protected]> Signed-off-by: Vikash Garodia <[email protected]> --- drivers/iommu/of_iommu.c | 134 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 9 deletions(-) diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 5c266511db78910d6fd221af5437cecff4931c22..0428ceb50dec58f11872b4fb64e283fa8d9046a3 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -185,11 +185,118 @@ iommu_resv_region_get_type(struct device *dev, if (start == phys->start && end == phys->end) return IOMMU_RESV_DIRECT; - dev_warn(dev, "treating non-direct mapping [%pr] -> [%pap-%pap] as reservation\n", phys, - &start, &end); + dev_err(dev, "non-direct mapping [%pr] -> [%pap-%pap] is not supported\n", phys, + &start, &end); 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. + */ +#if IS_ENABLED(CONFIG_OF_ADDRESS) +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; + struct list_head *prev; + 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, iova_end; + resource_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; + + iova_end = iova + length - 1; + + if (iova_end < iova) { + dev_err(dev, "%pOF: iommu-ranges overflows address space\n", + dev->of_node); + continue; + } + + prev = &ranges; + list_for_each_entry(pos, &ranges, node) { + if (pos->start > iova) + break; + prev = &pos->node; + } + + new = kmalloc_obj(*new); + if (!new) + continue; + + new->start = iova; + new->end = iova_end; + list_add(&new->node, prev); + } + + 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 == PHYS_ADDR_MAX) + goto exit; + + if (pos->end >= next) + next = pos->end + 1; + + list_del(&pos->node); + kfree(pos); + } + + region = iommu_alloc_resv_region(next, PHYS_ADDR_MAX - next + 1, + prot, IOMMU_RESV_RESERVED, GFP_KERNEL); + if (region) + list_add_tail(®ion->list, list); + +exit: + list_for_each_entry_safe(pos, next_range, &ranges, node) { + list_del(&pos->node); + kfree(pos); + } +} +#endif + /** * of_iommu_get_resv_regions - reserved region driver helper for device tree * @dev: device for which to get reserved regions @@ -214,10 +321,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 +337,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 +369,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 +379,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
