iommu: Implement of_iommu_get_resv_regions()
This is an implementation that IOMMU drivers can use to obtain reserved memory regions from a device tree node. It uses the reserved-memory DT bindings to find the regions associated with a given device. If these regions are marked accordingly, identity mappings will be created for them in the IOMMU domain that the devices will be attached to. Cc: Frank Rowand <frowand.list@gmail.com> Cc: devicetree@vger.kernel.org Reviewed-by: Rob Herring <robh@kernel.org> Acked-by: Robin Murphy <robin.murphy@arm.com> Signed-off-by: Thierry Reding <treding@nvidia.com> Link: https://lore.kernel.org/r/20230120174251.4004100-4-thierry.reding@gmail.com Signed-off-by: Joerg Roedel <jroedel@suse.de>
This commit is contained in:
Родитель
af0d81357c
Коммит
a5bf3cfce8
|
@ -10,6 +10,7 @@
|
|||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_iommu.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
|
@ -171,3 +172,96 @@ const struct iommu_ops *of_iommu_configure(struct device *dev,
|
|||
|
||||
return ops;
|
||||
}
|
||||
|
||||
static enum iommu_resv_type iommu_resv_region_get_type(struct device *dev, struct resource *phys,
|
||||
phys_addr_t start, size_t length)
|
||||
{
|
||||
phys_addr_t end = start + length - 1;
|
||||
|
||||
/*
|
||||
* IOMMU regions without an associated physical region cannot be
|
||||
* mapped and are simply reservations.
|
||||
*/
|
||||
if (phys->start >= phys->end)
|
||||
return IOMMU_RESV_RESERVED;
|
||||
|
||||
/* may be IOMMU_RESV_DIRECT_RELAXABLE for certain cases */
|
||||
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);
|
||||
return IOMMU_RESV_RESERVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_iommu_get_resv_regions - reserved region driver helper for device tree
|
||||
* @dev: device for which to get reserved regions
|
||||
* @list: reserved region list
|
||||
*
|
||||
* IOMMU drivers can use this to implement their .get_resv_regions() callback
|
||||
* for memory regions attached to a device tree node. See the reserved-memory
|
||||
* device tree bindings on how to use these:
|
||||
*
|
||||
* Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt
|
||||
*/
|
||||
void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_OF_ADDRESS)
|
||||
struct of_phandle_iterator it;
|
||||
int err;
|
||||
|
||||
of_for_each_phandle(&it, err, dev->of_node, "memory-region", NULL, 0) {
|
||||
const __be32 *maps, *end;
|
||||
struct resource phys;
|
||||
int size;
|
||||
|
||||
memset(&phys, 0, sizeof(phys));
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
if (of_find_property(it.node, "reg", NULL)) {
|
||||
err = of_address_to_resource(it.node, 0, &phys);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to parse memory region %pOF: %d\n",
|
||||
it.node, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
maps = of_get_property(it.node, "iommu-addresses", &size);
|
||||
if (!maps)
|
||||
continue;
|
||||
|
||||
end = maps + size / sizeof(__be32);
|
||||
|
||||
while (maps < end) {
|
||||
struct device_node *np;
|
||||
u32 phandle;
|
||||
|
||||
phandle = be32_to_cpup(maps++);
|
||||
np = of_find_node_by_phandle(phandle);
|
||||
|
||||
if (np == dev->of_node) {
|
||||
int prot = IOMMU_READ | IOMMU_WRITE;
|
||||
struct iommu_resv_region *region;
|
||||
enum iommu_resv_type type;
|
||||
phys_addr_t iova;
|
||||
size_t length;
|
||||
|
||||
maps = of_translate_dma_region(np, maps, &iova, &length);
|
||||
type = iommu_resv_region_get_type(dev, &phys, iova, length);
|
||||
|
||||
region = iommu_alloc_resv_region(iova, length, prot, type,
|
||||
GFP_KERNEL);
|
||||
if (region)
|
||||
list_add_tail(®ion->list, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
EXPORT_SYMBOL(of_iommu_get_resv_regions);
|
||||
|
|
|
@ -12,6 +12,9 @@ extern const struct iommu_ops *of_iommu_configure(struct device *dev,
|
|||
struct device_node *master_np,
|
||||
const u32 *id);
|
||||
|
||||
extern void of_iommu_get_resv_regions(struct device *dev,
|
||||
struct list_head *list);
|
||||
|
||||
#else
|
||||
|
||||
static inline const struct iommu_ops *of_iommu_configure(struct device *dev,
|
||||
|
@ -21,6 +24,11 @@ static inline const struct iommu_ops *of_iommu_configure(struct device *dev,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static inline void of_iommu_get_resv_regions(struct device *dev,
|
||||
struct list_head *list)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_OF_IOMMU */
|
||||
|
||||
#endif /* __OF_IOMMU_H */
|
||||
|
|
Загрузка…
Ссылка в новой задаче