Skip to content

Commit

Permalink
device: implement dt_translate_address() properly
Browse files Browse the repository at this point in the history
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 <oohall@gmail.com>
Tested-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
  • Loading branch information
oohal authored and stewartsmith committed Mar 7, 2017
1 parent 0f28e38 commit fb27dd8
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 3 deletions.
65 changes: 63 additions & 2 deletions core/device.c
Expand Up @@ -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)
Expand Down
47 changes: 46 additions & 1 deletion core/test/run-device.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

0 comments on commit fb27dd8

Please sign in to comment.