1 /*
2  * libfdt - Flat Device Tree manipulation
3  * Copyright (C) 2006 David Gibson, IBM Corporation.
4  *
5  * libfdt is dual licensed: you can use it either under the terms of
6  * the GPL, or the BSD license, at your option.
7  *
8  *  a) This library is free software; you can redistribute it and/or
9  *     modify it under the terms of the GNU General Public License as
10  *     published by the Free Software Foundation; either version 2 of the
11  *     License, or (at your option) any later version.
12  *
13  *     This library is distributed in the hope that it will be useful,
14  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *     GNU General Public License for more details.
17  *
18  *     You should have received a copy of the GNU General Public
19  *     License along with this library; if not, write to the Free
20  *     Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
21  *     MA 02110-1301 USA
22  *
23  * Alternatively,
24  *
25  *  b) Redistribution and use in source and binary forms, with or
26  *     without modification, are permitted provided that the following
27  *     conditions are met:
28  *
29  *     1. Redistributions of source code must retain the above
30  *        copyright notice, this list of conditions and the following
31  *        disclaimer.
32  *     2. Redistributions in binary form must reproduce the above
33  *        copyright notice, this list of conditions and the following
34  *        disclaimer in the documentation and/or other materials
35  *        provided with the distribution.
36  *
37  *     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
38  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
39  *     INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
40  *     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
41  *     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
42  *     CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44  *     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
45  *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46  *     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
47  *     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
48  *     OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
49  *     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50  */
51 #include "libfdt_env.h"
52 #include "fdt.h"
53 #include "libfdt.h"
54 #include "libfdt_internal.h"
55 
_fdt_nodename_eq(const void * fdt,int offset,const char * s,int len)56 static int _fdt_nodename_eq(const void *fdt, int offset,
57                 const char *s, int len)
58 {
59     const char *p = fdt_offset_ptr(fdt, offset + FDT_TAGSIZE, len+1);
60 
61     if (! p)
62         /* short match */
63         return 0;
64 
65     if (memcmp(p, s, len) != 0)
66         return 0;
67 
68     if (p[len] == '\0')
69         return 1;
70     else if (!memchr(s, '@', len) && (p[len] == '@'))
71         return 1;
72     else
73         return 0;
74 }
75 
fdt_string(const void * fdt,int stroffset)76 const char *fdt_string(const void *fdt, int stroffset)
77 {
78     return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset;
79 }
80 
_fdt_string_eq(const void * fdt,int stroffset,const char * s,int len)81 static int _fdt_string_eq(const void *fdt, int stroffset,
82               const char *s, int len)
83 {
84     const char *p = fdt_string(fdt, stroffset);
85 
86     return (strlen(p) == len) && (memcmp(p, s, len) == 0);
87 }
88 
fdt_get_max_phandle(const void * fdt)89 uint32_t fdt_get_max_phandle(const void *fdt)
90 {
91     uint32_t max_phandle = 0;
92     int offset;
93 
94     for (offset = fdt_next_node(fdt, -1, NULL);;
95          offset = fdt_next_node(fdt, offset, NULL)) {
96         uint32_t phandle;
97 
98         if (offset == -FDT_ERR_NOTFOUND)
99             return max_phandle;
100 
101         if (offset < 0)
102             return (uint32_t)-1;
103 
104         phandle = fdt_get_phandle(fdt, offset);
105         if (phandle == (uint32_t)-1)
106             continue;
107 
108         if (phandle > max_phandle)
109             max_phandle = phandle;
110     }
111 
112     return 0;
113 }
114 
fdt_get_mem_rsv(const void * fdt,int n,uint64_t * address,uint64_t * size)115 int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size)
116 {
117     FDT_CHECK_HEADER(fdt);
118     *address = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->address);
119     *size = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->size);
120     return 0;
121 }
122 
fdt_num_mem_rsv(const void * fdt)123 int fdt_num_mem_rsv(const void *fdt)
124 {
125     int i = 0;
126 
127     while (fdt64_to_cpu(_fdt_mem_rsv(fdt, i)->size) != 0)
128         i++;
129     return i;
130 }
131 
_nextprop(const void * fdt,int offset)132 static int _nextprop(const void *fdt, int offset)
133 {
134     uint32_t tag;
135     int nextoffset;
136 
137     do {
138         tag = fdt_next_tag(fdt, offset, &nextoffset);
139 
140         switch (tag) {
141         case FDT_END:
142             if (nextoffset >= 0)
143                 return -FDT_ERR_BADSTRUCTURE;
144             else
145                 return nextoffset;
146 
147         case FDT_PROP:
148             return offset;
149         }
150         offset = nextoffset;
151     } while (tag == FDT_NOP);
152 
153     return -FDT_ERR_NOTFOUND;
154 }
155 
fdt_subnode_offset_namelen(const void * fdt,int offset,const char * name,int namelen)156 int fdt_subnode_offset_namelen(const void *fdt, int offset,
157                    const char *name, int namelen)
158 {
159     int depth;
160 
161     FDT_CHECK_HEADER(fdt);
162 
163     for (depth = 0;
164          (offset >= 0) && (depth >= 0);
165          offset = fdt_next_node(fdt, offset, &depth))
166         if ((depth == 1)
167             && _fdt_nodename_eq(fdt, offset, name, namelen))
168             return offset;
169 
170     if (depth < 0)
171         return -FDT_ERR_NOTFOUND;
172     return offset; /* error */
173 }
174 
fdt_subnode_offset(const void * fdt,int parentoffset,const char * name)175 int fdt_subnode_offset(const void *fdt, int parentoffset,
176                const char *name)
177 {
178     return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name));
179 }
180 
fdt_path_offset_namelen(const void * fdt,const char * path,int namelen)181 int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen)
182 {
183     const char *end = path + namelen;
184     const char *p = path;
185     int offset = 0;
186 
187     FDT_CHECK_HEADER(fdt);
188 
189     /* see if we have an alias */
190     if (*path != '/') {
191         const char *q = memchr(path, '/', end - p);
192 
193         if (!q)
194             q = end;
195 
196         p = fdt_get_alias_namelen(fdt, p, q - p);
197         if (!p)
198             return -FDT_ERR_BADPATH;
199         offset = fdt_path_offset(fdt, p);
200 
201         p = q;
202     }
203 
204     while (p < end) {
205         const char *q;
206 
207         while (*p == '/') {
208             p++;
209             if (p == end)
210                 return offset;
211         }
212         q = memchr(p, '/', end - p);
213         if (! q)
214             q = end;
215 
216         offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p);
217         if (offset < 0)
218             return offset;
219 
220         p = q;
221     }
222 
223     return offset;
224 }
225 
fdt_path_offset(const void * fdt,const char * path)226 int fdt_path_offset(const void *fdt, const char *path)
227 {
228     return fdt_path_offset_namelen(fdt, path, strlen(path));
229 }
230 
fdt_get_name(const void * fdt,int nodeoffset,int * len)231 const char *fdt_get_name(const void *fdt, int nodeoffset, int *len)
232 {
233     const struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset);
234     int err;
235 
236     if (((err = fdt_check_header(fdt)) != 0)
237         || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0))
238             goto fail;
239 
240     if (len)
241         *len = strlen(nh->name);
242 
243     return nh->name;
244 
245  fail:
246     if (len)
247         *len = err;
248     return NULL;
249 }
250 
fdt_first_property_offset(const void * fdt,int nodeoffset)251 int fdt_first_property_offset(const void *fdt, int nodeoffset)
252 {
253     int offset;
254 
255     if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0)
256         return offset;
257 
258     return _nextprop(fdt, offset);
259 }
260 
fdt_next_property_offset(const void * fdt,int offset)261 int fdt_next_property_offset(const void *fdt, int offset)
262 {
263     if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0)
264         return offset;
265 
266     return _nextprop(fdt, offset);
267 }
268 
fdt_get_property_by_offset(const void * fdt,int offset,int * lenp)269 const struct fdt_property *fdt_get_property_by_offset(const void *fdt,
270                               int offset,
271                               int *lenp)
272 {
273     int err;
274     const struct fdt_property *prop;
275 
276     if ((err = _fdt_check_prop_offset(fdt, offset)) < 0) {
277         if (lenp)
278             *lenp = err;
279         return NULL;
280     }
281 
282     prop = _fdt_offset_ptr(fdt, offset);
283 
284     if (lenp)
285         *lenp = fdt32_to_cpu(prop->len);
286 
287     return prop;
288 }
289 
fdt_get_property_namelen(const void * fdt,int offset,const char * name,int namelen,int * lenp)290 const struct fdt_property *fdt_get_property_namelen(const void *fdt,
291                             int offset,
292                             const char *name,
293                             int namelen, int *lenp)
294 {
295     for (offset = fdt_first_property_offset(fdt, offset);
296          (offset >= 0);
297          (offset = fdt_next_property_offset(fdt, offset))) {
298         const struct fdt_property *prop;
299 
300         if (!(prop = fdt_get_property_by_offset(fdt, offset, lenp))) {
301             offset = -FDT_ERR_INTERNAL;
302             break;
303         }
304         if (_fdt_string_eq(fdt, fdt32_to_cpu(prop->nameoff),
305                    name, namelen))
306             return prop;
307     }
308 
309     if (lenp)
310         *lenp = offset;
311     return NULL;
312 }
313 
fdt_get_property(const void * fdt,int nodeoffset,const char * name,int * lenp)314 const struct fdt_property *fdt_get_property(const void *fdt,
315                         int nodeoffset,
316                         const char *name, int *lenp)
317 {
318     return fdt_get_property_namelen(fdt, nodeoffset, name,
319                     strlen(name), lenp);
320 }
321 
fdt_getprop_namelen(const void * fdt,int nodeoffset,const char * name,int namelen,int * lenp)322 const void *fdt_getprop_namelen(const void *fdt, int nodeoffset,
323                 const char *name, int namelen, int *lenp)
324 {
325     const struct fdt_property *prop;
326 
327     prop = fdt_get_property_namelen(fdt, nodeoffset, name, namelen, lenp);
328     if (! prop)
329         return NULL;
330 
331     return prop->data;
332 }
333 
fdt_getprop_by_offset(const void * fdt,int offset,const char ** namep,int * lenp)334 const void *fdt_getprop_by_offset(const void *fdt, int offset,
335                   const char **namep, int *lenp)
336 {
337     const struct fdt_property *prop;
338 
339     prop = fdt_get_property_by_offset(fdt, offset, lenp);
340     if (!prop)
341         return NULL;
342     if (namep)
343         *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
344     return prop->data;
345 }
346 
fdt_getprop(const void * fdt,int nodeoffset,const char * name,int * lenp)347 const void *fdt_getprop(const void *fdt, int nodeoffset,
348             const char *name, int *lenp)
349 {
350     return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp);
351 }
352 
fdt_get_phandle(const void * fdt,int nodeoffset)353 uint32_t fdt_get_phandle(const void *fdt, int nodeoffset)
354 {
355     const fdt32_t *php;
356     int len;
357 
358     /* FIXME: This is a bit sub-optimal, since we potentially scan
359      * over all the properties twice. */
360     php = fdt_getprop(fdt, nodeoffset, "phandle", &len);
361     if (!php || (len != sizeof(*php))) {
362         php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len);
363         if (!php || (len != sizeof(*php)))
364             return 0;
365     }
366 
367     return fdt32_to_cpu(*php);
368 }
369 
fdt_get_alias_namelen(const void * fdt,const char * name,int namelen)370 const char *fdt_get_alias_namelen(const void *fdt,
371                   const char *name, int namelen)
372 {
373     int aliasoffset;
374 
375     aliasoffset = fdt_path_offset(fdt, "/aliases");
376     if (aliasoffset < 0)
377         return NULL;
378 
379     return fdt_getprop_namelen(fdt, aliasoffset, name, namelen, NULL);
380 }
381 
fdt_get_alias(const void * fdt,const char * name)382 const char *fdt_get_alias(const void *fdt, const char *name)
383 {
384     return fdt_get_alias_namelen(fdt, name, strlen(name));
385 }
386 
fdt_get_path(const void * fdt,int nodeoffset,char * buf,int buflen)387 int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen)
388 {
389     int pdepth = 0, p = 0;
390     int offset, depth, namelen;
391     const char *name;
392 
393     FDT_CHECK_HEADER(fdt);
394 
395     if (buflen < 2)
396         return -FDT_ERR_NOSPACE;
397 
398     for (offset = 0, depth = 0;
399          (offset >= 0) && (offset <= nodeoffset);
400          offset = fdt_next_node(fdt, offset, &depth)) {
401         while (pdepth > depth) {
402             do {
403                 p--;
404             } while (buf[p-1] != '/');
405             pdepth--;
406         }
407 
408         if (pdepth >= depth) {
409             name = fdt_get_name(fdt, offset, &namelen);
410             if (!name)
411                 return namelen;
412             if ((p + namelen + 1) <= buflen) {
413                 memcpy(buf + p, name, namelen);
414                 p += namelen;
415                 buf[p++] = '/';
416                 pdepth++;
417             }
418         }
419 
420         if (offset == nodeoffset) {
421             if (pdepth < (depth + 1))
422                 return -FDT_ERR_NOSPACE;
423 
424             if (p > 1) /* special case so that root path is "/", not "" */
425                 p--;
426             buf[p] = '\0';
427             return 0;
428         }
429     }
430 
431     if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0))
432         return -FDT_ERR_BADOFFSET;
433     else if (offset == -FDT_ERR_BADOFFSET)
434         return -FDT_ERR_BADSTRUCTURE;
435 
436     return offset; /* error from fdt_next_node() */
437 }
438 
fdt_supernode_atdepth_offset(const void * fdt,int nodeoffset,int supernodedepth,int * nodedepth)439 int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset,
440                  int supernodedepth, int *nodedepth)
441 {
442     int offset, depth;
443     int supernodeoffset = -FDT_ERR_INTERNAL;
444 
445     FDT_CHECK_HEADER(fdt);
446 
447     if (supernodedepth < 0)
448         return -FDT_ERR_NOTFOUND;
449 
450     for (offset = 0, depth = 0;
451          (offset >= 0) && (offset <= nodeoffset);
452          offset = fdt_next_node(fdt, offset, &depth)) {
453         if (depth == supernodedepth)
454             supernodeoffset = offset;
455 
456         if (offset == nodeoffset) {
457             if (nodedepth)
458                 *nodedepth = depth;
459 
460             if (supernodedepth > depth)
461                 return -FDT_ERR_NOTFOUND;
462             else
463                 return supernodeoffset;
464         }
465     }
466 
467     if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0))
468         return -FDT_ERR_BADOFFSET;
469     else if (offset == -FDT_ERR_BADOFFSET)
470         return -FDT_ERR_BADSTRUCTURE;
471 
472     return offset; /* error from fdt_next_node() */
473 }
474 
fdt_node_depth(const void * fdt,int nodeoffset)475 int fdt_node_depth(const void *fdt, int nodeoffset)
476 {
477     int nodedepth;
478     int err;
479 
480     err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth);
481     if (err)
482         return (err < 0) ? err : -FDT_ERR_INTERNAL;
483     return nodedepth;
484 }
485 
fdt_parent_offset(const void * fdt,int nodeoffset)486 int fdt_parent_offset(const void *fdt, int nodeoffset)
487 {
488     int nodedepth = fdt_node_depth(fdt, nodeoffset);
489 
490     if (nodedepth < 0)
491         return nodedepth;
492     return fdt_supernode_atdepth_offset(fdt, nodeoffset,
493                         nodedepth - 1, NULL);
494 }
495 
fdt_node_offset_by_prop_value(const void * fdt,int startoffset,const char * propname,const void * propval,int proplen)496 int fdt_node_offset_by_prop_value(const void *fdt, int startoffset,
497                   const char *propname,
498                   const void *propval, int proplen)
499 {
500     int offset;
501     const void *val;
502     int len;
503 
504     FDT_CHECK_HEADER(fdt);
505 
506     /* FIXME: The algorithm here is pretty horrible: we scan each
507      * property of a node in fdt_getprop(), then if that didn't
508      * find what we want, we scan over them again making our way
509      * to the next node.  Still it's the easiest to implement
510      * approach; performance can come later. */
511     for (offset = fdt_next_node(fdt, startoffset, NULL);
512          offset >= 0;
513          offset = fdt_next_node(fdt, offset, NULL)) {
514         val = fdt_getprop(fdt, offset, propname, &len);
515         if (val && (len == proplen)
516             && (memcmp(val, propval, len) == 0))
517             return offset;
518     }
519 
520     return offset; /* error from fdt_next_node() */
521 }
522 
fdt_node_offset_by_phandle(const void * fdt,uint32_t phandle)523 int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle)
524 {
525     int offset;
526 
527     if ((phandle == 0) || (phandle == -1))
528         return -FDT_ERR_BADPHANDLE;
529 
530     FDT_CHECK_HEADER(fdt);
531 
532     /* FIXME: The algorithm here is pretty horrible: we
533      * potentially scan each property of a node in
534      * fdt_get_phandle(), then if that didn't find what
535      * we want, we scan over them again making our way to the next
536      * node.  Still it's the easiest to implement approach;
537      * performance can come later. */
538     for (offset = fdt_next_node(fdt, -1, NULL);
539          offset >= 0;
540          offset = fdt_next_node(fdt, offset, NULL)) {
541         if (fdt_get_phandle(fdt, offset) == phandle)
542             return offset;
543     }
544 
545     return offset; /* error from fdt_next_node() */
546 }
547 
fdt_stringlist_contains(const char * strlist,int listlen,const char * str)548 int fdt_stringlist_contains(const char *strlist, int listlen, const char *str)
549 {
550     int len = strlen(str);
551     const char *p;
552 
553     while (listlen >= len) {
554         if (memcmp(str, strlist, len+1) == 0)
555             return 1;
556         p = memchr(strlist, '\0', listlen);
557         if (!p)
558             return 0; /* malformed strlist.. */
559         listlen -= (p-strlist) + 1;
560         strlist = p + 1;
561     }
562     return 0;
563 }
564 
fdt_stringlist_count(const void * fdt,int nodeoffset,const char * property)565 int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property)
566 {
567     const char *list, *end;
568     int length, count = 0;
569 
570     list = fdt_getprop(fdt, nodeoffset, property, &length);
571     if (!list)
572         return length;
573 
574     end = list + length;
575 
576     while (list < end) {
577         length = strnlen(list, end - list) + 1;
578 
579         /* Abort if the last string isn't properly NUL-terminated. */
580         if (list + length > end)
581             return -FDT_ERR_BADVALUE;
582 
583         list += length;
584         count++;
585     }
586 
587     return count;
588 }
589 
fdt_stringlist_search(const void * fdt,int nodeoffset,const char * property,const char * string)590 int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property,
591               const char *string)
592 {
593     int length, len, idx = 0;
594     const char *list, *end;
595 
596     list = fdt_getprop(fdt, nodeoffset, property, &length);
597     if (!list)
598         return length;
599 
600     len = strlen(string) + 1;
601     end = list + length;
602 
603     while (list < end) {
604         length = strnlen(list, end - list) + 1;
605 
606         /* Abort if the last string isn't properly NUL-terminated. */
607         if (list + length > end)
608             return -FDT_ERR_BADVALUE;
609 
610         if (length == len && memcmp(list, string, length) == 0)
611             return idx;
612 
613         list += length;
614         idx++;
615     }
616 
617     return -FDT_ERR_NOTFOUND;
618 }
619 
fdt_stringlist_get(const void * fdt,int nodeoffset,const char * property,int idx,int * lenp)620 const char *fdt_stringlist_get(const void *fdt, int nodeoffset,
621                    const char *property, int idx,
622                    int *lenp)
623 {
624     const char *list, *end;
625     int length;
626 
627     list = fdt_getprop(fdt, nodeoffset, property, &length);
628     if (!list) {
629         if (lenp)
630             *lenp = length;
631 
632         return NULL;
633     }
634 
635     end = list + length;
636 
637     while (list < end) {
638         length = strnlen(list, end - list) + 1;
639 
640         /* Abort if the last string isn't properly NUL-terminated. */
641         if (list + length > end) {
642             if (lenp)
643                 *lenp = -FDT_ERR_BADVALUE;
644 
645             return NULL;
646         }
647 
648         if (idx == 0) {
649             if (lenp)
650                 *lenp = length - 1;
651 
652             return list;
653         }
654 
655         list += length;
656         idx--;
657     }
658 
659     if (lenp)
660         *lenp = -FDT_ERR_NOTFOUND;
661 
662     return NULL;
663 }
664 
fdt_node_check_compatible(const void * fdt,int nodeoffset,const char * compatible)665 int fdt_node_check_compatible(const void *fdt, int nodeoffset,
666                   const char *compatible)
667 {
668     const void *prop;
669     int len;
670 
671     prop = fdt_getprop(fdt, nodeoffset, "compatible", &len);
672     if (!prop)
673         return len;
674 
675     return !fdt_stringlist_contains(prop, len, compatible);
676 }
677 
fdt_node_offset_by_compatible(const void * fdt,int startoffset,const char * compatible)678 int fdt_node_offset_by_compatible(const void *fdt, int startoffset,
679                   const char *compatible)
680 {
681     int offset, err;
682 
683     FDT_CHECK_HEADER(fdt);
684 
685     /* FIXME: The algorithm here is pretty horrible: we scan each
686      * property of a node in fdt_node_check_compatible(), then if
687      * that didn't find what we want, we scan over them again
688      * making our way to the next node.  Still it's the easiest to
689      * implement approach; performance can come later. */
690     for (offset = fdt_next_node(fdt, startoffset, NULL);
691          offset >= 0;
692          offset = fdt_next_node(fdt, offset, NULL)) {
693         err = fdt_node_check_compatible(fdt, offset, compatible);
694         if ((err < 0) && (err != -FDT_ERR_NOTFOUND))
695             return err;
696         else if (err == 0)
697             return offset;
698     }
699 
700     return offset; /* error from fdt_next_node() */
701 }
702