diff options
author | Julien Grall <julien.grall@linaro.org> | 2013-04-26 17:28:21 +0100 |
---|---|---|
committer | Ian Campbell <ian.campbell@citrix.com> | 2013-05-13 11:59:57 +0100 |
commit | 227c02f8a627734761da130327d42b4c55aec1f4 (patch) | |
tree | bd4a746fbc170fe909ede5267305c0579510de50 /xen/common/device_tree.c | |
parent | dbd1243248298014f47f88ad43f592133f4fc82f (diff) | |
download | xen-227c02f8a627734761da130327d42b4c55aec1f4.tar.gz xen-227c02f8a627734761da130327d42b4c55aec1f4.tar.bz2 xen-227c02f8a627734761da130327d42b4c55aec1f4.zip |
xen/arm: Add helpers to retrieve an address from the device tree
Signed-off-by: Julien Grall <julien.grall@linaro.org>
Acked-by: Ian Campbell <ian.campbell@citrix.com>
Diffstat (limited to 'xen/common/device_tree.c')
-rw-r--r-- | xen/common/device_tree.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/xen/common/device_tree.c b/xen/common/device_tree.c index 92f2ac5dc7..c1d1cf1162 100644 --- a/xen/common/device_tree.c +++ b/xen/common/device_tree.c @@ -68,10 +68,38 @@ static LIST_HEAD(aliases_lookup); #ifdef DEBUG_DT # define dt_dprintk(fmt, args...) dt_printk(XENLOG_DEBUG fmt, ##args) +static void dt_dump_addr(const char *s, const __be32 *addr, int na) +{ + dt_dprintk("%s", s); + while ( na-- ) + dt_dprintk(" %08x", be32_to_cpu(*(addr++))); + dt_dprintk("\n"); +} #else # define dt_dprintk(fmt, args...) do {} while ( 0 ) +static void dt_dump_addr(const char *s, const __be32 *addr, int na) { } #endif +#define DT_BAD_ADDR ((u64)-1) + +/* Max address size we deal with */ +#define DT_MAX_ADDR_CELLS 4 +#define DT_CHECK_ADDR_COUNT(na) ((na) > 0 && (na) <= DT_MAX_ADDR_CELLS) +#define DT_CHECK_COUNTS(na, ns) (DT_CHECK_ADDR_COUNT(na) && (ns) > 0) + +/* Callbacks for bus specific translators */ +struct dt_bus +{ + const char *name; + const char *addresses; + int (*match)(const struct dt_device_node *parent); + void (*count_cells)(const struct dt_device_node *child, + int *addrc, int *sizec); + u64 (*map)(__be32 *addr, const __be32 *range, int na, int ns, int pna); + int (*translate)(__be32 *addr, u64 offset, int na); + unsigned int (*get_flags)(const __be32 *addr); +}; + bool_t __init device_tree_node_matches(const void *fdt, int node, const char *match) { @@ -716,6 +744,321 @@ int dt_n_size_cells(const struct dt_device_node *np) return DT_ROOT_NODE_SIZE_CELLS_DEFAULT; } +/* + * Default translator (generic bus) + */ +static void dt_bus_default_count_cells(const struct dt_device_node *dev, + int *addrc, int *sizec) +{ + if ( addrc ) + *addrc = dt_n_addr_cells(dev); + if ( sizec ) + *sizec = dt_n_size_cells(dev); +} + +static u64 dt_bus_default_map(__be32 *addr, const __be32 *range, + int na, int ns, int pna) +{ + u64 cp, s, da; + + cp = dt_read_number(range, na); + s = dt_read_number(range + na + pna, ns); + da = dt_read_number(addr, na); + + dt_dprintk("DT: default map, cp=%llx, s=%llx, da=%llx\n", + (unsigned long long)cp, (unsigned long long)s, + (unsigned long long)da); + + /* + * If the number of address cells is larger than 2 we assume the + * mapping doesn't specify a physical address. Rather, the address + * specifies an identifier that must match exactly. + */ + if ( na > 2 && memcmp(range, addr, na * 4) != 0 ) + return DT_BAD_ADDR; + + if ( da < cp || da >= (cp + s) ) + return DT_BAD_ADDR; + return da - cp; +} + +static int dt_bus_default_translate(__be32 *addr, u64 offset, int na) +{ + u64 a = dt_read_number(addr, na); + + memset(addr, 0, na * 4); + a += offset; + if ( na > 1 ) + addr[na - 2] = cpu_to_be32(a >> 32); + addr[na - 1] = cpu_to_be32(a & 0xffffffffu); + + return 0; +} +static unsigned int dt_bus_default_get_flags(const __be32 *addr) +{ + /* TODO: Return the type of memory (device, ...) for caching + * attribute during mapping */ + return 0; +} + +/* + * Array of bus specific translators + */ +static const struct dt_bus dt_busses[] = +{ + /* Default */ + { + .name = "default", + .addresses = "reg", + .match = NULL, + .count_cells = dt_bus_default_count_cells, + .map = dt_bus_default_map, + .translate = dt_bus_default_translate, + .get_flags = dt_bus_default_get_flags, + }, +}; + +static const struct dt_bus *dt_match_bus(const struct dt_device_node *np) +{ + int i; + + for ( i = 0; i < ARRAY_SIZE(dt_busses); i++ ) + if ( !dt_busses[i].match || dt_busses[i].match(np) ) + return &dt_busses[i]; + BUG(); + + return NULL; +} + +static const __be32 *dt_get_address(const struct dt_device_node *dev, + int index, u64 *size, + unsigned int *flags) +{ + const __be32 *prop; + u32 psize; + const struct dt_device_node *parent; + const struct dt_bus *bus; + int onesize, i, na, ns; + + /* Get parent & match bus type */ + parent = dt_get_parent(dev); + if ( parent == NULL ) + return NULL; + bus = dt_match_bus(parent); + bus->count_cells(dev, &na, &ns); + + if ( !DT_CHECK_ADDR_COUNT(na) ) + return NULL; + + /* Get "reg" or "assigned-addresses" property */ + prop = dt_get_property(dev, bus->addresses, &psize); + if ( prop == NULL ) + return NULL; + psize /= 4; + + onesize = na + ns; + for ( i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++ ) + { + if ( i == index ) + { + if ( size ) + *size = dt_read_number(prop + na, ns); + if ( flags ) + *flags = bus->get_flags(prop); + return prop; + } + } + return NULL; +} + +static int dt_translate_one(const struct dt_device_node *parent, + const struct dt_bus *bus, + const struct dt_bus *pbus, + __be32 *addr, int na, int ns, + int pna, const char *rprop) +{ + const __be32 *ranges; + unsigned int rlen; + int rone; + u64 offset = DT_BAD_ADDR; + + ranges = dt_get_property(parent, rprop, &rlen); + if ( ranges == NULL ) + { + dt_printk(XENLOG_ERR "DT: no ranges; cannot translate\n"); + return 1; + } + if ( ranges == NULL || rlen == 0 ) + { + offset = dt_read_number(addr, na); + memset(addr, 0, pna * 4); + dt_dprintk("DT: empty ranges; 1:1 translation\n"); + goto finish; + } + + dt_dprintk("DT: walking ranges...\n"); + + /* Now walk through the ranges */ + rlen /= 4; + rone = na + pna + ns; + for ( ; rlen >= rone; rlen -= rone, ranges += rone ) + { + offset = bus->map(addr, ranges, na, ns, pna); + if ( offset != DT_BAD_ADDR ) + break; + } + if ( offset == DT_BAD_ADDR ) + { + dt_dprintk("DT: not found !\n"); + return 1; + } + memcpy(addr, ranges + na, 4 * pna); + +finish: + dt_dump_addr("DT: parent translation for:", addr, pna); + dt_dprintk("DT: with offset: %llx\n", (unsigned long long)offset); + + /* Translate it into parent bus space */ + return pbus->translate(addr, offset, pna); +} + +/* + * Translate an address from the device-tree into a CPU physical address, + * this walks up the tree and applies the various bus mappings on the + * way. + * + * Note: We consider that crossing any level with #size-cells == 0 to mean + * that translation is impossible (that is we are not dealing with a value + * that can be mapped to a cpu physical address). This is not really specified + * that way, but this is traditionally the way IBM at least do things + */ +static u64 __dt_translate_address(const struct dt_device_node *dev, + const __be32 *in_addr, const char *rprop) +{ + const struct dt_device_node *parent = NULL; + const struct dt_bus *bus, *pbus; + __be32 addr[DT_MAX_ADDR_CELLS]; + int na, ns, pna, pns; + u64 result = DT_BAD_ADDR; + + dt_dprintk("DT: ** translation for device %s **\n", dev->full_name); + + /* Get parent & match bus type */ + parent = dt_get_parent(dev); + if ( parent == NULL ) + goto bail; + bus = dt_match_bus(parent); + + /* Count address cells & copy address locally */ + bus->count_cells(dev, &na, &ns); + if ( !DT_CHECK_COUNTS(na, ns) ) + { + dt_printk(XENLOG_ERR "dt_parse: Bad cell count for %s\n", + dev->full_name); + goto bail; + } + memcpy(addr, in_addr, na * 4); + + dt_dprintk("DT: bus is %s (na=%d, ns=%d) on %s\n", + bus->name, na, ns, parent->full_name); + dt_dump_addr("DT: translating address:", addr, na); + + /* Translate */ + for ( ;; ) + { + /* Switch to parent bus */ + dev = parent; + parent = dt_get_parent(dev); + + /* If root, we have finished */ + if ( parent == NULL ) + { + dt_dprintk("DT: reached root node\n"); + result = dt_read_number(addr, na); + break; + } + + /* Get new parent bus and counts */ + pbus = dt_match_bus(parent); + pbus->count_cells(dev, &pna, &pns); + if ( !DT_CHECK_COUNTS(pna, pns) ) + { + printk(XENLOG_ERR "dt_parse: Bad cell count for %s\n", + dev->full_name); + break; + } + + dt_dprintk("DT: parent bus is %s (na=%d, ns=%d) on %s\n", + pbus->name, pna, pns, parent->full_name); + + /* Apply bus translation */ + if ( dt_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop) ) + break; + + /* Complete the move up one level */ + na = pna; + ns = pns; + bus = pbus; + + dt_dump_addr("DT: one level translation:", addr, na); + } + +bail: + return result; +} + +/* dt_device_address - Translate device tree address and return it */ +int dt_device_get_address(const struct dt_device_node *dev, int index, + u64 *addr, u64 *size) +{ + const __be32 *addrp; + unsigned int flags; + + addrp = dt_get_address(dev, index, size, &flags); + if ( addrp == NULL ) + return -EINVAL; + + if ( !addr ) + return -EINVAL; + + *addr = __dt_translate_address(dev, addrp, "ranges"); + + if ( *addr == DT_BAD_ADDR ) + return -EINVAL; + + return 0; +} + +unsigned int dt_number_of_address(const struct dt_device_node *dev) +{ + const __be32 *prop; + u32 psize; + const struct dt_device_node *parent; + const struct dt_bus *bus; + int onesize, na, ns; + + /* Get parent & match bus type */ + parent = dt_get_parent(dev); + if ( parent == NULL ) + return 0; + + bus = dt_match_bus(parent); + bus->count_cells(dev, &na, &ns); + + if ( !DT_CHECK_COUNTS(na, ns) ) + return 0; + + /* Get "reg" or "assigned-addresses" property */ + prop = dt_get_property(dev, bus->addresses, &psize); + if ( prop == NULL ) + return 0; + + psize /= 4; + onesize = na + ns; + + return (psize / onesize); +} + /** * unflatten_dt_node - Alloc and populate a device_node from the flat tree * @fdt: The parent device tree blob |