1243ce5d5SMadhukar Pappireddy // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) 2630b011fSAntonio Nino Diaz /* 3630b011fSAntonio Nino Diaz * libfdt - Flat Device Tree manipulation 4630b011fSAntonio Nino Diaz * Copyright (C) 2006 David Gibson, IBM Corporation. 5630b011fSAntonio Nino Diaz */ 6630b011fSAntonio Nino Diaz #include "libfdt_env.h" 7630b011fSAntonio Nino Diaz 8630b011fSAntonio Nino Diaz #include <fdt.h> 9630b011fSAntonio Nino Diaz #include <libfdt.h> 10630b011fSAntonio Nino Diaz 11630b011fSAntonio Nino Diaz #include "libfdt_internal.h" 12630b011fSAntonio Nino Diaz 13243ce5d5SMadhukar Pappireddy /* 14243ce5d5SMadhukar Pappireddy * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks 15243ce5d5SMadhukar Pappireddy * that the given buffer contains what appears to be a flattened 16243ce5d5SMadhukar Pappireddy * device tree with sane information in its header. 17243ce5d5SMadhukar Pappireddy */ 18243ce5d5SMadhukar Pappireddy int32_t fdt_ro_probe_(const void *fdt) 19630b011fSAntonio Nino Diaz { 20243ce5d5SMadhukar Pappireddy uint32_t totalsize = fdt_totalsize(fdt); 21243ce5d5SMadhukar Pappireddy 22243ce5d5SMadhukar Pappireddy if (can_assume(VALID_DTB)) 23243ce5d5SMadhukar Pappireddy return totalsize; 24243ce5d5SMadhukar Pappireddy 25*94b2f94bSDaniel Boulby /* The device tree must be at an 8-byte aligned address */ 26*94b2f94bSDaniel Boulby if ((uintptr_t)fdt & 7) 27*94b2f94bSDaniel Boulby return -FDT_ERR_ALIGNMENT; 28*94b2f94bSDaniel Boulby 29630b011fSAntonio Nino Diaz if (fdt_magic(fdt) == FDT_MAGIC) { 30630b011fSAntonio Nino Diaz /* Complete tree */ 31243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST)) { 32630b011fSAntonio Nino Diaz if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) 33630b011fSAntonio Nino Diaz return -FDT_ERR_BADVERSION; 34243ce5d5SMadhukar Pappireddy if (fdt_last_comp_version(fdt) > 35243ce5d5SMadhukar Pappireddy FDT_LAST_SUPPORTED_VERSION) 36630b011fSAntonio Nino Diaz return -FDT_ERR_BADVERSION; 37243ce5d5SMadhukar Pappireddy } 38630b011fSAntonio Nino Diaz } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { 39630b011fSAntonio Nino Diaz /* Unfinished sequential-write blob */ 40243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0) 41630b011fSAntonio Nino Diaz return -FDT_ERR_BADSTATE; 42630b011fSAntonio Nino Diaz } else { 43630b011fSAntonio Nino Diaz return -FDT_ERR_BADMAGIC; 44630b011fSAntonio Nino Diaz } 45630b011fSAntonio Nino Diaz 46243ce5d5SMadhukar Pappireddy if (totalsize < INT32_MAX) 47243ce5d5SMadhukar Pappireddy return totalsize; 48243ce5d5SMadhukar Pappireddy else 49243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 50243ce5d5SMadhukar Pappireddy } 51243ce5d5SMadhukar Pappireddy 52243ce5d5SMadhukar Pappireddy static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) 53243ce5d5SMadhukar Pappireddy { 54243ce5d5SMadhukar Pappireddy return (off >= hdrsize) && (off <= totalsize); 55243ce5d5SMadhukar Pappireddy } 56243ce5d5SMadhukar Pappireddy 57243ce5d5SMadhukar Pappireddy static int check_block_(uint32_t hdrsize, uint32_t totalsize, 58243ce5d5SMadhukar Pappireddy uint32_t base, uint32_t size) 59243ce5d5SMadhukar Pappireddy { 60243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, totalsize, base)) 61243ce5d5SMadhukar Pappireddy return 0; /* block start out of bounds */ 62243ce5d5SMadhukar Pappireddy if ((base + size) < base) 63243ce5d5SMadhukar Pappireddy return 0; /* overflow */ 64243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, totalsize, base + size)) 65243ce5d5SMadhukar Pappireddy return 0; /* block end out of bounds */ 66243ce5d5SMadhukar Pappireddy return 1; 67243ce5d5SMadhukar Pappireddy } 68243ce5d5SMadhukar Pappireddy 69243ce5d5SMadhukar Pappireddy size_t fdt_header_size_(uint32_t version) 70243ce5d5SMadhukar Pappireddy { 71243ce5d5SMadhukar Pappireddy if (version <= 1) 72243ce5d5SMadhukar Pappireddy return FDT_V1_SIZE; 73243ce5d5SMadhukar Pappireddy else if (version <= 2) 74243ce5d5SMadhukar Pappireddy return FDT_V2_SIZE; 75243ce5d5SMadhukar Pappireddy else if (version <= 3) 76243ce5d5SMadhukar Pappireddy return FDT_V3_SIZE; 77243ce5d5SMadhukar Pappireddy else if (version <= 16) 78243ce5d5SMadhukar Pappireddy return FDT_V16_SIZE; 79243ce5d5SMadhukar Pappireddy else 80243ce5d5SMadhukar Pappireddy return FDT_V17_SIZE; 81243ce5d5SMadhukar Pappireddy } 82243ce5d5SMadhukar Pappireddy 83243ce5d5SMadhukar Pappireddy size_t fdt_header_size(const void *fdt) 84243ce5d5SMadhukar Pappireddy { 85243ce5d5SMadhukar Pappireddy return can_assume(LATEST) ? FDT_V17_SIZE : 86243ce5d5SMadhukar Pappireddy fdt_header_size_(fdt_version(fdt)); 87243ce5d5SMadhukar Pappireddy } 88243ce5d5SMadhukar Pappireddy 89243ce5d5SMadhukar Pappireddy int fdt_check_header(const void *fdt) 90243ce5d5SMadhukar Pappireddy { 91243ce5d5SMadhukar Pappireddy size_t hdrsize; 92243ce5d5SMadhukar Pappireddy 93*94b2f94bSDaniel Boulby /* The device tree must be at an 8-byte aligned address */ 94*94b2f94bSDaniel Boulby if ((uintptr_t)fdt & 7) 95*94b2f94bSDaniel Boulby return -FDT_ERR_ALIGNMENT; 96*94b2f94bSDaniel Boulby 97243ce5d5SMadhukar Pappireddy if (fdt_magic(fdt) != FDT_MAGIC) 98243ce5d5SMadhukar Pappireddy return -FDT_ERR_BADMAGIC; 99243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST)) { 100243ce5d5SMadhukar Pappireddy if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) 101243ce5d5SMadhukar Pappireddy || (fdt_last_comp_version(fdt) > 102243ce5d5SMadhukar Pappireddy FDT_LAST_SUPPORTED_VERSION)) 103243ce5d5SMadhukar Pappireddy return -FDT_ERR_BADVERSION; 104243ce5d5SMadhukar Pappireddy if (fdt_version(fdt) < fdt_last_comp_version(fdt)) 105243ce5d5SMadhukar Pappireddy return -FDT_ERR_BADVERSION; 106243ce5d5SMadhukar Pappireddy } 107243ce5d5SMadhukar Pappireddy hdrsize = fdt_header_size(fdt); 108243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB)) { 109243ce5d5SMadhukar Pappireddy 110243ce5d5SMadhukar Pappireddy if ((fdt_totalsize(fdt) < hdrsize) 111243ce5d5SMadhukar Pappireddy || (fdt_totalsize(fdt) > INT_MAX)) 112243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 113243ce5d5SMadhukar Pappireddy 114243ce5d5SMadhukar Pappireddy /* Bounds check memrsv block */ 115243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, fdt_totalsize(fdt), 116243ce5d5SMadhukar Pappireddy fdt_off_mem_rsvmap(fdt))) 117243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 118243ce5d5SMadhukar Pappireddy } 119243ce5d5SMadhukar Pappireddy 120243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB)) { 121243ce5d5SMadhukar Pappireddy /* Bounds check structure block */ 122243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST) && fdt_version(fdt) < 17) { 123243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, fdt_totalsize(fdt), 124243ce5d5SMadhukar Pappireddy fdt_off_dt_struct(fdt))) 125243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 126243ce5d5SMadhukar Pappireddy } else { 127243ce5d5SMadhukar Pappireddy if (!check_block_(hdrsize, fdt_totalsize(fdt), 128243ce5d5SMadhukar Pappireddy fdt_off_dt_struct(fdt), 129243ce5d5SMadhukar Pappireddy fdt_size_dt_struct(fdt))) 130243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 131243ce5d5SMadhukar Pappireddy } 132243ce5d5SMadhukar Pappireddy 133243ce5d5SMadhukar Pappireddy /* Bounds check strings block */ 134243ce5d5SMadhukar Pappireddy if (!check_block_(hdrsize, fdt_totalsize(fdt), 135243ce5d5SMadhukar Pappireddy fdt_off_dt_strings(fdt), 136243ce5d5SMadhukar Pappireddy fdt_size_dt_strings(fdt))) 137243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 138243ce5d5SMadhukar Pappireddy } 139243ce5d5SMadhukar Pappireddy 140630b011fSAntonio Nino Diaz return 0; 141630b011fSAntonio Nino Diaz } 142630b011fSAntonio Nino Diaz 143630b011fSAntonio Nino Diaz const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) 144630b011fSAntonio Nino Diaz { 1453b456661SAndre Przywara unsigned int uoffset = offset; 1463b456661SAndre Przywara unsigned int absoffset = offset + fdt_off_dt_struct(fdt); 1473b456661SAndre Przywara 1483b456661SAndre Przywara if (offset < 0) 1493b456661SAndre Przywara return NULL; 150630b011fSAntonio Nino Diaz 151243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_INPUT)) 1523b456661SAndre Przywara if ((absoffset < uoffset) 153630b011fSAntonio Nino Diaz || ((absoffset + len) < absoffset) 154630b011fSAntonio Nino Diaz || (absoffset + len) > fdt_totalsize(fdt)) 155630b011fSAntonio Nino Diaz return NULL; 156630b011fSAntonio Nino Diaz 157243ce5d5SMadhukar Pappireddy if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) 1583b456661SAndre Przywara if (((uoffset + len) < uoffset) 159630b011fSAntonio Nino Diaz || ((offset + len) > fdt_size_dt_struct(fdt))) 160630b011fSAntonio Nino Diaz return NULL; 161630b011fSAntonio Nino Diaz 162630b011fSAntonio Nino Diaz return fdt_offset_ptr_(fdt, offset); 163630b011fSAntonio Nino Diaz } 164630b011fSAntonio Nino Diaz 165630b011fSAntonio Nino Diaz uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) 166630b011fSAntonio Nino Diaz { 167630b011fSAntonio Nino Diaz const fdt32_t *tagp, *lenp; 168630b011fSAntonio Nino Diaz uint32_t tag; 169630b011fSAntonio Nino Diaz int offset = startoffset; 170630b011fSAntonio Nino Diaz const char *p; 171630b011fSAntonio Nino Diaz 172630b011fSAntonio Nino Diaz *nextoffset = -FDT_ERR_TRUNCATED; 173630b011fSAntonio Nino Diaz tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); 174243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !tagp) 175630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 176630b011fSAntonio Nino Diaz tag = fdt32_to_cpu(*tagp); 177630b011fSAntonio Nino Diaz offset += FDT_TAGSIZE; 178630b011fSAntonio Nino Diaz 179630b011fSAntonio Nino Diaz *nextoffset = -FDT_ERR_BADSTRUCTURE; 180630b011fSAntonio Nino Diaz switch (tag) { 181630b011fSAntonio Nino Diaz case FDT_BEGIN_NODE: 182630b011fSAntonio Nino Diaz /* skip name */ 183630b011fSAntonio Nino Diaz do { 184630b011fSAntonio Nino Diaz p = fdt_offset_ptr(fdt, offset++, 1); 185630b011fSAntonio Nino Diaz } while (p && (*p != '\0')); 186243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !p) 187630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 188630b011fSAntonio Nino Diaz break; 189630b011fSAntonio Nino Diaz 190630b011fSAntonio Nino Diaz case FDT_PROP: 191630b011fSAntonio Nino Diaz lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); 192243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !lenp) 193630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 194630b011fSAntonio Nino Diaz /* skip-name offset, length and value */ 195630b011fSAntonio Nino Diaz offset += sizeof(struct fdt_property) - FDT_TAGSIZE 196630b011fSAntonio Nino Diaz + fdt32_to_cpu(*lenp); 197243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST) && 198243ce5d5SMadhukar Pappireddy fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 && 199630b011fSAntonio Nino Diaz ((offset - fdt32_to_cpu(*lenp)) % 8) != 0) 200630b011fSAntonio Nino Diaz offset += 4; 201630b011fSAntonio Nino Diaz break; 202630b011fSAntonio Nino Diaz 203630b011fSAntonio Nino Diaz case FDT_END: 204630b011fSAntonio Nino Diaz case FDT_END_NODE: 205630b011fSAntonio Nino Diaz case FDT_NOP: 206630b011fSAntonio Nino Diaz break; 207630b011fSAntonio Nino Diaz 208630b011fSAntonio Nino Diaz default: 209630b011fSAntonio Nino Diaz return FDT_END; 210630b011fSAntonio Nino Diaz } 211630b011fSAntonio Nino Diaz 212630b011fSAntonio Nino Diaz if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) 213630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 214630b011fSAntonio Nino Diaz 215630b011fSAntonio Nino Diaz *nextoffset = FDT_TAGALIGN(offset); 216630b011fSAntonio Nino Diaz return tag; 217630b011fSAntonio Nino Diaz } 218630b011fSAntonio Nino Diaz 219630b011fSAntonio Nino Diaz int fdt_check_node_offset_(const void *fdt, int offset) 220630b011fSAntonio Nino Diaz { 2213b456661SAndre Przywara if (!can_assume(VALID_INPUT) 2223b456661SAndre Przywara && ((offset < 0) || (offset % FDT_TAGSIZE))) 2233b456661SAndre Przywara return -FDT_ERR_BADOFFSET; 2243b456661SAndre Przywara 2253b456661SAndre Przywara if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) 226630b011fSAntonio Nino Diaz return -FDT_ERR_BADOFFSET; 227630b011fSAntonio Nino Diaz 228630b011fSAntonio Nino Diaz return offset; 229630b011fSAntonio Nino Diaz } 230630b011fSAntonio Nino Diaz 231630b011fSAntonio Nino Diaz int fdt_check_prop_offset_(const void *fdt, int offset) 232630b011fSAntonio Nino Diaz { 2333b456661SAndre Przywara if (!can_assume(VALID_INPUT) 2343b456661SAndre Przywara && ((offset < 0) || (offset % FDT_TAGSIZE))) 2353b456661SAndre Przywara return -FDT_ERR_BADOFFSET; 2363b456661SAndre Przywara 2373b456661SAndre Przywara if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) 238630b011fSAntonio Nino Diaz return -FDT_ERR_BADOFFSET; 239630b011fSAntonio Nino Diaz 240630b011fSAntonio Nino Diaz return offset; 241630b011fSAntonio Nino Diaz } 242630b011fSAntonio Nino Diaz 243630b011fSAntonio Nino Diaz int fdt_next_node(const void *fdt, int offset, int *depth) 244630b011fSAntonio Nino Diaz { 245630b011fSAntonio Nino Diaz int nextoffset = 0; 246630b011fSAntonio Nino Diaz uint32_t tag; 247630b011fSAntonio Nino Diaz 248630b011fSAntonio Nino Diaz if (offset >= 0) 249630b011fSAntonio Nino Diaz if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) 250630b011fSAntonio Nino Diaz return nextoffset; 251630b011fSAntonio Nino Diaz 252630b011fSAntonio Nino Diaz do { 253630b011fSAntonio Nino Diaz offset = nextoffset; 254630b011fSAntonio Nino Diaz tag = fdt_next_tag(fdt, offset, &nextoffset); 255630b011fSAntonio Nino Diaz 256630b011fSAntonio Nino Diaz switch (tag) { 257630b011fSAntonio Nino Diaz case FDT_PROP: 258630b011fSAntonio Nino Diaz case FDT_NOP: 259630b011fSAntonio Nino Diaz break; 260630b011fSAntonio Nino Diaz 261630b011fSAntonio Nino Diaz case FDT_BEGIN_NODE: 262630b011fSAntonio Nino Diaz if (depth) 263630b011fSAntonio Nino Diaz (*depth)++; 264630b011fSAntonio Nino Diaz break; 265630b011fSAntonio Nino Diaz 266630b011fSAntonio Nino Diaz case FDT_END_NODE: 267630b011fSAntonio Nino Diaz if (depth && ((--(*depth)) < 0)) 268630b011fSAntonio Nino Diaz return nextoffset; 269630b011fSAntonio Nino Diaz break; 270630b011fSAntonio Nino Diaz 271630b011fSAntonio Nino Diaz case FDT_END: 272630b011fSAntonio Nino Diaz if ((nextoffset >= 0) 273630b011fSAntonio Nino Diaz || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) 274630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 275630b011fSAntonio Nino Diaz else 276630b011fSAntonio Nino Diaz return nextoffset; 277630b011fSAntonio Nino Diaz } 278630b011fSAntonio Nino Diaz } while (tag != FDT_BEGIN_NODE); 279630b011fSAntonio Nino Diaz 280630b011fSAntonio Nino Diaz return offset; 281630b011fSAntonio Nino Diaz } 282630b011fSAntonio Nino Diaz 283630b011fSAntonio Nino Diaz int fdt_first_subnode(const void *fdt, int offset) 284630b011fSAntonio Nino Diaz { 285630b011fSAntonio Nino Diaz int depth = 0; 286630b011fSAntonio Nino Diaz 287630b011fSAntonio Nino Diaz offset = fdt_next_node(fdt, offset, &depth); 288630b011fSAntonio Nino Diaz if (offset < 0 || depth != 1) 289630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 290630b011fSAntonio Nino Diaz 291630b011fSAntonio Nino Diaz return offset; 292630b011fSAntonio Nino Diaz } 293630b011fSAntonio Nino Diaz 294630b011fSAntonio Nino Diaz int fdt_next_subnode(const void *fdt, int offset) 295630b011fSAntonio Nino Diaz { 296630b011fSAntonio Nino Diaz int depth = 1; 297630b011fSAntonio Nino Diaz 298630b011fSAntonio Nino Diaz /* 299630b011fSAntonio Nino Diaz * With respect to the parent, the depth of the next subnode will be 300630b011fSAntonio Nino Diaz * the same as the last. 301630b011fSAntonio Nino Diaz */ 302630b011fSAntonio Nino Diaz do { 303630b011fSAntonio Nino Diaz offset = fdt_next_node(fdt, offset, &depth); 304630b011fSAntonio Nino Diaz if (offset < 0 || depth < 1) 305630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 306630b011fSAntonio Nino Diaz } while (depth > 1); 307630b011fSAntonio Nino Diaz 308630b011fSAntonio Nino Diaz return offset; 309630b011fSAntonio Nino Diaz } 310630b011fSAntonio Nino Diaz 311630b011fSAntonio Nino Diaz const char *fdt_find_string_(const char *strtab, int tabsize, const char *s) 312630b011fSAntonio Nino Diaz { 313630b011fSAntonio Nino Diaz int len = strlen(s) + 1; 314630b011fSAntonio Nino Diaz const char *last = strtab + tabsize - len; 315630b011fSAntonio Nino Diaz const char *p; 316630b011fSAntonio Nino Diaz 317630b011fSAntonio Nino Diaz for (p = strtab; p <= last; p++) 318630b011fSAntonio Nino Diaz if (memcmp(p, s, len) == 0) 319630b011fSAntonio Nino Diaz return p; 320630b011fSAntonio Nino Diaz return NULL; 321630b011fSAntonio Nino Diaz } 322630b011fSAntonio Nino Diaz 323630b011fSAntonio Nino Diaz int fdt_move(const void *fdt, void *buf, int bufsize) 324630b011fSAntonio Nino Diaz { 3253b456661SAndre Przywara if (!can_assume(VALID_INPUT) && bufsize < 0) 3263b456661SAndre Przywara return -FDT_ERR_NOSPACE; 3273b456661SAndre Przywara 328243ce5d5SMadhukar Pappireddy FDT_RO_PROBE(fdt); 329630b011fSAntonio Nino Diaz 3303b456661SAndre Przywara if (fdt_totalsize(fdt) > (unsigned int)bufsize) 331630b011fSAntonio Nino Diaz return -FDT_ERR_NOSPACE; 332630b011fSAntonio Nino Diaz 333630b011fSAntonio Nino Diaz memmove(buf, fdt, fdt_totalsize(fdt)); 334630b011fSAntonio Nino Diaz return 0; 335630b011fSAntonio Nino Diaz } 336