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