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 | 107 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 
00b02c19f27e195fc36c4bc1d91253255147c9ab..e133d3b346feecac7e2bfaec44e4882060ee89ec
 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -183,6 +183,111 @@ 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;
+       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);
+                       goto exit;
+               }
+
+               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);
+                       goto exit;
+               }
+
+               prev = &ranges;
+               list_for_each_entry(pos, &ranges, node) {
+                       if (pos->start > iova)
+                               break;
+                       prev = &pos->node;
+               }
+
+               new = kmalloc_obj(*new);
+               if (!new)
+                       goto exit;
+
+               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(&region->list, list);
+               }
+
+               if (pos->end >= PHYS_ADDR_MAX - 1)
+                       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,
+                                        prot, IOMMU_RESV_RESERVED, GFP_KERNEL);
+       if (region)
+               list_add_tail(&region->list, list);
+
+exit:
+       list_for_each_entry_safe(pos, next_range, &ranges, node) {
+               list_del(&pos->node);
+               kfree(pos);
+       }
+}
+
 /**
  * of_iommu_get_resv_regions - reserved region driver helper for device tree
  * @dev: device for which to get reserved regions
@@ -262,5 +367,7 @@ void of_iommu_get_resv_regions(struct device *dev, struct 
list_head *list)
                        }
                }
        }
+
+       of_iommu_derive_resv_regions(dev, list);
 }
 EXPORT_SYMBOL(of_iommu_get_resv_regions);

-- 
2.34.1


Reply via email to