diff mbox

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

Message ID 1485508145-20706-1-git-send-email-oohall@gmail.com
State Superseded
Headers show

Commit Message

Oliver O'Halloran Jan. 27, 2017, 9:09 a.m. UTC
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@google.com>
Signed-off-by: Oliver O'Halloran <oohall@gmail.com>
---
 core/device.c          | 65 ++++++++++++++++++++++++++++++++++++++++++++++++--
 core/test/run-device.c | 47 +++++++++++++++++++++++++++++++++++-
 2 files changed, 109 insertions(+), 3 deletions(-)

Comments

Michael Neuling March 7, 2017, 3:01 a.m. UTC | #1
On Fri, 2017-01-27 at 20:09 +1100, Oliver O'Halloran wrote:
> 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@google.com>
> Signed-off-by: Oliver O'Halloran <oohall@gmail.com>

Tested-by: Michael Neuling <mikey@neuling.org>

> ---
>  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;
>  }
>
diff mbox

Patch

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;
 }