diff options
author | Rob Herring <robh@kernel.org> | 2014-03-27 08:37:43 -0400 |
---|---|---|
committer | Rob Herring <robh@kernel.org> | 2014-05-20 16:19:25 -0400 |
commit | e06e8b27082852bdab417af884241a4ed2037c73 (patch) | |
tree | d6ecaef3c196b99f3ca4f4b5fbbe24a3108a1d19 | |
parent | b0b6abd34c1b508d4ac95dbc614f36c49d29e65a (diff) |
of/fdt: add FDT address translation support
Copy u-boot's FDT address translation code from common/fdt_support. This
code was originally based on the kernel's unflattened DT address parsing
code.
This commit can be reverted once relicensing of this code to GPLv2/BSD
is done and it is added to libfdt.
Signed-off-by: Rob Herring <robh@kernel.org>
Acked-by: Grant Likely <grant.likely@linaro.org>
-rw-r--r-- | drivers/of/Makefile | 2 | ||||
-rw-r--r-- | drivers/of/fdt_address.c | 241 | ||||
-rw-r--r-- | include/linux/of_fdt.h | 1 |
3 files changed, 244 insertions, 0 deletions
diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 9891232f999e..099b1fb00af4 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile | |||
@@ -1,5 +1,6 @@ | |||
1 | obj-y = base.o device.o platform.o | 1 | obj-y = base.o device.o platform.o |
2 | obj-$(CONFIG_OF_FLATTREE) += fdt.o | 2 | obj-$(CONFIG_OF_FLATTREE) += fdt.o |
3 | obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o | ||
3 | obj-$(CONFIG_OF_PROMTREE) += pdt.o | 4 | obj-$(CONFIG_OF_PROMTREE) += pdt.o |
4 | obj-$(CONFIG_OF_ADDRESS) += address.o | 5 | obj-$(CONFIG_OF_ADDRESS) += address.o |
5 | obj-$(CONFIG_OF_IRQ) += irq.o | 6 | obj-$(CONFIG_OF_IRQ) += irq.o |
@@ -12,3 +13,4 @@ obj-$(CONFIG_OF_MTD) += of_mtd.o | |||
12 | obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o | 13 | obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o |
13 | 14 | ||
14 | CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt | 15 | CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt |
16 | CFLAGS_fdt_address.o = -I$(src)/../../scripts/dtc/libfdt | ||
diff --git a/drivers/of/fdt_address.c b/drivers/of/fdt_address.c new file mode 100644 index 000000000000..8d3dc6fbdb7a --- /dev/null +++ b/drivers/of/fdt_address.c | |||
@@ -0,0 +1,241 @@ | |||
1 | /* | ||
2 | * FDT Address translation based on u-boot fdt_support.c which in turn was | ||
3 | * based on the kernel unflattened DT address translation code. | ||
4 | * | ||
5 | * (C) Copyright 2007 | ||
6 | * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com | ||
7 | * | ||
8 | * Copyright 2010-2011 Freescale Semiconductor, Inc. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2, or (at your option) | ||
13 | * any later version. | ||
14 | */ | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/libfdt.h> | ||
17 | #include <linux/of.h> | ||
18 | #include <linux/of_fdt.h> | ||
19 | #include <linux/sizes.h> | ||
20 | |||
21 | /* Max address size we deal with */ | ||
22 | #define OF_MAX_ADDR_CELLS 4 | ||
23 | #define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ | ||
24 | (ns) > 0) | ||
25 | |||
26 | /* Debug utility */ | ||
27 | #ifdef DEBUG | ||
28 | static void __init of_dump_addr(const char *s, const __be32 *addr, int na) | ||
29 | { | ||
30 | pr_debug("%s", s); | ||
31 | while(na--) | ||
32 | pr_cont(" %08x", *(addr++)); | ||
33 | pr_debug("\n"); | ||
34 | } | ||
35 | #else | ||
36 | static void __init of_dump_addr(const char *s, const __be32 *addr, int na) { } | ||
37 | #endif | ||
38 | |||
39 | /* Callbacks for bus specific translators */ | ||
40 | struct of_bus { | ||
41 | void (*count_cells)(const void *blob, int parentoffset, | ||
42 | int *addrc, int *sizec); | ||
43 | u64 (*map)(__be32 *addr, const __be32 *range, | ||
44 | int na, int ns, int pna); | ||
45 | int (*translate)(__be32 *addr, u64 offset, int na); | ||
46 | }; | ||
47 | |||
48 | /* Default translator (generic bus) */ | ||
49 | static void __init fdt_bus_default_count_cells(const void *blob, int parentoffset, | ||
50 | int *addrc, int *sizec) | ||
51 | { | ||
52 | const __be32 *prop; | ||
53 | |||
54 | if (addrc) { | ||
55 | prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL); | ||
56 | if (prop) | ||
57 | *addrc = be32_to_cpup(prop); | ||
58 | else | ||
59 | *addrc = dt_root_addr_cells; | ||
60 | } | ||
61 | |||
62 | if (sizec) { | ||
63 | prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL); | ||
64 | if (prop) | ||
65 | *sizec = be32_to_cpup(prop); | ||
66 | else | ||
67 | *sizec = dt_root_size_cells; | ||
68 | } | ||
69 | } | ||
70 | |||
71 | static u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range, | ||
72 | int na, int ns, int pna) | ||
73 | { | ||
74 | u64 cp, s, da; | ||
75 | |||
76 | cp = of_read_number(range, na); | ||
77 | s = of_read_number(range + na + pna, ns); | ||
78 | da = of_read_number(addr, na); | ||
79 | |||
80 | pr_debug("FDT: default map, cp=%llx, s=%llx, da=%llx\n", | ||
81 | cp, s, da); | ||
82 | |||
83 | if (da < cp || da >= (cp + s)) | ||
84 | return OF_BAD_ADDR; | ||
85 | return da - cp; | ||
86 | } | ||
87 | |||
88 | static int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na) | ||
89 | { | ||
90 | u64 a = of_read_number(addr, na); | ||
91 | memset(addr, 0, na * 4); | ||
92 | a += offset; | ||
93 | if (na > 1) | ||
94 | addr[na - 2] = cpu_to_fdt32(a >> 32); | ||
95 | addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu); | ||
96 | |||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | /* Array of bus specific translators */ | ||
101 | static const struct of_bus of_busses[] __initconst = { | ||
102 | /* Default */ | ||
103 | { | ||
104 | .count_cells = fdt_bus_default_count_cells, | ||
105 | .map = fdt_bus_default_map, | ||
106 | .translate = fdt_bus_default_translate, | ||
107 | }, | ||
108 | }; | ||
109 | |||
110 | static int __init fdt_translate_one(const void *blob, int parent, | ||
111 | const struct of_bus *bus, | ||
112 | const struct of_bus *pbus, __be32 *addr, | ||
113 | int na, int ns, int pna, const char *rprop) | ||
114 | { | ||
115 | const __be32 *ranges; | ||
116 | int rlen; | ||
117 | int rone; | ||
118 | u64 offset = OF_BAD_ADDR; | ||
119 | |||
120 | ranges = fdt_getprop(blob, parent, rprop, &rlen); | ||
121 | if (!ranges) | ||
122 | return 1; | ||
123 | if (rlen == 0) { | ||
124 | offset = of_read_number(addr, na); | ||
125 | memset(addr, 0, pna * 4); | ||
126 | pr_debug("FDT: empty ranges, 1:1 translation\n"); | ||
127 | goto finish; | ||
128 | } | ||
129 | |||
130 | pr_debug("FDT: walking ranges...\n"); | ||
131 | |||
132 | /* Now walk through the ranges */ | ||
133 | rlen /= 4; | ||
134 | rone = na + pna + ns; | ||
135 | for (; rlen >= rone; rlen -= rone, ranges += rone) { | ||
136 | offset = bus->map(addr, ranges, na, ns, pna); | ||
137 | if (offset != OF_BAD_ADDR) | ||
138 | break; | ||
139 | } | ||
140 | if (offset == OF_BAD_ADDR) { | ||
141 | pr_debug("FDT: not found !\n"); | ||
142 | return 1; | ||
143 | } | ||
144 | memcpy(addr, ranges + na, 4 * pna); | ||
145 | |||
146 | finish: | ||
147 | of_dump_addr("FDT: parent translation for:", addr, pna); | ||
148 | pr_debug("FDT: with offset: %llx\n", offset); | ||
149 | |||
150 | /* Translate it into parent bus space */ | ||
151 | return pbus->translate(addr, offset, pna); | ||
152 | } | ||
153 | |||
154 | /* | ||
155 | * Translate an address from the device-tree into a CPU physical address, | ||
156 | * this walks up the tree and applies the various bus mappings on the | ||
157 | * way. | ||
158 | * | ||
159 | * Note: We consider that crossing any level with #size-cells == 0 to mean | ||
160 | * that translation is impossible (that is we are not dealing with a value | ||
161 | * that can be mapped to a cpu physical address). This is not really specified | ||
162 | * that way, but this is traditionally the way IBM at least do things | ||
163 | */ | ||
164 | u64 __init fdt_translate_address(const void *blob, int node_offset) | ||
165 | { | ||
166 | int parent, len; | ||
167 | const struct of_bus *bus, *pbus; | ||
168 | const __be32 *reg; | ||
169 | __be32 addr[OF_MAX_ADDR_CELLS]; | ||
170 | int na, ns, pna, pns; | ||
171 | u64 result = OF_BAD_ADDR; | ||
172 | |||
173 | pr_debug("FDT: ** translation for device %s **\n", | ||
174 | fdt_get_name(blob, node_offset, NULL)); | ||
175 | |||
176 | reg = fdt_getprop(blob, node_offset, "reg", &len); | ||
177 | if (!reg) { | ||
178 | pr_err("FDT: warning: device tree node '%s' has no address.\n", | ||
179 | fdt_get_name(blob, node_offset, NULL)); | ||
180 | goto bail; | ||
181 | } | ||
182 | |||
183 | /* Get parent & match bus type */ | ||
184 | parent = fdt_parent_offset(blob, node_offset); | ||
185 | if (parent < 0) | ||
186 | goto bail; | ||
187 | bus = &of_busses[0]; | ||
188 | |||
189 | /* Cound address cells & copy address locally */ | ||
190 | bus->count_cells(blob, parent, &na, &ns); | ||
191 | if (!OF_CHECK_COUNTS(na, ns)) { | ||
192 | pr_err("FDT: Bad cell count for %s\n", | ||
193 | fdt_get_name(blob, node_offset, NULL)); | ||
194 | goto bail; | ||
195 | } | ||
196 | memcpy(addr, reg, na * 4); | ||
197 | |||
198 | pr_debug("FDT: bus (na=%d, ns=%d) on %s\n", | ||
199 | na, ns, fdt_get_name(blob, parent, NULL)); | ||
200 | of_dump_addr("OF: translating address:", addr, na); | ||
201 | |||
202 | /* Translate */ | ||
203 | for (;;) { | ||
204 | /* Switch to parent bus */ | ||
205 | node_offset = parent; | ||
206 | parent = fdt_parent_offset(blob, node_offset); | ||
207 | |||
208 | /* If root, we have finished */ | ||
209 | if (parent < 0) { | ||
210 | pr_debug("FDT: reached root node\n"); | ||
211 | result = of_read_number(addr, na); | ||
212 | break; | ||
213 | } | ||
214 | |||
215 | /* Get new parent bus and counts */ | ||
216 | pbus = &of_busses[0]; | ||
217 | pbus->count_cells(blob, parent, &pna, &pns); | ||
218 | if (!OF_CHECK_COUNTS(pna, pns)) { | ||
219 | pr_err("FDT: Bad cell count for %s\n", | ||
220 | fdt_get_name(blob, node_offset, NULL)); | ||
221 | break; | ||
222 | } | ||
223 | |||
224 | pr_debug("FDT: parent bus (na=%d, ns=%d) on %s\n", | ||
225 | pna, pns, fdt_get_name(blob, parent, NULL)); | ||
226 | |||
227 | /* Apply bus translation */ | ||
228 | if (fdt_translate_one(blob, node_offset, bus, pbus, | ||
229 | addr, na, ns, pna, "ranges")) | ||
230 | break; | ||
231 | |||
232 | /* Complete the move up one level */ | ||
233 | na = pna; | ||
234 | ns = pns; | ||
235 | bus = pbus; | ||
236 | |||
237 | of_dump_addr("FDT: one level translation:", addr, na); | ||
238 | } | ||
239 | bail: | ||
240 | return result; | ||
241 | } | ||
diff --git a/include/linux/of_fdt.h b/include/linux/of_fdt.h index 5c0ab057eecf..05117899fcb4 100644 --- a/include/linux/of_fdt.h +++ b/include/linux/of_fdt.h | |||
@@ -83,6 +83,7 @@ extern void unflatten_device_tree(void); | |||
83 | extern void unflatten_and_copy_device_tree(void); | 83 | extern void unflatten_and_copy_device_tree(void); |
84 | extern void early_init_devtree(void *); | 84 | extern void early_init_devtree(void *); |
85 | extern void early_get_first_memblock_info(void *, phys_addr_t *); | 85 | extern void early_get_first_memblock_info(void *, phys_addr_t *); |
86 | extern u64 fdt_translate_address(const void *blob, int node_offset); | ||
86 | #else /* CONFIG_OF_FLATTREE */ | 87 | #else /* CONFIG_OF_FLATTREE */ |
87 | static inline void early_init_fdt_scan_reserved_mem(void) {} | 88 | static inline void early_init_fdt_scan_reserved_mem(void) {} |
88 | static inline const char *of_flat_dt_get_machine_name(void) { return NULL; } | 89 | static inline const char *of_flat_dt_get_machine_name(void) { return NULL; } |