diff options
| -rw-r--r-- | common/fdt_support.c | 264 | ||||
| -rw-r--r-- | include/fdt_support.h | 1 | 
2 files changed, 265 insertions, 0 deletions
| diff --git a/common/fdt_support.c b/common/fdt_support.c index 4d87135cb..70ad81d29 100644 --- a/common/fdt_support.c +++ b/common/fdt_support.c @@ -2,6 +2,8 @@   * (C) Copyright 2007   * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com   * + * Copyright 2010 Freescale Semiconductor, Inc. + *   * See file CREDITS for list of people who contributed to this   * project.   * @@ -861,3 +863,265 @@ void fdt_del_node_and_alias(void *blob, const char *alias)  	off = fdt_path_offset(blob, "/aliases");  	fdt_delprop(blob, off, alias);  } + +/* Helper to read a big number; size is in cells (not bytes) */ +static inline u64 of_read_number(const __be32 *cell, int size) +{ +	u64 r = 0; +	while (size--) +		r = (r << 32) | be32_to_cpu(*(cell++)); +	return r; +} + +static int of_n_cells(const void *blob, int nodeoffset, const char *name) +{ +	int np; +	const int *ip; + +	do { +		np = fdt_parent_offset(blob, nodeoffset); + +		if (np >= 0) +			nodeoffset = np; +		ip = (int *)fdt_getprop(blob, nodeoffset, name, NULL); +		if (ip) +			return be32_to_cpup(ip); +	} while (np >= 0); + +	/* No #<NAME>-cells property for the root node */ +	return 1; +} + +int of_n_addr_cells(const void *blob, int nodeoffset) +{ +	return of_n_cells(blob, nodeoffset, "#address-cells"); +} + +int of_n_size_cells(const void *blob, int nodeoffset) +{ +	return of_n_cells(blob, nodeoffset, "#size-cells"); +} + +#define PRu64	"%llx" + +/* Max address size we deal with */ +#define OF_MAX_ADDR_CELLS	4 +#define OF_BAD_ADDR	((u64)-1) +#define OF_CHECK_COUNTS(na, ns)	((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ +			(ns) > 0) + +/* Debug utility */ +#ifdef DEBUG +static void of_dump_addr(const char *s, const u32 *addr, int na) +{ +	printf("%s", s); +	while(na--) +		printf(" %08x", *(addr++)); +	printf("\n"); +} +#else +static void of_dump_addr(const char *s, const u32 *addr, int na) { } +#endif + +/* Callbacks for bus specific translators */ +struct of_bus { +	const char	*name; +	const char	*addresses; +	void		(*count_cells)(void *blob, int offset, +				int *addrc, int *sizec); +	u64		(*map)(u32 *addr, const u32 *range, +				int na, int ns, int pna); +	int		(*translate)(u32 *addr, u64 offset, int na); +}; + +/* Default translator (generic bus) */ +static void of_bus_default_count_cells(void *blob, int offset, +					int *addrc, int *sizec) +{ +	if (addrc) +		*addrc = of_n_addr_cells(blob, offset); +	if (sizec) +		*sizec = of_n_size_cells(blob, offset); +} + +static u64 of_bus_default_map(u32 *addr, const u32 *range, +		int na, int ns, int pna) +{ +	u64 cp, s, da; + +	cp = of_read_number(range, na); +	s  = of_read_number(range + na + pna, ns); +	da = of_read_number(addr, na); + +	debug("OF: default map, cp="PRu64", s="PRu64", da="PRu64"\n", +	    cp, s, da); + +	if (da < cp || da >= (cp + s)) +		return OF_BAD_ADDR; +	return da - cp; +} + +static int of_bus_default_translate(u32 *addr, u64 offset, int na) +{ +	u64 a = of_read_number(addr, na); +	memset(addr, 0, na * 4); +	a += offset; +	if (na > 1) +		addr[na - 2] = a >> 32; +	addr[na - 1] = a & 0xffffffffu; + +	return 0; +} + +/* Array of bus specific translators */ +static struct of_bus of_busses[] = { +	/* Default */ +	{ +		.name = "default", +		.addresses = "reg", +		.count_cells = of_bus_default_count_cells, +		.map = of_bus_default_map, +		.translate = of_bus_default_translate, +	}, +}; + +static int of_translate_one(void * blob, int parent, struct of_bus *bus, +			    struct of_bus *pbus, u32 *addr, +			    int na, int ns, int pna, const char *rprop) +{ +	const u32 *ranges; +	int rlen; +	int rone; +	u64 offset = OF_BAD_ADDR; + +	/* Normally, an absence of a "ranges" property means we are +	 * crossing a non-translatable boundary, and thus the addresses +	 * below the current not cannot be converted to CPU physical ones. +	 * Unfortunately, while this is very clear in the spec, it's not +	 * what Apple understood, and they do have things like /uni-n or +	 * /ht nodes with no "ranges" property and a lot of perfectly +	 * useable mapped devices below them. Thus we treat the absence of +	 * "ranges" as equivalent to an empty "ranges" property which means +	 * a 1:1 translation at that level. It's up to the caller not to try +	 * to translate addresses that aren't supposed to be translated in +	 * the first place. --BenH. +	 */ +	ranges = (u32 *)fdt_getprop(blob, parent, rprop, &rlen); +	if (ranges == NULL || rlen == 0) { +		offset = of_read_number(addr, na); +		memset(addr, 0, pna * 4); +		debug("OF: no ranges, 1:1 translation\n"); +		goto finish; +	} + +	debug("OF: 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 != OF_BAD_ADDR) +			break; +	} +	if (offset == OF_BAD_ADDR) { +		debug("OF: not found !\n"); +		return 1; +	} +	memcpy(addr, ranges + na, 4 * pna); + + finish: +	of_dump_addr("OF: parent translation for:", addr, pna); +	debug("OF: with offset: "PRu64"\n", 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 + */ +u64 __of_translate_address(void *blob, int node_offset, const u32 *in_addr, +			   const char *rprop) +{ +	int parent; +	struct of_bus *bus, *pbus; +	u32 addr[OF_MAX_ADDR_CELLS]; +	int na, ns, pna, pns; +	u64 result = OF_BAD_ADDR; + +	debug("OF: ** translation for device %s **\n", +		fdt_get_name(blob, node_offset, NULL)); + +	/* Get parent & match bus type */ +	parent = fdt_parent_offset(blob, node_offset); +	if (parent < 0) +		goto bail; +	bus = &of_busses[0]; + +	/* Cound address cells & copy address locally */ +	bus->count_cells(blob, node_offset, &na, &ns); +	if (!OF_CHECK_COUNTS(na, ns)) { +		printf("%s: Bad cell count for %s\n", __FUNCTION__, +		       fdt_get_name(blob, node_offset, NULL)); +		goto bail; +	} +	memcpy(addr, in_addr, na * 4); + +	debug("OF: bus is %s (na=%d, ns=%d) on %s\n", +	    bus->name, na, ns, fdt_get_name(blob, parent, NULL)); +	of_dump_addr("OF: translating address:", addr, na); + +	/* Translate */ +	for (;;) { +		/* Switch to parent bus */ +		node_offset = parent; +		parent = fdt_parent_offset(blob, node_offset); + +		/* If root, we have finished */ +		if (parent < 0) { +			debug("OF: reached root node\n"); +			result = of_read_number(addr, na); +			break; +		} + +		/* Get new parent bus and counts */ +		pbus = &of_busses[0]; +		pbus->count_cells(blob, node_offset, &pna, &pns); +		if (!OF_CHECK_COUNTS(pna, pns)) { +			printf("%s: Bad cell count for %s\n", __FUNCTION__, +				fdt_get_name(blob, node_offset, NULL)); +			break; +		} + +		debug("OF: parent bus is %s (na=%d, ns=%d) on %s\n", +		    pbus->name, pna, pns, fdt_get_name(blob, parent, NULL)); + +		/* Apply bus translation */ +		if (of_translate_one(blob, node_offset, bus, pbus, +					addr, na, ns, pna, rprop)) +			break; + +		/* Complete the move up one level */ +		na = pna; +		ns = pns; +		bus = pbus; + +		of_dump_addr("OF: one level translation:", addr, na); +	} + bail: + +	return result; +} + +u64 fdt_translate_address(void *blob, int node_offset, const u32 *in_addr) +{ +	return __of_translate_address(blob, node_offset, in_addr, "ranges"); +} diff --git a/include/fdt_support.h b/include/fdt_support.h index fc16159eb..78c38b296 100644 --- a/include/fdt_support.h +++ b/include/fdt_support.h @@ -83,6 +83,7 @@ int fdt_fixup_nor_flash_size(void *blob, int cs, u32 size);  void fdt_fixup_mtdparts(void *fdt, void *node_info, int node_info_size);  void fdt_del_node_and_alias(void *blob, const char *alias); +u64 fdt_translate_address(void *blob, int node_offset, const u32 *in_addr);  #endif /* ifdef CONFIG_OF_LIBFDT */  #endif /* ifndef __FDT_SUPPORT_H */ |