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(&region->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(&region->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


Reply via email to