[RFC PATCH] of: Fix DMA configuration for non-DT masters
Oza Oza
oza.oza at broadcom.com
Sat Apr 22 04:53:47 EDT 2017
On Wed, Mar 29, 2017 at 12:27 AM, Robin Murphy <robin.murphy at arm.com> wrote:
> For PCI masters not represented in DT, we pass the OF node of their
> associated host bridge to of_dma_configure(), such that they can inherit
> the appropriate DMA configuration from whatever is described there.
> Unfortunately, whilst this has worked for the "dma-coherent" property,
> it turns out to miss the case where the host bridge node has a non-empty
> "dma-ranges", since nobody is expecting the 'device' to be a bus itself.
>
> It transpires, though, that the de-facto interface since the prototype
> change in 1f5c69aa51f9 ("of: Move of_dma_configure() to device.c to help
> re-use") is very clear-cut: either the master_np argument is redundant
> with dev->of_node, or dev->of_node is NULL and master_np is the relevant
> parent bus. Let's ratify that behaviour, then teach the whole
> of_dma_configure() pipeline to cope with both cases properly.
>
> Signed-off-by: Robin Murphy <robin.murphy at arm.com>
> ---
>
> This is what I'd consider the better fix - rather than adding yet more
> special cases - which will also make it simple to handle multiple
> "dma-ranges" entries with minimal further disruption. The callers now
> left passing dev->of_node as 'parent' are harmless, but look a bit silly
> and clearly want cleaning up - I'd be partial to renaming the existing
> function and having a single-argument wrapper for the 'normal' case, e.g.:
>
> static inline int of_dma_configure(struct device_node *dev)
> {
> return of_dma_configure_parent(dev, NULL);
> }
>
> Thoughts?
>
> Robin.
>
> drivers/iommu/of_iommu.c | 7 ++++---
> drivers/of/address.c | 37 +++++++++++++++++++++++++------------
> drivers/of/device.c | 12 +++++++-----
> include/linux/of_address.h | 7 ++++---
> include/linux/of_device.h | 4 ++--
> include/linux/of_iommu.h | 4 ++--
> 6 files changed, 44 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
> index 2683e9fc0dcf..35aff07bb5eb 100644
> --- a/drivers/iommu/of_iommu.c
> +++ b/drivers/iommu/of_iommu.c
> @@ -138,21 +138,22 @@ static const struct iommu_ops
> }
>
> const struct iommu_ops *of_iommu_configure(struct device *dev,
> - struct device_node *master_np)
> + struct device_node *parent)
> {
> struct of_phandle_args iommu_spec;
> - struct device_node *np;
> + struct device_node *np, *master_np;
> const struct iommu_ops *ops = NULL;
> int idx = 0;
>
> if (dev_is_pci(dev))
> - return of_pci_iommu_configure(to_pci_dev(dev), master_np);
> + return of_pci_iommu_configure(to_pci_dev(dev), parent);
>
> /*
> * We don't currently walk up the tree looking for a parent IOMMU.
> * See the `Notes:' section of
> * Documentation/devicetree/bindings/iommu/iommu.txt
> */
> + master_np = dev->of_node ? dev->of_node : parent;
> while (!of_parse_phandle_with_args(master_np, "iommus",
> "#iommu-cells", idx,
> &iommu_spec)) {
> diff --git a/drivers/of/address.c b/drivers/of/address.c
> index 02b2903fe9d2..833bc17f5e55 100644
> --- a/drivers/of/address.c
> +++ b/drivers/of/address.c
> @@ -808,6 +808,7 @@ EXPORT_SYMBOL(of_io_request_and_map);
> /**
> * of_dma_get_range - Get DMA range info
> * @np: device node to get DMA range info
> + * @parent: node of device's parent bus, if @np is NULL
> * @dma_addr: pointer to store initial DMA address of DMA range
> * @paddr: pointer to store initial CPU address of DMA range
> * @size: pointer to store size of DMA range
> @@ -822,36 +823,48 @@ EXPORT_SYMBOL(of_io_request_and_map);
> * It returns -ENODEV if "dma-ranges" property was not found
> * for this device in DT.
> */
> -int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *size)
> +int of_dma_get_range(struct device_node *np, struct device_node *parent,
> + u64 *dma_addr, u64 *paddr, u64 *size)
> {
> - struct device_node *node = of_node_get(np);
> + struct device_node *node;
> const __be32 *ranges = NULL;
> int len, naddr, nsize, pna;
> int ret = 0;
> u64 dmaaddr;
>
> - if (!node)
> - return -EINVAL;
> -
> - while (1) {
> + if (np) {
> + node = of_node_get(np);
> naddr = of_n_addr_cells(node);
> nsize = of_n_size_cells(node);
> node = of_get_next_parent(node);
> - if (!node)
> - break;
> + } else if (parent) {
> + node = of_node_get(parent);
> + np = parent;
> + if (of_property_read_u32(node, "#address-cells", &naddr))
> + naddr = of_n_addr_cells(node);
> + if (of_property_read_u32(node, "#size-cells", &nsize))
> + nsize = of_n_size_cells(node);
> + } else {
> + return -EINVAL;
> + }
>
> + while (node) {
> ranges = of_get_property(node, "dma-ranges", &len);
>
> - /* Ignore empty ranges, they imply no translation required */
> - if (ranges && len > 0)
> - break;
> -
> /*
> * At least empty ranges has to be defined for parent node if
> * DMA is supported
> */
> if (!ranges)
> break;
> +
> + /* Ignore empty ranges, they imply no translation required */
> + if (len > 0)
> + break;
> +
> + naddr = of_n_addr_cells(node);
> + nsize = of_n_size_cells(node);
> + node = of_get_next_parent(node);
> }
>
> if (!ranges) {
> diff --git a/drivers/of/device.c b/drivers/of/device.c
> index 9bb8518b28f2..57ec5324ed6c 100644
> --- a/drivers/of/device.c
> +++ b/drivers/of/device.c
> @@ -73,7 +73,8 @@ int of_device_add(struct platform_device *ofdev)
> /**
> * of_dma_configure - Setup DMA configuration
> * @dev: Device to apply DMA configuration
> - * @np: Pointer to OF node having DMA configuration
> + * @parent: OF node of device's parent bus, if @dev is not
> + * represented in DT (i.e. @dev->of_node is NULL)
> *
> * Try to get devices's DMA configuration from DT and update it
> * accordingly.
> @@ -82,13 +83,14 @@ int of_device_add(struct platform_device *ofdev)
> * can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
> * to fix up DMA configuration.
> */
> -void of_dma_configure(struct device *dev, struct device_node *np)
> +void of_dma_configure(struct device *dev, struct device_node *parent)
> {
> u64 dma_addr, paddr, size;
> int ret;
> bool coherent;
> unsigned long offset;
> const struct iommu_ops *iommu;
> + struct device_node *np = dev->of_node;
>
> /*
> * Set default coherent_dma_mask to 32 bit. Drivers are expected to
> @@ -104,7 +106,7 @@ void of_dma_configure(struct device *dev, struct device_node *np)
> if (!dev->dma_mask)
> dev->dma_mask = &dev->coherent_dma_mask;
>
> - ret = of_dma_get_range(np, &dma_addr, &paddr, &size);
> + ret = of_dma_get_range(np, parent, &dma_addr, &paddr, &size);
> if (ret < 0) {
> dma_addr = offset = 0;
> size = dev->coherent_dma_mask + 1;
> @@ -132,11 +134,11 @@ void of_dma_configure(struct device *dev, struct device_node *np)
> dev->coherent_dma_mask = DMA_BIT_MASK(ilog2(dma_addr + size));
> *dev->dma_mask = dev->coherent_dma_mask;
>
> - coherent = of_dma_is_coherent(np);
> + coherent = of_dma_is_coherent(np ? np : parent);
> dev_dbg(dev, "device is%sdma coherent\n",
> coherent ? " " : " not ");
>
> - iommu = of_iommu_configure(dev, np);
> + iommu = of_iommu_configure(dev, parent);
> dev_dbg(dev, "device is%sbehind an iommu\n",
> iommu ? " " : " not ");
>
> diff --git a/include/linux/of_address.h b/include/linux/of_address.h
> index 37864734ca50..f1a507f3ae57 100644
> --- a/include/linux/of_address.h
> +++ b/include/linux/of_address.h
> @@ -52,8 +52,8 @@ extern int of_pci_range_parser_init(struct of_pci_range_parser *parser,
> extern struct of_pci_range *of_pci_range_parser_one(
> struct of_pci_range_parser *parser,
> struct of_pci_range *range);
> -extern int of_dma_get_range(struct device_node *np, u64 *dma_addr,
> - u64 *paddr, u64 *size);
> +extern int of_dma_get_range(struct device_node *np, struct device_node *parent,
> + u64 *dma_addr, u64 *paddr, u64 *size);
> extern bool of_dma_is_coherent(struct device_node *np);
> #else /* CONFIG_OF_ADDRESS */
> static inline void __iomem *of_io_request_and_map(struct device_node *device,
> @@ -95,7 +95,8 @@ static inline struct of_pci_range *of_pci_range_parser_one(
> return NULL;
> }
>
> -static inline int of_dma_get_range(struct device_node *np, u64 *dma_addr,
> +static inline int of_dma_get_range(struct device_node *np,
> + struct device_node *parent, u64 *dma_addr,
> u64 *paddr, u64 *size)
> {
> return -ENODEV;
> diff --git a/include/linux/of_device.h b/include/linux/of_device.h
> index c12dace043f3..bcd2b6fbeef3 100644
> --- a/include/linux/of_device.h
> +++ b/include/linux/of_device.h
> @@ -55,7 +55,7 @@ static inline struct device_node *of_cpu_device_node_get(int cpu)
> return of_node_get(cpu_dev->of_node);
> }
>
> -void of_dma_configure(struct device *dev, struct device_node *np);
> +void of_dma_configure(struct device *dev, struct device_node *parent);
> #else /* CONFIG_OF */
>
> static inline int of_driver_match_device(struct device *dev,
> @@ -103,7 +103,7 @@ static inline struct device_node *of_cpu_device_node_get(int cpu)
> {
> return NULL;
> }
> -static inline void of_dma_configure(struct device *dev, struct device_node *np)
> +static inline void of_dma_configure(struct device *dev, struct device_node *parent)
> {}
> #endif /* CONFIG_OF */
>
> diff --git a/include/linux/of_iommu.h b/include/linux/of_iommu.h
> index 13394ac83c66..c02b62e8e6ed 100644
> --- a/include/linux/of_iommu.h
> +++ b/include/linux/of_iommu.h
> @@ -12,7 +12,7 @@ extern int of_get_dma_window(struct device_node *dn, const char *prefix,
> size_t *size);
>
> extern const struct iommu_ops *of_iommu_configure(struct device *dev,
> - struct device_node *master_np);
> + struct device_node *parent);
>
> #else
>
> @@ -24,7 +24,7 @@ static inline int of_get_dma_window(struct device_node *dn, const char *prefix,
> }
>
> static inline const struct iommu_ops *of_iommu_configure(struct device *dev,
> - struct device_node *master_np)
> + struct device_node *parent)
> {
> return NULL;
> }
> --
> 2.11.0.dirty
>
Hi Robin,
After some more thoughts to this, I have improved upon old patch design.
please check https://lkml.org/lkml/2017/4/22/34
this patch served following purposes
1) exposes intrface to the pci host driver for thir inbound memory ranges
2) provide an interface to callers such as of_dma_get_ranges.
so then the returned size get best possible (largest) dma_mask.
for e.g.
dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
we should get dev->coherent_dma_mask=0x7fffffffff.
3) this patch handles multiple inbound windows and dma-ranges.
it is left to the caller, how it wants to use them.
the new function returns the resources in a standard and unform way
4) this way the callers of of_dma_get_ranges does not need to change.
and
5) leaves scope of adding PCI flag handling for inbound memory
by the new function.
which I feel much better approach/way than accommodating
of_dma_get_ranges to handle multiple dma-ranges, addressing PCI
masters.
also in your patch I think, you have to make emulated parent node
which is more work I suppose.
again I have tested this on our SOC and it works well.
Regards,
Oza.
More information about the linux-arm-kernel
mailing list