]>
Commit | Line | Data |
---|---|---|
35748177 GVB |
1 | /* |
2 | * libfdt - Flat Device Tree manipulation | |
3 | * Copyright (C) 2006 David Gibson, IBM Corporation. | |
35084760 | 4 | * SPDX-License-Identifier: GPL-2.0+ BSD-2-Clause |
35748177 GVB |
5 | */ |
6 | #include "libfdt_env.h" | |
7 | ||
8cf30809 | 8 | #ifndef USE_HOSTCC |
35748177 GVB |
9 | #include <fdt.h> |
10 | #include <libfdt.h> | |
8cf30809 BS |
11 | #else |
12 | #include "fdt_host.h" | |
13 | #endif | |
35748177 GVB |
14 | |
15 | #include "libfdt_internal.h" | |
16 | ||
fc7758ee DG |
17 | static int _fdt_nodename_eq(const void *fdt, int offset, |
18 | const char *s, int len) | |
35748177 | 19 | { |
ae0b5908 | 20 | const char *p = fdt_offset_ptr(fdt, offset + FDT_TAGSIZE, len+1); |
35748177 GVB |
21 | |
22 | if (! p) | |
23 | /* short match */ | |
24 | return 0; | |
25 | ||
26 | if (memcmp(p, s, len) != 0) | |
27 | return 0; | |
28 | ||
8d04f02f KG |
29 | if (p[len] == '\0') |
30 | return 1; | |
31 | else if (!memchr(s, '@', len) && (p[len] == '@')) | |
32 | return 1; | |
33 | else | |
35748177 | 34 | return 0; |
35748177 GVB |
35 | } |
36 | ||
8d04f02f | 37 | const char *fdt_string(const void *fdt, int stroffset) |
9675ee72 | 38 | { |
c6683026 | 39 | return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; |
8d04f02f | 40 | } |
9675ee72 | 41 | |
0219399a DG |
42 | static int _fdt_string_eq(const void *fdt, int stroffset, |
43 | const char *s, int len) | |
44 | { | |
45 | const char *p = fdt_string(fdt, stroffset); | |
46 | ||
af67b252 | 47 | return (strnlen(p, len + 1) == len) && (memcmp(p, s, len) == 0); |
0219399a DG |
48 | } |
49 | ||
8d04f02f KG |
50 | int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) |
51 | { | |
fc7758ee | 52 | FDT_CHECK_HEADER(fdt); |
8d04f02f KG |
53 | *address = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->address); |
54 | *size = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->size); | |
9675ee72 GVB |
55 | return 0; |
56 | } | |
57 | ||
8d04f02f | 58 | int fdt_num_mem_rsv(const void *fdt) |
35748177 | 59 | { |
8d04f02f KG |
60 | int i = 0; |
61 | ||
62 | while (fdt64_to_cpu(_fdt_mem_rsv(fdt, i)->size) != 0) | |
63 | i++; | |
64 | return i; | |
35748177 GVB |
65 | } |
66 | ||
d1c63148 DG |
67 | static int _nextprop(const void *fdt, int offset) |
68 | { | |
69 | uint32_t tag; | |
70 | int nextoffset; | |
71 | ||
72 | do { | |
73 | tag = fdt_next_tag(fdt, offset, &nextoffset); | |
74 | ||
75 | switch (tag) { | |
76 | case FDT_END: | |
77 | if (nextoffset >= 0) | |
78 | return -FDT_ERR_BADSTRUCTURE; | |
79 | else | |
80 | return nextoffset; | |
81 | ||
82 | case FDT_PROP: | |
83 | return offset; | |
84 | } | |
85 | offset = nextoffset; | |
86 | } while (tag == FDT_NOP); | |
87 | ||
88 | return -FDT_ERR_NOTFOUND; | |
89 | } | |
90 | ||
ae0b5908 | 91 | int fdt_subnode_offset_namelen(const void *fdt, int offset, |
35748177 GVB |
92 | const char *name, int namelen) |
93 | { | |
2c0b843e | 94 | int depth; |
35748177 | 95 | |
fc7758ee | 96 | FDT_CHECK_HEADER(fdt); |
35748177 | 97 | |
2c0b843e DG |
98 | for (depth = 0; |
99 | (offset >= 0) && (depth >= 0); | |
100 | offset = fdt_next_node(fdt, offset, &depth)) | |
101 | if ((depth == 1) | |
102 | && _fdt_nodename_eq(fdt, offset, name, namelen)) | |
ae0b5908 | 103 | return offset; |
35748177 | 104 | |
2c0b843e | 105 | if (depth < 0) |
4bc7deee | 106 | return -FDT_ERR_NOTFOUND; |
2c0b843e | 107 | return offset; /* error */ |
35748177 GVB |
108 | } |
109 | ||
110 | int fdt_subnode_offset(const void *fdt, int parentoffset, | |
111 | const char *name) | |
112 | { | |
113 | return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); | |
114 | } | |
115 | ||
8d04f02f | 116 | int fdt_path_offset(const void *fdt, const char *path) |
35748177 GVB |
117 | { |
118 | const char *end = path + strlen(path); | |
119 | const char *p = path; | |
120 | int offset = 0; | |
121 | ||
fc7758ee | 122 | FDT_CHECK_HEADER(fdt); |
35748177 | 123 | |
feeca3f5 KG |
124 | /* see if we have an alias */ |
125 | if (*path != '/') { | |
9a6cf73a | 126 | const char *q = strchr(path, '/'); |
feeca3f5 | 127 | |
feeca3f5 KG |
128 | if (!q) |
129 | q = end; | |
130 | ||
9a6cf73a | 131 | p = fdt_get_alias_namelen(fdt, p, q - p); |
feeca3f5 KG |
132 | if (!p) |
133 | return -FDT_ERR_BADPATH; | |
134 | offset = fdt_path_offset(fdt, p); | |
135 | ||
136 | p = q; | |
137 | } | |
35748177 GVB |
138 | |
139 | while (*p) { | |
140 | const char *q; | |
141 | ||
142 | while (*p == '/') | |
143 | p++; | |
144 | if (! *p) | |
8d04f02f | 145 | return offset; |
35748177 GVB |
146 | q = strchr(p, '/'); |
147 | if (! q) | |
148 | q = end; | |
149 | ||
150 | offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); | |
151 | if (offset < 0) | |
152 | return offset; | |
153 | ||
154 | p = q; | |
155 | } | |
156 | ||
aea03c4e | 157 | return offset; |
35748177 GVB |
158 | } |
159 | ||
8d04f02f KG |
160 | const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) |
161 | { | |
2f08bfa9 | 162 | const struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset); |
8d04f02f KG |
163 | int err; |
164 | ||
2f08bfa9 DG |
165 | if (((err = fdt_check_header(fdt)) != 0) |
166 | || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0)) | |
167 | goto fail; | |
8d04f02f KG |
168 | |
169 | if (len) | |
170 | *len = strlen(nh->name); | |
171 | ||
172 | return nh->name; | |
173 | ||
174 | fail: | |
175 | if (len) | |
176 | *len = err; | |
177 | return NULL; | |
178 | } | |
179 | ||
d1c63148 DG |
180 | int fdt_first_property_offset(const void *fdt, int nodeoffset) |
181 | { | |
182 | int offset; | |
183 | ||
184 | if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0) | |
185 | return offset; | |
186 | ||
187 | return _nextprop(fdt, offset); | |
188 | } | |
189 | ||
190 | int fdt_next_property_offset(const void *fdt, int offset) | |
191 | { | |
192 | if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) | |
193 | return offset; | |
194 | ||
195 | return _nextprop(fdt, offset); | |
196 | } | |
197 | ||
198 | const struct fdt_property *fdt_get_property_by_offset(const void *fdt, | |
199 | int offset, | |
200 | int *lenp) | |
35748177 | 201 | { |
35748177 | 202 | int err; |
d1c63148 | 203 | const struct fdt_property *prop; |
35748177 | 204 | |
d1c63148 DG |
205 | if ((err = _fdt_check_prop_offset(fdt, offset)) < 0) { |
206 | if (lenp) | |
207 | *lenp = err; | |
208 | return NULL; | |
209 | } | |
35748177 | 210 | |
d1c63148 | 211 | prop = _fdt_offset_ptr(fdt, offset); |
35748177 | 212 | |
d1c63148 DG |
213 | if (lenp) |
214 | *lenp = fdt32_to_cpu(prop->len); | |
35748177 | 215 | |
d1c63148 DG |
216 | return prop; |
217 | } | |
218 | ||
219 | const struct fdt_property *fdt_get_property_namelen(const void *fdt, | |
220 | int offset, | |
221 | const char *name, | |
222 | int namelen, int *lenp) | |
223 | { | |
224 | for (offset = fdt_first_property_offset(fdt, offset); | |
225 | (offset >= 0); | |
226 | (offset = fdt_next_property_offset(fdt, offset))) { | |
227 | const struct fdt_property *prop; | |
228 | ||
229 | if (!(prop = fdt_get_property_by_offset(fdt, offset, lenp))) { | |
230 | offset = -FDT_ERR_INTERNAL; | |
35748177 | 231 | break; |
35748177 | 232 | } |
d1c63148 DG |
233 | if (_fdt_string_eq(fdt, fdt32_to_cpu(prop->nameoff), |
234 | name, namelen)) | |
235 | return prop; | |
236 | } | |
35748177 | 237 | |
35748177 | 238 | if (lenp) |
d1c63148 | 239 | *lenp = offset; |
35748177 GVB |
240 | return NULL; |
241 | } | |
242 | ||
0219399a DG |
243 | const struct fdt_property *fdt_get_property(const void *fdt, |
244 | int nodeoffset, | |
245 | const char *name, int *lenp) | |
246 | { | |
247 | return fdt_get_property_namelen(fdt, nodeoffset, name, | |
248 | strlen(name), lenp); | |
249 | } | |
250 | ||
251 | const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, | |
252 | const char *name, int namelen, int *lenp) | |
35748177 GVB |
253 | { |
254 | const struct fdt_property *prop; | |
255 | ||
0219399a | 256 | prop = fdt_get_property_namelen(fdt, nodeoffset, name, namelen, lenp); |
35748177 GVB |
257 | if (! prop) |
258 | return NULL; | |
259 | ||
8d04f02f | 260 | return prop->data; |
3af0d587 GVB |
261 | } |
262 | ||
d1c63148 DG |
263 | const void *fdt_getprop_by_offset(const void *fdt, int offset, |
264 | const char **namep, int *lenp) | |
265 | { | |
266 | const struct fdt_property *prop; | |
267 | ||
268 | prop = fdt_get_property_by_offset(fdt, offset, lenp); | |
269 | if (!prop) | |
270 | return NULL; | |
271 | if (namep) | |
272 | *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); | |
273 | return prop->data; | |
274 | } | |
275 | ||
0219399a DG |
276 | const void *fdt_getprop(const void *fdt, int nodeoffset, |
277 | const char *name, int *lenp) | |
278 | { | |
279 | return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); | |
280 | } | |
281 | ||
8d04f02f KG |
282 | uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) |
283 | { | |
b2ba62a1 | 284 | const fdt32_t *php; |
8d04f02f | 285 | int len; |
3af0d587 | 286 | |
05a22ba0 DG |
287 | /* FIXME: This is a bit sub-optimal, since we potentially scan |
288 | * over all the properties twice. */ | |
289 | php = fdt_getprop(fdt, nodeoffset, "phandle", &len); | |
290 | if (!php || (len != sizeof(*php))) { | |
291 | php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); | |
292 | if (!php || (len != sizeof(*php))) | |
293 | return 0; | |
294 | } | |
8d04f02f KG |
295 | |
296 | return fdt32_to_cpu(*php); | |
297 | } | |
298 | ||
9a6cf73a DG |
299 | const char *fdt_get_alias_namelen(const void *fdt, |
300 | const char *name, int namelen) | |
301 | { | |
302 | int aliasoffset; | |
303 | ||
304 | aliasoffset = fdt_path_offset(fdt, "/aliases"); | |
305 | if (aliasoffset < 0) | |
306 | return NULL; | |
307 | ||
308 | return fdt_getprop_namelen(fdt, aliasoffset, name, namelen, NULL); | |
309 | } | |
310 | ||
311 | const char *fdt_get_alias(const void *fdt, const char *name) | |
312 | { | |
313 | return fdt_get_alias_namelen(fdt, name, strlen(name)); | |
314 | } | |
315 | ||
8d04f02f | 316 | int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) |
3af0d587 | 317 | { |
ae0b5908 DG |
318 | int pdepth = 0, p = 0; |
319 | int offset, depth, namelen; | |
8d04f02f KG |
320 | const char *name; |
321 | ||
fc7758ee | 322 | FDT_CHECK_HEADER(fdt); |
8d04f02f | 323 | |
8d04f02f KG |
324 | if (buflen < 2) |
325 | return -FDT_ERR_NOSPACE; | |
8d04f02f | 326 | |
ae0b5908 DG |
327 | for (offset = 0, depth = 0; |
328 | (offset >= 0) && (offset <= nodeoffset); | |
329 | offset = fdt_next_node(fdt, offset, &depth)) { | |
ae0b5908 DG |
330 | while (pdepth > depth) { |
331 | do { | |
332 | p--; | |
333 | } while (buf[p-1] != '/'); | |
334 | pdepth--; | |
335 | } | |
336 | ||
bbdbc7cb DG |
337 | if (pdepth >= depth) { |
338 | name = fdt_get_name(fdt, offset, &namelen); | |
339 | if (!name) | |
340 | return namelen; | |
341 | if ((p + namelen + 1) <= buflen) { | |
342 | memcpy(buf + p, name, namelen); | |
343 | p += namelen; | |
344 | buf[p++] = '/'; | |
345 | pdepth++; | |
346 | } | |
ae0b5908 | 347 | } |
8d04f02f | 348 | |
ae0b5908 DG |
349 | if (offset == nodeoffset) { |
350 | if (pdepth < (depth + 1)) | |
351 | return -FDT_ERR_NOSPACE; | |
8d04f02f | 352 | |
ae0b5908 DG |
353 | if (p > 1) /* special case so that root path is "/", not "" */ |
354 | p--; | |
355 | buf[p] = '\0'; | |
bbdbc7cb | 356 | return 0; |
3af0d587 | 357 | } |
3af0d587 GVB |
358 | } |
359 | ||
ae0b5908 DG |
360 | if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) |
361 | return -FDT_ERR_BADOFFSET; | |
362 | else if (offset == -FDT_ERR_BADOFFSET) | |
363 | return -FDT_ERR_BADSTRUCTURE; | |
3af0d587 | 364 | |
ae0b5908 | 365 | return offset; /* error from fdt_next_node() */ |
35748177 | 366 | } |
3f9f08cf | 367 | |
8d04f02f KG |
368 | int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, |
369 | int supernodedepth, int *nodedepth) | |
370 | { | |
ae0b5908 | 371 | int offset, depth; |
8d04f02f KG |
372 | int supernodeoffset = -FDT_ERR_INTERNAL; |
373 | ||
fc7758ee | 374 | FDT_CHECK_HEADER(fdt); |
8d04f02f KG |
375 | |
376 | if (supernodedepth < 0) | |
377 | return -FDT_ERR_NOTFOUND; | |
378 | ||
ae0b5908 DG |
379 | for (offset = 0, depth = 0; |
380 | (offset >= 0) && (offset <= nodeoffset); | |
381 | offset = fdt_next_node(fdt, offset, &depth)) { | |
382 | if (depth == supernodedepth) | |
383 | supernodeoffset = offset; | |
8d04f02f | 384 | |
ae0b5908 DG |
385 | if (offset == nodeoffset) { |
386 | if (nodedepth) | |
387 | *nodedepth = depth; | |
8d04f02f | 388 | |
ae0b5908 DG |
389 | if (supernodedepth > depth) |
390 | return -FDT_ERR_NOTFOUND; | |
391 | else | |
392 | return supernodeoffset; | |
8d04f02f | 393 | } |
ae0b5908 | 394 | } |
8d04f02f | 395 | |
ae0b5908 DG |
396 | if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) |
397 | return -FDT_ERR_BADOFFSET; | |
398 | else if (offset == -FDT_ERR_BADOFFSET) | |
399 | return -FDT_ERR_BADSTRUCTURE; | |
8d04f02f | 400 | |
ae0b5908 | 401 | return offset; /* error from fdt_next_node() */ |
8d04f02f KG |
402 | } |
403 | ||
404 | int fdt_node_depth(const void *fdt, int nodeoffset) | |
3f9f08cf | 405 | { |
8d04f02f KG |
406 | int nodedepth; |
407 | int err; | |
408 | ||
409 | err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); | |
410 | if (err) | |
411 | return (err < 0) ? err : -FDT_ERR_INTERNAL; | |
412 | return nodedepth; | |
413 | } | |
414 | ||
415 | int fdt_parent_offset(const void *fdt, int nodeoffset) | |
416 | { | |
417 | int nodedepth = fdt_node_depth(fdt, nodeoffset); | |
418 | ||
419 | if (nodedepth < 0) | |
420 | return nodedepth; | |
421 | return fdt_supernode_atdepth_offset(fdt, nodeoffset, | |
422 | nodedepth - 1, NULL); | |
423 | } | |
424 | ||
425 | int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, | |
426 | const char *propname, | |
427 | const void *propval, int proplen) | |
428 | { | |
ae0b5908 | 429 | int offset; |
8d04f02f KG |
430 | const void *val; |
431 | int len; | |
432 | ||
fc7758ee | 433 | FDT_CHECK_HEADER(fdt); |
8d04f02f | 434 | |
8d04f02f KG |
435 | /* FIXME: The algorithm here is pretty horrible: we scan each |
436 | * property of a node in fdt_getprop(), then if that didn't | |
437 | * find what we want, we scan over them again making our way | |
438 | * to the next node. Still it's the easiest to implement | |
439 | * approach; performance can come later. */ | |
ae0b5908 DG |
440 | for (offset = fdt_next_node(fdt, startoffset, NULL); |
441 | offset >= 0; | |
442 | offset = fdt_next_node(fdt, offset, NULL)) { | |
443 | val = fdt_getprop(fdt, offset, propname, &len); | |
444 | if (val && (len == proplen) | |
445 | && (memcmp(val, propval, len) == 0)) | |
446 | return offset; | |
447 | } | |
8d04f02f | 448 | |
ae0b5908 | 449 | return offset; /* error from fdt_next_node() */ |
8d04f02f KG |
450 | } |
451 | ||
452 | int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) | |
453 | { | |
05a22ba0 DG |
454 | int offset; |
455 | ||
8d04f02f KG |
456 | if ((phandle == 0) || (phandle == -1)) |
457 | return -FDT_ERR_BADPHANDLE; | |
05a22ba0 DG |
458 | |
459 | FDT_CHECK_HEADER(fdt); | |
460 | ||
461 | /* FIXME: The algorithm here is pretty horrible: we | |
462 | * potentially scan each property of a node in | |
463 | * fdt_get_phandle(), then if that didn't find what | |
464 | * we want, we scan over them again making our way to the next | |
465 | * node. Still it's the easiest to implement approach; | |
466 | * performance can come later. */ | |
467 | for (offset = fdt_next_node(fdt, -1, NULL); | |
468 | offset >= 0; | |
469 | offset = fdt_next_node(fdt, offset, NULL)) { | |
470 | if (fdt_get_phandle(fdt, offset) == phandle) | |
471 | return offset; | |
472 | } | |
473 | ||
474 | return offset; /* error from fdt_next_node() */ | |
8d04f02f KG |
475 | } |
476 | ||
e853b324 | 477 | int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) |
8d04f02f KG |
478 | { |
479 | int len = strlen(str); | |
ef4e8ce1 | 480 | const char *p; |
8d04f02f KG |
481 | |
482 | while (listlen >= len) { | |
483 | if (memcmp(str, strlist, len+1) == 0) | |
484 | return 1; | |
485 | p = memchr(strlist, '\0', listlen); | |
486 | if (!p) | |
487 | return 0; /* malformed strlist.. */ | |
488 | listlen -= (p-strlist) + 1; | |
489 | strlist = p + 1; | |
3f9f08cf GVB |
490 | } |
491 | return 0; | |
492 | } | |
493 | ||
bc4147ab TR |
494 | int fdt_count_strings(const void *fdt, int node, const char *property) |
495 | { | |
496 | int length, i, count = 0; | |
497 | const char *list; | |
498 | ||
499 | list = fdt_getprop(fdt, node, property, &length); | |
500 | if (!list) | |
501 | return -length; | |
502 | ||
503 | for (i = 0; i < length; i++) { | |
504 | int len = strlen(list); | |
505 | ||
506 | list += len + 1; | |
507 | i += len; | |
508 | count++; | |
509 | } | |
510 | ||
511 | return count; | |
512 | } | |
513 | ||
8d04f02f KG |
514 | int fdt_node_check_compatible(const void *fdt, int nodeoffset, |
515 | const char *compatible) | |
3f9f08cf | 516 | { |
8d04f02f KG |
517 | const void *prop; |
518 | int len; | |
3f9f08cf | 519 | |
8d04f02f KG |
520 | prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); |
521 | if (!prop) | |
522 | return len; | |
e853b324 | 523 | if (fdt_stringlist_contains(prop, len, compatible)) |
8d04f02f KG |
524 | return 0; |
525 | else | |
526 | return 1; | |
527 | } | |
3f9f08cf | 528 | |
8d04f02f KG |
529 | int fdt_node_offset_by_compatible(const void *fdt, int startoffset, |
530 | const char *compatible) | |
531 | { | |
11abe45c | 532 | int offset, err; |
8d04f02f | 533 | |
fc7758ee | 534 | FDT_CHECK_HEADER(fdt); |
8d04f02f | 535 | |
8d04f02f KG |
536 | /* FIXME: The algorithm here is pretty horrible: we scan each |
537 | * property of a node in fdt_node_check_compatible(), then if | |
538 | * that didn't find what we want, we scan over them again | |
539 | * making our way to the next node. Still it's the easiest to | |
540 | * implement approach; performance can come later. */ | |
ae0b5908 DG |
541 | for (offset = fdt_next_node(fdt, startoffset, NULL); |
542 | offset >= 0; | |
543 | offset = fdt_next_node(fdt, offset, NULL)) { | |
544 | err = fdt_node_check_compatible(fdt, offset, compatible); | |
545 | if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) | |
546 | return err; | |
547 | else if (err == 0) | |
548 | return offset; | |
549 | } | |
8d04f02f | 550 | |
ae0b5908 | 551 | return offset; /* error from fdt_next_node() */ |
3f9f08cf | 552 | } |