From fb27dd8ac6c4c80511e2ca674a6886cd504e1a2c Mon Sep 17 00:00:00 2001 From: Oliver O'Halloran Date: Tue, 7 Mar 2017 15:16:20 +1100 Subject: [PATCH] device: implement dt_translate_address() properly Currently this is implemented by calling dt_get_address() which only works when a device is a child of the root node. This patch implements the functionality to work with nested nodes when all parent nodes have an appropriate "ranges" property. This implementation only works for up to 64 bit addresses. Properly supporting larger addressing schemes is a fair amount of (probably pointless) work, so I'm leaving supporting that until we have an actual a need for it. Signed-off-by: Oliver O'Halloran Tested-by: Michael Neuling Signed-off-by: Stewart Smith --- core/device.c | 65 ++++++++++++++++++++++++++++++++++++++++-- core/test/run-device.c | 47 +++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/core/device.c b/core/device.c index 30b31f461c45..76c2f8481b9f 100644 --- a/core/device.c +++ b/core/device.c @@ -942,11 +942,72 @@ unsigned int dt_count_addresses(const struct dt_node *node) return p->len / n; } +/* Translates an address from the given bus into its parent's address space */ +static u64 dt_translate_one(const struct dt_node *bus, u64 addr) +{ + u32 ranges_count, na, ns, parent_na; + const struct dt_property *p; + const u32 *ranges; + int i, stride; + + assert(bus->parent); + + na = dt_prop_get_u32_def(bus, "#address-cells", 2); + ns = dt_prop_get_u32_def(bus, "#size-cells", 2); + parent_na = dt_n_address_cells(bus); + + stride = na + ns + parent_na; + + /* + * FIXME: We should handle arbitrary length addresses, rather than + * limiting it to 64bit. If someone wants/needs that they + * can implement the bignum math for it :) + */ + assert(na <= 2); + assert(parent_na <= 2); + + /* We should never be trying to translate an address without a ranges */ + p = dt_require_property(bus, "ranges", -1); + + ranges = (u32 *) &p->prop; + ranges_count = (p->len / 4) / (na + parent_na + ns); + + /* An empty ranges property implies 1-1 translation */ + if (ranges_count == 0) + return addr; + + for (i = 0; i < ranges_count; i++, ranges += stride) { + /* ranges format: */ + u64 child_base = dt_get_number(ranges, na); + u64 parent_base = dt_get_number(ranges + na, parent_na); + u64 size = dt_get_number(ranges + na + parent_na, ns); + + if (addr >= child_base && addr < child_base + size) + return (addr - child_base) + parent_base; + } + + /* input address was outside the any of our mapped ranges */ + return 0; +} + u64 dt_translate_address(const struct dt_node *node, unsigned int index, u64 *out_size) { - /* XXX TODO */ - return dt_get_address(node, index, out_size); + u64 addr = dt_get_address(node, index, NULL); + struct dt_node *bus = node->parent; + + /* FIXME: One day we will probably want to use this, but for now just + * force it it to be zero since we only support returning a u64 or u32 + */ + assert(!out_size); + + /* apply each translation until we hit the root bus */ + while (bus->parent) { + addr = dt_translate_one(bus, addr); + bus = bus->parent; + } + + return addr; } bool dt_node_is_enabled(struct dt_node *node) diff --git a/core/test/run-device.c b/core/test/run-device.c index 3da4a2fbcc44..e91e5a5fc5c6 100644 --- a/core/test/run-device.c +++ b/core/test/run-device.c @@ -90,7 +90,7 @@ static bool is_sorted(const struct dt_node *root) int main(void) { - struct dt_node *root, *c1, *c2, *gc1, *gc2, *gc3, *ggc1; + struct dt_node *root, *c1, *c2, *gc1, *gc2, *gc3, *ggc1, *ggc2; struct dt_node *addrs, *addr1, *addr2; struct dt_node *i; const struct dt_property *p; @@ -366,6 +366,51 @@ int main(void) dt_free(root); + /* check dt_translate_address */ + + /* NB: the root bus has two address cells */ + root = dt_new_root(""); + + c1 = dt_new_addr(root, "some-32bit-bus", 0x80000000); + dt_add_property_cells(c1, "#address-cells", 1); + dt_add_property_cells(c1, "#size-cells", 1); + dt_add_property_cells(c1, "ranges", 0x0, 0x8, 0x0, 0x1000); + + gc1 = dt_new_addr(c1, "test", 0x0500); + dt_add_property_cells(gc1, "reg", 0x0500, 0x10); + + assert(dt_translate_address(gc1, 0, NULL) == 0x800000500ul); + + /* try three level translation */ + + gc2 = dt_new_addr(c1, "another-32bit-bus", 0x40000000); + dt_add_property_cells(gc2, "#address-cells", 1); + dt_add_property_cells(gc2, "#size-cells", 1); + dt_add_property_cells(gc2, "ranges", 0x0, 0x600, 0x100, + 0x100, 0x800, 0x100); + + ggc1 = dt_new_addr(gc2, "test", 0x50); + dt_add_property_cells(ggc1, "reg", 0x50, 0x10); + assert(dt_translate_address(ggc1, 0, NULL) == 0x800000650ul); + + /* test multiple ranges work */ + ggc2 = dt_new_addr(gc2, "test", 0x150); + dt_add_property_cells(ggc2, "reg", 0x150, 0x10); + assert(dt_translate_address(ggc2, 0, NULL) == 0x800000850ul); + + /* try 64bit -> 64bit */ + + c2 = dt_new_addr(root, "some-64bit-bus", 0xe00000000); + dt_add_property_cells(c2, "#address-cells", 2); + dt_add_property_cells(c2, "#size-cells", 2); + dt_add_property_cells(c2, "ranges", 0x0, 0x0, 0xe, 0x0, 0x2, 0x0); + + gc2 = dt_new_addr(c2, "test", 0x100000000ul); + dt_add_property_u64s(gc2, "reg", 0x100000000ul, 0x10ul); + assert(dt_translate_address(gc2, 0, NULL) == 0xf00000000ul); + + dt_free(root); + return 0; }