blob: 6ad98e85dc930b61ecbad637e1bed1ee4d391e28 [file] [log] [blame]
Grant Likelye169cfb2009-11-23 14:53:09 -07001/*
2 * Functions for working with the Flattened Device Tree data format
3 *
4 * Copyright 2009 Benjamin Herrenschmidt, IBM Corp
5 * benh@kernel.crashing.org
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * version 2 as published by the Free Software Foundation.
10 */
11
Grant Likely41f88002009-11-23 20:07:01 -070012#include <linux/kernel.h>
13#include <linux/lmb.h>
Grant Likelyf7b3a832009-11-24 03:26:58 -070014#include <linux/initrd.h>
Grant Likelye169cfb2009-11-23 14:53:09 -070015#include <linux/of.h>
16#include <linux/of_fdt.h>
17
18struct boot_param_header *initial_boot_params;
19
20char *find_flat_dt_string(u32 offset)
21{
22 return ((char *)initial_boot_params) +
23 initial_boot_params->off_dt_strings + offset;
24}
Grant Likelyc8cb7a52009-11-23 18:54:23 -070025
26/**
27 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
28 * @it: callback function
29 * @data: context data pointer
30 *
31 * This function is used to scan the flattened device-tree, it is
32 * used to extract the memory information at boot before we can
33 * unflatten the tree
34 */
35int __init of_scan_flat_dt(int (*it)(unsigned long node,
36 const char *uname, int depth,
37 void *data),
38 void *data)
39{
40 unsigned long p = ((unsigned long)initial_boot_params) +
41 initial_boot_params->off_dt_struct;
42 int rc = 0;
43 int depth = -1;
44
45 do {
46 u32 tag = *((u32 *)p);
47 char *pathp;
48
49 p += 4;
50 if (tag == OF_DT_END_NODE) {
51 depth--;
52 continue;
53 }
54 if (tag == OF_DT_NOP)
55 continue;
56 if (tag == OF_DT_END)
57 break;
58 if (tag == OF_DT_PROP) {
59 u32 sz = *((u32 *)p);
60 p += 8;
61 if (initial_boot_params->version < 0x10)
62 p = _ALIGN(p, sz >= 8 ? 8 : 4);
63 p += sz;
64 p = _ALIGN(p, 4);
65 continue;
66 }
67 if (tag != OF_DT_BEGIN_NODE) {
68 pr_err("Invalid tag %x in flat device tree!\n", tag);
69 return -EINVAL;
70 }
71 depth++;
72 pathp = (char *)p;
73 p = _ALIGN(p + strlen(pathp) + 1, 4);
74 if ((*pathp) == '/') {
75 char *lp, *np;
76 for (lp = NULL, np = pathp; *np; np++)
77 if ((*np) == '/')
78 lp = np+1;
79 if (lp != NULL)
80 pathp = lp;
81 }
82 rc = it(p, pathp, depth, data);
83 if (rc != 0)
84 break;
85 } while (1);
86
87 return rc;
88}
Grant Likely819d2812009-11-23 19:44:23 -070089
90/**
91 * of_get_flat_dt_root - find the root node in the flat blob
92 */
93unsigned long __init of_get_flat_dt_root(void)
94{
95 unsigned long p = ((unsigned long)initial_boot_params) +
96 initial_boot_params->off_dt_struct;
97
98 while (*((u32 *)p) == OF_DT_NOP)
99 p += 4;
100 BUG_ON(*((u32 *)p) != OF_DT_BEGIN_NODE);
101 p += 4;
102 return _ALIGN(p + strlen((char *)p) + 1, 4);
103}
104
Grant Likelyca900cf2009-11-23 20:06:59 -0700105/**
106 * of_get_flat_dt_prop - Given a node in the flat blob, return the property ptr
107 *
108 * This function can be used within scan_flattened_dt callback to get
109 * access to properties
110 */
111void *__init of_get_flat_dt_prop(unsigned long node, const char *name,
112 unsigned long *size)
113{
114 unsigned long p = node;
115
116 do {
117 u32 tag = *((u32 *)p);
118 u32 sz, noff;
119 const char *nstr;
120
121 p += 4;
122 if (tag == OF_DT_NOP)
123 continue;
124 if (tag != OF_DT_PROP)
125 return NULL;
126
127 sz = *((u32 *)p);
128 noff = *((u32 *)(p + 4));
129 p += 8;
130 if (initial_boot_params->version < 0x10)
131 p = _ALIGN(p, sz >= 8 ? 8 : 4);
132
133 nstr = find_flat_dt_string(noff);
134 if (nstr == NULL) {
135 pr_warning("Can't find property index name !\n");
136 return NULL;
137 }
138 if (strcmp(name, nstr) == 0) {
139 if (size)
140 *size = sz;
141 return (void *)p;
142 }
143 p += sz;
144 p = _ALIGN(p, 4);
145 } while (1);
146}
147
Grant Likely00e38ef2009-11-23 20:07:00 -0700148/**
149 * of_flat_dt_is_compatible - Return true if given node has compat in compatible list
150 * @node: node to test
151 * @compat: compatible string to compare with compatible list.
152 */
153int __init of_flat_dt_is_compatible(unsigned long node, const char *compat)
154{
155 const char *cp;
156 unsigned long cplen, l;
157
158 cp = of_get_flat_dt_prop(node, "compatible", &cplen);
159 if (cp == NULL)
160 return 0;
161 while (cplen > 0) {
162 if (strncasecmp(cp, compat, strlen(compat)) == 0)
163 return 1;
164 l = strlen(cp) + 1;
165 cp += l;
166 cplen -= l;
167 }
168
169 return 0;
170}
171
Grant Likelybbd33932009-11-23 20:07:00 -0700172static void *__init unflatten_dt_alloc(unsigned long *mem, unsigned long size,
173 unsigned long align)
174{
175 void *res;
176
177 *mem = _ALIGN(*mem, align);
178 res = (void *)*mem;
179 *mem += size;
180
181 return res;
182}
183
184/**
185 * unflatten_dt_node - Alloc and populate a device_node from the flat tree
186 * @p: pointer to node in flat tree
187 * @dad: Parent struct device_node
188 * @allnextpp: pointer to ->allnext from last allocated device_node
189 * @fpsize: Size of the node path up at the current depth.
190 */
191unsigned long __init unflatten_dt_node(unsigned long mem,
192 unsigned long *p,
193 struct device_node *dad,
194 struct device_node ***allnextpp,
195 unsigned long fpsize)
196{
197 struct device_node *np;
198 struct property *pp, **prev_pp = NULL;
199 char *pathp;
200 u32 tag;
201 unsigned int l, allocl;
202 int has_name = 0;
203 int new_format = 0;
204
205 tag = *((u32 *)(*p));
206 if (tag != OF_DT_BEGIN_NODE) {
207 pr_err("Weird tag at start of node: %x\n", tag);
208 return mem;
209 }
210 *p += 4;
211 pathp = (char *)*p;
212 l = allocl = strlen(pathp) + 1;
213 *p = _ALIGN(*p + l, 4);
214
215 /* version 0x10 has a more compact unit name here instead of the full
216 * path. we accumulate the full path size using "fpsize", we'll rebuild
217 * it later. We detect this because the first character of the name is
218 * not '/'.
219 */
220 if ((*pathp) != '/') {
221 new_format = 1;
222 if (fpsize == 0) {
223 /* root node: special case. fpsize accounts for path
224 * plus terminating zero. root node only has '/', so
225 * fpsize should be 2, but we want to avoid the first
226 * level nodes to have two '/' so we use fpsize 1 here
227 */
228 fpsize = 1;
229 allocl = 2;
230 } else {
231 /* account for '/' and path size minus terminal 0
232 * already in 'l'
233 */
234 fpsize += l;
235 allocl = fpsize;
236 }
237 }
238
239 np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
240 __alignof__(struct device_node));
241 if (allnextpp) {
242 memset(np, 0, sizeof(*np));
243 np->full_name = ((char *)np) + sizeof(struct device_node);
244 if (new_format) {
245 char *fn = np->full_name;
246 /* rebuild full path for new format */
247 if (dad && dad->parent) {
248 strcpy(fn, dad->full_name);
249#ifdef DEBUG
250 if ((strlen(fn) + l + 1) != allocl) {
251 pr_debug("%s: p: %d, l: %d, a: %d\n",
252 pathp, (int)strlen(fn),
253 l, allocl);
254 }
255#endif
256 fn += strlen(fn);
257 }
258 *(fn++) = '/';
259 memcpy(fn, pathp, l);
260 } else
261 memcpy(np->full_name, pathp, l);
262 prev_pp = &np->properties;
263 **allnextpp = np;
264 *allnextpp = &np->allnext;
265 if (dad != NULL) {
266 np->parent = dad;
267 /* we temporarily use the next field as `last_child'*/
268 if (dad->next == NULL)
269 dad->child = np;
270 else
271 dad->next->sibling = np;
272 dad->next = np;
273 }
274 kref_init(&np->kref);
275 }
276 while (1) {
277 u32 sz, noff;
278 char *pname;
279
280 tag = *((u32 *)(*p));
281 if (tag == OF_DT_NOP) {
282 *p += 4;
283 continue;
284 }
285 if (tag != OF_DT_PROP)
286 break;
287 *p += 4;
288 sz = *((u32 *)(*p));
289 noff = *((u32 *)((*p) + 4));
290 *p += 8;
291 if (initial_boot_params->version < 0x10)
292 *p = _ALIGN(*p, sz >= 8 ? 8 : 4);
293
294 pname = find_flat_dt_string(noff);
295 if (pname == NULL) {
296 pr_info("Can't find property name in list !\n");
297 break;
298 }
299 if (strcmp(pname, "name") == 0)
300 has_name = 1;
301 l = strlen(pname) + 1;
302 pp = unflatten_dt_alloc(&mem, sizeof(struct property),
303 __alignof__(struct property));
304 if (allnextpp) {
305 if (strcmp(pname, "linux,phandle") == 0) {
306 np->node = *((u32 *)*p);
307 if (np->linux_phandle == 0)
308 np->linux_phandle = np->node;
309 }
310 if (strcmp(pname, "ibm,phandle") == 0)
311 np->linux_phandle = *((u32 *)*p);
312 pp->name = pname;
313 pp->length = sz;
314 pp->value = (void *)*p;
315 *prev_pp = pp;
316 prev_pp = &pp->next;
317 }
318 *p = _ALIGN((*p) + sz, 4);
319 }
320 /* with version 0x10 we may not have the name property, recreate
321 * it here from the unit name if absent
322 */
323 if (!has_name) {
324 char *p1 = pathp, *ps = pathp, *pa = NULL;
325 int sz;
326
327 while (*p1) {
328 if ((*p1) == '@')
329 pa = p1;
330 if ((*p1) == '/')
331 ps = p1 + 1;
332 p1++;
333 }
334 if (pa < ps)
335 pa = p1;
336 sz = (pa - ps) + 1;
337 pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
338 __alignof__(struct property));
339 if (allnextpp) {
340 pp->name = "name";
341 pp->length = sz;
342 pp->value = pp + 1;
343 *prev_pp = pp;
344 prev_pp = &pp->next;
345 memcpy(pp->value, ps, sz - 1);
346 ((char *)pp->value)[sz - 1] = 0;
347 pr_debug("fixed up name for %s -> %s\n", pathp,
348 (char *)pp->value);
349 }
350 }
351 if (allnextpp) {
352 *prev_pp = NULL;
353 np->name = of_get_property(np, "name", NULL);
354 np->type = of_get_property(np, "device_type", NULL);
355
356 if (!np->name)
357 np->name = "<NULL>";
358 if (!np->type)
359 np->type = "<NULL>";
360 }
361 while (tag == OF_DT_BEGIN_NODE) {
362 mem = unflatten_dt_node(mem, p, np, allnextpp, fpsize);
363 tag = *((u32 *)(*p));
364 }
365 if (tag != OF_DT_END_NODE) {
366 pr_err("Weird tag at end of node: %x\n", tag);
367 return mem;
368 }
369 *p += 4;
370 return mem;
371}
Grant Likely41f88002009-11-23 20:07:01 -0700372
Grant Likelyf7b3a832009-11-24 03:26:58 -0700373#ifdef CONFIG_BLK_DEV_INITRD
374/**
375 * early_init_dt_check_for_initrd - Decode initrd location from flat tree
376 * @node: reference to node containing initrd location ('chosen')
377 */
378void __init early_init_dt_check_for_initrd(unsigned long node)
379{
380 unsigned long len;
381 u32 *prop;
382
383 pr_debug("Looking for initrd properties... ");
384
385 prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
386 if (prop) {
387 initrd_start = (unsigned long)
388 __va(of_read_ulong(prop, len/4));
389
390 prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
391 if (prop) {
392 initrd_end = (unsigned long)
393 __va(of_read_ulong(prop, len/4));
394 initrd_below_start_ok = 1;
395 } else {
396 initrd_start = 0;
397 }
398 }
399
400 pr_debug("initrd_start=0x%lx initrd_end=0x%lx\n",
401 initrd_start, initrd_end);
402}
403#else
404inline void early_init_dt_check_for_initrd(unsigned long node)
405{
406}
407#endif /* CONFIG_BLK_DEV_INITRD */
408
Grant Likely41f88002009-11-23 20:07:01 -0700409/**
410 * unflatten_device_tree - create tree of device_nodes from flat blob
411 *
412 * unflattens the device-tree passed by the firmware, creating the
413 * tree of struct device_node. It also fills the "name" and "type"
414 * pointers of the nodes so the normal device-tree walking functions
415 * can be used.
416 */
417void __init unflatten_device_tree(void)
418{
419 unsigned long start, mem, size;
420 struct device_node **allnextp = &allnodes;
421
422 pr_debug(" -> unflatten_device_tree()\n");
423
424 /* First pass, scan for size */
425 start = ((unsigned long)initial_boot_params) +
426 initial_boot_params->off_dt_struct;
427 size = unflatten_dt_node(0, &start, NULL, NULL, 0);
428 size = (size | 3) + 1;
429
430 pr_debug(" size is %lx, allocating...\n", size);
431
432 /* Allocate memory for the expanded device tree */
433 mem = lmb_alloc(size + 4, __alignof__(struct device_node));
434 mem = (unsigned long) __va(mem);
435
436 ((u32 *)mem)[size / 4] = 0xdeadbeef;
437
438 pr_debug(" unflattening %lx...\n", mem);
439
440 /* Second pass, do actual unflattening */
441 start = ((unsigned long)initial_boot_params) +
442 initial_boot_params->off_dt_struct;
443 unflatten_dt_node(mem, &start, NULL, &allnextp, 0);
444 if (*((u32 *)start) != OF_DT_END)
445 pr_warning("Weird tag at end of tree: %08x\n", *((u32 *)start));
446 if (((u32 *)mem)[size / 4] != 0xdeadbeef)
447 pr_warning("End of tree marker overwritten: %08x\n",
448 ((u32 *)mem)[size / 4]);
449 *allnextp = NULL;
450
451 /* Get pointer to OF "/chosen" node for use everywhere */
452 of_chosen = of_find_node_by_path("/chosen");
453 if (of_chosen == NULL)
454 of_chosen = of_find_node_by_path("/chosen@0");
455
456 pr_debug(" <- unflatten_device_tree()\n");
457}