1 // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) 2 /* 3 * libfdt - Flat Device Tree manipulation 4 * Copyright (C) 2006 David Gibson, IBM Corporation. 5 */ 6 #include "libfdt_env.h" 7 8 #include <fdt.h> 9 #include <libfdt.h> 10 11 #include "libfdt_internal.h" 12 13 static int fdt_nodename_eq_(const void *fdt, int offset, 14 const char *s, int len) 15 { 16 int olen; 17 const char *p = fdt_get_name(fdt, offset, &olen); 18 19 if (!p || olen < len) 20 /* short match */ 21 return 0; 22 23 if (memcmp(p, s, len) != 0) 24 return 0; 25 26 if (p[len] == '\0') 27 return 1; 28 else if (!memchr(s, '@', len) && (p[len] == '@')) 29 return 1; 30 else 31 return 0; 32 } 33 34 const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) 35 { 36 int32_t totalsize; 37 uint32_t absoffset; 38 size_t len; 39 int err; 40 const char *s, *n; 41 42 if (can_assume(VALID_INPUT)) { 43 s = (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; 44 45 if (lenp) 46 *lenp = strlen(s); 47 return s; 48 } 49 totalsize = fdt_ro_probe_(fdt); 50 err = totalsize; 51 if (totalsize < 0) 52 goto fail; 53 54 err = -FDT_ERR_BADOFFSET; 55 absoffset = stroffset + fdt_off_dt_strings(fdt); 56 if (absoffset >= (unsigned)totalsize) 57 goto fail; 58 len = totalsize - absoffset; 59 60 if (fdt_magic(fdt) == FDT_MAGIC) { 61 if (stroffset < 0) 62 goto fail; 63 if (can_assume(LATEST) || fdt_version(fdt) >= 17) { 64 if ((unsigned)stroffset >= fdt_size_dt_strings(fdt)) 65 goto fail; 66 if ((fdt_size_dt_strings(fdt) - stroffset) < len) 67 len = fdt_size_dt_strings(fdt) - stroffset; 68 } 69 } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { 70 unsigned int sw_stroffset = -stroffset; 71 72 if ((stroffset >= 0) || 73 (sw_stroffset > fdt_size_dt_strings(fdt))) 74 goto fail; 75 if (sw_stroffset < len) 76 len = sw_stroffset; 77 } else { 78 err = -FDT_ERR_INTERNAL; 79 goto fail; 80 } 81 82 s = (const char *)fdt + absoffset; 83 n = memchr(s, '\0', len); 84 if (!n) { 85 /* missing terminating NULL */ 86 err = -FDT_ERR_TRUNCATED; 87 goto fail; 88 } 89 90 if (lenp) 91 *lenp = n - s; 92 return s; 93 94 fail: 95 if (lenp) 96 *lenp = err; 97 return NULL; 98 } 99 100 const char *fdt_string(const void *fdt, int stroffset) 101 { 102 return fdt_get_string(fdt, stroffset, NULL); 103 } 104 105 static int fdt_string_eq_(const void *fdt, int stroffset, 106 const char *s, int len) 107 { 108 int slen; 109 const char *p = fdt_get_string(fdt, stroffset, &slen); 110 111 return p && (slen == len) && (memcmp(p, s, len) == 0); 112 } 113 114 int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) 115 { 116 uint32_t max = 0; 117 int offset = -1; 118 119 while (true) { 120 uint32_t value; 121 122 offset = fdt_next_node(fdt, offset, NULL); 123 if (offset < 0) { 124 if (offset == -FDT_ERR_NOTFOUND) 125 break; 126 127 return offset; 128 } 129 130 value = fdt_get_phandle(fdt, offset); 131 132 if (value > max) 133 max = value; 134 } 135 136 if (phandle) 137 *phandle = max; 138 139 return 0; 140 } 141 142 int fdt_generate_phandle(const void *fdt, uint32_t *phandle) 143 { 144 uint32_t max; 145 int err; 146 147 err = fdt_find_max_phandle(fdt, &max); 148 if (err < 0) 149 return err; 150 151 if (max == FDT_MAX_PHANDLE) 152 return -FDT_ERR_NOPHANDLES; 153 154 if (phandle) 155 *phandle = max + 1; 156 157 return 0; 158 } 159 160 static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) 161 { 162 unsigned int offset = n * sizeof(struct fdt_reserve_entry); 163 unsigned int absoffset = fdt_off_mem_rsvmap(fdt) + offset; 164 165 if (!can_assume(VALID_INPUT)) { 166 if (absoffset < fdt_off_mem_rsvmap(fdt)) 167 return NULL; 168 if (absoffset > fdt_totalsize(fdt) - 169 sizeof(struct fdt_reserve_entry)) 170 return NULL; 171 } 172 return fdt_mem_rsv_(fdt, n); 173 } 174 175 int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) 176 { 177 const struct fdt_reserve_entry *re; 178 179 FDT_RO_PROBE(fdt); 180 re = fdt_mem_rsv(fdt, n); 181 if (!can_assume(VALID_INPUT) && !re) 182 return -FDT_ERR_BADOFFSET; 183 184 *address = fdt64_ld(&re->address); 185 *size = fdt64_ld(&re->size); 186 return 0; 187 } 188 189 int fdt_num_mem_rsv(const void *fdt) 190 { 191 int i; 192 const struct fdt_reserve_entry *re; 193 194 for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { 195 if (fdt64_ld(&re->size) == 0) 196 return i; 197 } 198 return -FDT_ERR_TRUNCATED; 199 } 200 201 static int nextprop_(const void *fdt, int offset) 202 { 203 uint32_t tag; 204 int nextoffset; 205 206 do { 207 tag = fdt_next_tag(fdt, offset, &nextoffset); 208 209 switch (tag) { 210 case FDT_END: 211 if (nextoffset >= 0) 212 return -FDT_ERR_BADSTRUCTURE; 213 else 214 return nextoffset; 215 216 case FDT_PROP: 217 return offset; 218 } 219 offset = nextoffset; 220 } while (tag == FDT_NOP); 221 222 return -FDT_ERR_NOTFOUND; 223 } 224 225 int fdt_subnode_offset_namelen(const void *fdt, int offset, 226 const char *name, int namelen) 227 { 228 int depth; 229 230 FDT_RO_PROBE(fdt); 231 232 for (depth = 0; 233 (offset >= 0) && (depth >= 0); 234 offset = fdt_next_node(fdt, offset, &depth)) 235 if ((depth == 1) 236 && fdt_nodename_eq_(fdt, offset, name, namelen)) 237 return offset; 238 239 if (depth < 0) 240 return -FDT_ERR_NOTFOUND; 241 return offset; /* error */ 242 } 243 244 int fdt_subnode_offset(const void *fdt, int parentoffset, 245 const char *name) 246 { 247 return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); 248 } 249 250 int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) 251 { 252 const char *end = path + namelen; 253 const char *p = path; 254 int offset = 0; 255 256 FDT_RO_PROBE(fdt); 257 258 /* see if we have an alias */ 259 if (*path != '/') { 260 const char *q = memchr(path, '/', end - p); 261 262 if (!q) 263 q = end; 264 265 p = fdt_get_alias_namelen(fdt, p, q - p); 266 if (!p) 267 return -FDT_ERR_BADPATH; 268 offset = fdt_path_offset(fdt, p); 269 270 p = q; 271 } 272 273 while (p < end) { 274 const char *q; 275 276 while (*p == '/') { 277 p++; 278 if (p == end) 279 return offset; 280 } 281 q = memchr(p, '/', end - p); 282 if (! q) 283 q = end; 284 285 offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); 286 if (offset < 0) 287 return offset; 288 289 p = q; 290 } 291 292 return offset; 293 } 294 295 int fdt_path_offset(const void *fdt, const char *path) 296 { 297 return fdt_path_offset_namelen(fdt, path, strlen(path)); 298 } 299 300 const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) 301 { 302 const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); 303 const char *nameptr; 304 int err; 305 306 if (((err = fdt_ro_probe_(fdt)) < 0) 307 || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0)) 308 goto fail; 309 310 nameptr = nh->name; 311 312 if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { 313 /* 314 * For old FDT versions, match the naming conventions of V16: 315 * give only the leaf name (after all /). The actual tree 316 * contents are loosely checked. 317 */ 318 const char *leaf; 319 leaf = strrchr(nameptr, '/'); 320 if (leaf == NULL) { 321 err = -FDT_ERR_BADSTRUCTURE; 322 goto fail; 323 } 324 nameptr = leaf+1; 325 } 326 327 if (len) 328 *len = strlen(nameptr); 329 330 return nameptr; 331 332 fail: 333 if (len) 334 *len = err; 335 return NULL; 336 } 337 338 int fdt_first_property_offset(const void *fdt, int nodeoffset) 339 { 340 int offset; 341 342 if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) 343 return offset; 344 345 return nextprop_(fdt, offset); 346 } 347 348 int fdt_next_property_offset(const void *fdt, int offset) 349 { 350 if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) 351 return offset; 352 353 return nextprop_(fdt, offset); 354 } 355 356 static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, 357 int offset, 358 int *lenp) 359 { 360 int err; 361 const struct fdt_property *prop; 362 363 if (!can_assume(VALID_INPUT) && 364 (err = fdt_check_prop_offset_(fdt, offset)) < 0) { 365 if (lenp) 366 *lenp = err; 367 return NULL; 368 } 369 370 prop = fdt_offset_ptr_(fdt, offset); 371 372 if (lenp) 373 *lenp = fdt32_ld(&prop->len); 374 375 return prop; 376 } 377 378 const struct fdt_property *fdt_get_property_by_offset(const void *fdt, 379 int offset, 380 int *lenp) 381 { 382 /* Prior to version 16, properties may need realignment 383 * and this API does not work. fdt_getprop_*() will, however. */ 384 385 if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { 386 if (lenp) 387 *lenp = -FDT_ERR_BADVERSION; 388 return NULL; 389 } 390 391 return fdt_get_property_by_offset_(fdt, offset, lenp); 392 } 393 394 static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, 395 int offset, 396 const char *name, 397 int namelen, 398 int *lenp, 399 int *poffset) 400 { 401 for (offset = fdt_first_property_offset(fdt, offset); 402 (offset >= 0); 403 (offset = fdt_next_property_offset(fdt, offset))) { 404 const struct fdt_property *prop; 405 406 prop = fdt_get_property_by_offset_(fdt, offset, lenp); 407 if (!can_assume(LIBFDT_FLAWLESS) && !prop) { 408 offset = -FDT_ERR_INTERNAL; 409 break; 410 } 411 if (fdt_string_eq_(fdt, fdt32_ld(&prop->nameoff), 412 name, namelen)) { 413 if (poffset) 414 *poffset = offset; 415 return prop; 416 } 417 } 418 419 if (lenp) 420 *lenp = offset; 421 return NULL; 422 } 423 424 425 const struct fdt_property *fdt_get_property_namelen(const void *fdt, 426 int offset, 427 const char *name, 428 int namelen, int *lenp) 429 { 430 /* Prior to version 16, properties may need realignment 431 * and this API does not work. fdt_getprop_*() will, however. */ 432 if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { 433 if (lenp) 434 *lenp = -FDT_ERR_BADVERSION; 435 return NULL; 436 } 437 438 return fdt_get_property_namelen_(fdt, offset, name, namelen, lenp, 439 NULL); 440 } 441 442 443 const struct fdt_property *fdt_get_property(const void *fdt, 444 int nodeoffset, 445 const char *name, int *lenp) 446 { 447 return fdt_get_property_namelen(fdt, nodeoffset, name, 448 strlen(name), lenp); 449 } 450 451 const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, 452 const char *name, int namelen, int *lenp) 453 { 454 int poffset; 455 const struct fdt_property *prop; 456 457 prop = fdt_get_property_namelen_(fdt, nodeoffset, name, namelen, lenp, 458 &poffset); 459 if (!prop) 460 return NULL; 461 462 /* Handle realignment */ 463 if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && 464 (poffset + sizeof(*prop)) % 8 && fdt32_ld(&prop->len) >= 8) 465 return prop->data + 4; 466 return prop->data; 467 } 468 469 const void *fdt_getprop_by_offset(const void *fdt, int offset, 470 const char **namep, int *lenp) 471 { 472 const struct fdt_property *prop; 473 474 prop = fdt_get_property_by_offset_(fdt, offset, lenp); 475 if (!prop) 476 return NULL; 477 if (namep) { 478 const char *name; 479 int namelen; 480 481 if (!can_assume(VALID_INPUT)) { 482 name = fdt_get_string(fdt, fdt32_ld(&prop->nameoff), 483 &namelen); 484 if (!name) { 485 if (lenp) 486 *lenp = namelen; 487 return NULL; 488 } 489 *namep = name; 490 } else { 491 *namep = fdt_string(fdt, fdt32_ld(&prop->nameoff)); 492 } 493 } 494 495 /* Handle realignment */ 496 if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && 497 (offset + sizeof(*prop)) % 8 && fdt32_ld(&prop->len) >= 8) 498 return prop->data + 4; 499 return prop->data; 500 } 501 502 const void *fdt_getprop(const void *fdt, int nodeoffset, 503 const char *name, int *lenp) 504 { 505 return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); 506 } 507 508 uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) 509 { 510 const fdt32_t *php; 511 int len; 512 513 /* FIXME: This is a bit sub-optimal, since we potentially scan 514 * over all the properties twice. */ 515 php = fdt_getprop(fdt, nodeoffset, "phandle", &len); 516 if (!php || (len != sizeof(*php))) { 517 php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); 518 if (!php || (len != sizeof(*php))) 519 return 0; 520 } 521 522 return fdt32_ld(php); 523 } 524 525 const char *fdt_get_alias_namelen(const void *fdt, 526 const char *name, int namelen) 527 { 528 int aliasoffset; 529 530 aliasoffset = fdt_path_offset(fdt, "/aliases"); 531 if (aliasoffset < 0) 532 return NULL; 533 534 return fdt_getprop_namelen(fdt, aliasoffset, name, namelen, NULL); 535 } 536 537 const char *fdt_get_alias(const void *fdt, const char *name) 538 { 539 return fdt_get_alias_namelen(fdt, name, strlen(name)); 540 } 541 542 int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) 543 { 544 int pdepth = 0, p = 0; 545 int offset, depth, namelen; 546 const char *name; 547 548 FDT_RO_PROBE(fdt); 549 550 if (buflen < 2) 551 return -FDT_ERR_NOSPACE; 552 553 for (offset = 0, depth = 0; 554 (offset >= 0) && (offset <= nodeoffset); 555 offset = fdt_next_node(fdt, offset, &depth)) { 556 while (pdepth > depth) { 557 do { 558 p--; 559 } while (buf[p-1] != '/'); 560 pdepth--; 561 } 562 563 if (pdepth >= depth) { 564 name = fdt_get_name(fdt, offset, &namelen); 565 if (!name) 566 return namelen; 567 if ((p + namelen + 1) <= buflen) { 568 memcpy(buf + p, name, namelen); 569 p += namelen; 570 buf[p++] = '/'; 571 pdepth++; 572 } 573 } 574 575 if (offset == nodeoffset) { 576 if (pdepth < (depth + 1)) 577 return -FDT_ERR_NOSPACE; 578 579 if (p > 1) /* special case so that root path is "/", not "" */ 580 p--; 581 buf[p] = '\0'; 582 return 0; 583 } 584 } 585 586 if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) 587 return -FDT_ERR_BADOFFSET; 588 else if (offset == -FDT_ERR_BADOFFSET) 589 return -FDT_ERR_BADSTRUCTURE; 590 591 return offset; /* error from fdt_next_node() */ 592 } 593 594 int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, 595 int supernodedepth, int *nodedepth) 596 { 597 int offset, depth; 598 int supernodeoffset = -FDT_ERR_INTERNAL; 599 600 FDT_RO_PROBE(fdt); 601 602 if (supernodedepth < 0) 603 return -FDT_ERR_NOTFOUND; 604 605 for (offset = 0, depth = 0; 606 (offset >= 0) && (offset <= nodeoffset); 607 offset = fdt_next_node(fdt, offset, &depth)) { 608 if (depth == supernodedepth) 609 supernodeoffset = offset; 610 611 if (offset == nodeoffset) { 612 if (nodedepth) 613 *nodedepth = depth; 614 615 if (supernodedepth > depth) 616 return -FDT_ERR_NOTFOUND; 617 else 618 return supernodeoffset; 619 } 620 } 621 622 if (!can_assume(VALID_INPUT)) { 623 if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) 624 return -FDT_ERR_BADOFFSET; 625 else if (offset == -FDT_ERR_BADOFFSET) 626 return -FDT_ERR_BADSTRUCTURE; 627 } 628 629 return offset; /* error from fdt_next_node() */ 630 } 631 632 int fdt_node_depth(const void *fdt, int nodeoffset) 633 { 634 int nodedepth; 635 int err; 636 637 err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); 638 if (err) 639 return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err : 640 -FDT_ERR_INTERNAL; 641 return nodedepth; 642 } 643 644 int fdt_parent_offset(const void *fdt, int nodeoffset) 645 { 646 int nodedepth = fdt_node_depth(fdt, nodeoffset); 647 648 if (nodedepth < 0) 649 return nodedepth; 650 return fdt_supernode_atdepth_offset(fdt, nodeoffset, 651 nodedepth - 1, NULL); 652 } 653 654 int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, 655 const char *propname, 656 const void *propval, int proplen) 657 { 658 int offset; 659 const void *val; 660 int len; 661 662 FDT_RO_PROBE(fdt); 663 664 /* FIXME: The algorithm here is pretty horrible: we scan each 665 * property of a node in fdt_getprop(), then if that didn't 666 * find what we want, we scan over them again making our way 667 * to the next node. Still it's the easiest to implement 668 * approach; performance can come later. */ 669 for (offset = fdt_next_node(fdt, startoffset, NULL); 670 offset >= 0; 671 offset = fdt_next_node(fdt, offset, NULL)) { 672 val = fdt_getprop(fdt, offset, propname, &len); 673 if (val && (len == proplen) 674 && (memcmp(val, propval, len) == 0)) 675 return offset; 676 } 677 678 return offset; /* error from fdt_next_node() */ 679 } 680 681 int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) 682 { 683 int offset; 684 685 if ((phandle == 0) || (phandle == ~0U)) 686 return -FDT_ERR_BADPHANDLE; 687 688 FDT_RO_PROBE(fdt); 689 690 /* FIXME: The algorithm here is pretty horrible: we 691 * potentially scan each property of a node in 692 * fdt_get_phandle(), then if that didn't find what 693 * we want, we scan over them again making our way to the next 694 * node. Still it's the easiest to implement approach; 695 * performance can come later. */ 696 for (offset = fdt_next_node(fdt, -1, NULL); 697 offset >= 0; 698 offset = fdt_next_node(fdt, offset, NULL)) { 699 if (fdt_get_phandle(fdt, offset) == phandle) 700 return offset; 701 } 702 703 return offset; /* error from fdt_next_node() */ 704 } 705 706 int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) 707 { 708 int len = strlen(str); 709 const char *p; 710 711 while (listlen >= len) { 712 if (memcmp(str, strlist, len+1) == 0) 713 return 1; 714 p = memchr(strlist, '\0', listlen); 715 if (!p) 716 return 0; /* malformed strlist.. */ 717 listlen -= (p-strlist) + 1; 718 strlist = p + 1; 719 } 720 return 0; 721 } 722 723 int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) 724 { 725 const char *list, *end; 726 int length, count = 0; 727 728 list = fdt_getprop(fdt, nodeoffset, property, &length); 729 if (!list) 730 return length; 731 732 end = list + length; 733 734 while (list < end) { 735 length = strnlen(list, end - list) + 1; 736 737 /* Abort if the last string isn't properly NUL-terminated. */ 738 if (list + length > end) 739 return -FDT_ERR_BADVALUE; 740 741 list += length; 742 count++; 743 } 744 745 return count; 746 } 747 748 int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, 749 const char *string) 750 { 751 int length, len, idx = 0; 752 const char *list, *end; 753 754 list = fdt_getprop(fdt, nodeoffset, property, &length); 755 if (!list) 756 return length; 757 758 len = strlen(string) + 1; 759 end = list + length; 760 761 while (list < end) { 762 length = strnlen(list, end - list) + 1; 763 764 /* Abort if the last string isn't properly NUL-terminated. */ 765 if (list + length > end) 766 return -FDT_ERR_BADVALUE; 767 768 if (length == len && memcmp(list, string, length) == 0) 769 return idx; 770 771 list += length; 772 idx++; 773 } 774 775 return -FDT_ERR_NOTFOUND; 776 } 777 778 const char *fdt_stringlist_get(const void *fdt, int nodeoffset, 779 const char *property, int idx, 780 int *lenp) 781 { 782 const char *list, *end; 783 int length; 784 785 list = fdt_getprop(fdt, nodeoffset, property, &length); 786 if (!list) { 787 if (lenp) 788 *lenp = length; 789 790 return NULL; 791 } 792 793 end = list + length; 794 795 while (list < end) { 796 length = strnlen(list, end - list) + 1; 797 798 /* Abort if the last string isn't properly NUL-terminated. */ 799 if (list + length > end) { 800 if (lenp) 801 *lenp = -FDT_ERR_BADVALUE; 802 803 return NULL; 804 } 805 806 if (idx == 0) { 807 if (lenp) 808 *lenp = length - 1; 809 810 return list; 811 } 812 813 list += length; 814 idx--; 815 } 816 817 if (lenp) 818 *lenp = -FDT_ERR_NOTFOUND; 819 820 return NULL; 821 } 822 823 int fdt_node_check_compatible(const void *fdt, int nodeoffset, 824 const char *compatible) 825 { 826 const void *prop; 827 int len; 828 829 prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); 830 if (!prop) 831 return len; 832 833 return !fdt_stringlist_contains(prop, len, compatible); 834 } 835 836 int fdt_node_offset_by_compatible(const void *fdt, int startoffset, 837 const char *compatible) 838 { 839 int offset, err; 840 841 FDT_RO_PROBE(fdt); 842 843 /* FIXME: The algorithm here is pretty horrible: we scan each 844 * property of a node in fdt_node_check_compatible(), then if 845 * that didn't find what we want, we scan over them again 846 * making our way to the next node. Still it's the easiest to 847 * implement approach; performance can come later. */ 848 for (offset = fdt_next_node(fdt, startoffset, NULL); 849 offset >= 0; 850 offset = fdt_next_node(fdt, offset, NULL)) { 851 err = fdt_node_check_compatible(fdt, offset, compatible); 852 if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) 853 return err; 854 else if (err == 0) 855 return offset; 856 } 857 858 return offset; /* error from fdt_next_node() */ 859 } 860