[Skiboot] [PATCH 1/2] device: implement dt_translate_address() properly

Oliver O'Halloran oohall at gmail.com
Fri Jan 27 20:09:04 AEDT 2017


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.

Cc: Rob Lippert <rlippert at google.com>
Signed-off-by: Oliver O'Halloran <oohall at gmail.com>
---
 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: <child base> <parent base> <size> */
+		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;
 }
 
-- 
2.7.4



More information about the Skiboot mailing list