Re: [PATCH 1/2] of: device: Allow DMA range map to be set before of_dma_configure_id

From: Robin Murphy
Date: Mon Jan 18 2021 - 08:37:28 EST


On 2021-01-16 17:07, Paul Kocialkowski wrote:
Hi Robin,

Le Sat 16 Jan 21, 14:57, Robin Murphy a écrit :
On 2021-01-15 17:58, Paul Kocialkowski wrote:
A mechanism was recently introduced for the sunxi architecture where
the DMA offset for specific devices (under the MBUS) is set by a common
driver (sunxi_mbus). This driver calls dma_direct_set_offset to set
the device's dma_range_map manually.

However this information was overwritten by of_dma_configure_id, which
obtains the map from of_dma_get_range (or keeps it NULL when it fails
and the force_dma argument is true, which is the case for platform
devices).

As a result, the dma_range_map was always overwritten and the mechanism
could not correctly take effect.

This adds a check to ensure that no previous DMA range map is
overwritten and prints a warning when the map was already set while
also being available from dt. In this case, the map that was already
set is kept.

Hang on, the hard-coded offset is only intended to be installed when there
*isn't* anything described in DT, in which case of_dma_get_range() should
always bail out early without touching it anyway. This sounds like
something's not quite right in the MBUS driver, so I don't think working
around it in core code is really the right thing to do.

That's right, there is no practical case where the two are in conflict.
The problem that I'm solving here is that dev->dma_range_map is *always*
assigned, even when of_dma_get_range bailed and map still is NULL.

This has the effect of always overwriting what the MBUS driver does
(and leaking its memory too).

Oh, I should have looked closer at of_dma_configure_id() itself. I was assuming that b4bdc4fbf8d0 had been tested and at least *could* work, but apparently not :/

Indeed it seems there was a fundamental oversight in e0d072782c73 ("dma-mapping: introduce DMA range map, supplanting dma_pfn_offset"), equivalent to the same one I previously made myself with bus_dma_mask and fixed in 6778be4e5209 ("of/device: Really only set bus DMA mask when appropriate"). I think same simpler fix is the right one for this case too, i.e. just move the assignment to dev->dma_range_map up under the "if (!ret)" condition. Assigning it this late - after the IOMMU and arch setup - looks wrong anyway, even if it isn't currently an issue in practice (in principle an IOMMU driver *could* start trying to figure out reserved regions around DMA windows for a device as early as its .of_xlate callback, even though that's not the intent of the API design).

Luckily dma_range_map hasn't been hooked up in the equivalent ACPI path yet, so you're off the hook for fixing that too :)

Cheers,
Robin.

Do you have a case where one of the relevant devices inherits a "dma-ranges"
via the regular hierarchy without indirecting via an "interconnects"
reference? Currently you're only checking for the latter, so that would be
one way things could go awry (although to be a problem, said "dma-ranges"
would also have to encode something *other* than the appropriate MBUS
offset, which implies an incorrect or at least inaccurately-structured DT as
well).

No, I think things are good in that regard. No messed up dt or anything like
that :)

Cheers,

Paul

Robin.

Fixes: b4bdc4fbf8d0 ("soc: sunxi: Deal with the MBUS DMA offsets in a central place")
Signed-off-by: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
---
drivers/of/device.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/of/device.c b/drivers/of/device.c
index aedfaaafd3e7..db1b8634c2c7 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -181,7 +181,14 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
arch_setup_dma_ops(dev, dma_start, size, iommu, coherent);
- dev->dma_range_map = map;
+ if (!dev->dma_range_map) {
+ dev->dma_range_map = map;
+ } else if (map) {
+ dev_warn(dev,
+ "DMA range map was already set, ignoring range map from dt\n");
+ kfree(map);
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(of_dma_configure_id);