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 2594b2f94bSDaniel Boulby /* The device tree must be at an 8-byte aligned address */ 2694b2f94bSDaniel Boulby if ((uintptr_t)fdt & 7) 2794b2f94bSDaniel Boulby return -FDT_ERR_ALIGNMENT; 2894b2f94bSDaniel 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 9394b2f94bSDaniel Boulby /* The device tree must be at an 8-byte aligned address */ 9494b2f94bSDaniel Boulby if ((uintptr_t)fdt & 7) 9594b2f94bSDaniel Boulby return -FDT_ERR_ALIGNMENT; 9694b2f94bSDaniel 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 if ((fdt_totalsize(fdt) < hdrsize) 110243ce5d5SMadhukar Pappireddy || (fdt_totalsize(fdt) > INT_MAX)) 111243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 112243ce5d5SMadhukar Pappireddy 113243ce5d5SMadhukar Pappireddy /* Bounds check memrsv block */ 114243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, fdt_totalsize(fdt), 115243ce5d5SMadhukar Pappireddy fdt_off_mem_rsvmap(fdt))) 116243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 117243ce5d5SMadhukar Pappireddy 118243ce5d5SMadhukar Pappireddy /* Bounds check structure block */ 119243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST) && fdt_version(fdt) < 17) { 120243ce5d5SMadhukar Pappireddy if (!check_off_(hdrsize, fdt_totalsize(fdt), 121243ce5d5SMadhukar Pappireddy fdt_off_dt_struct(fdt))) 122243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 123243ce5d5SMadhukar Pappireddy } else { 124243ce5d5SMadhukar Pappireddy if (!check_block_(hdrsize, fdt_totalsize(fdt), 125243ce5d5SMadhukar Pappireddy fdt_off_dt_struct(fdt), 126243ce5d5SMadhukar Pappireddy fdt_size_dt_struct(fdt))) 127243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 128243ce5d5SMadhukar Pappireddy } 129243ce5d5SMadhukar Pappireddy 130243ce5d5SMadhukar Pappireddy /* Bounds check strings block */ 131243ce5d5SMadhukar Pappireddy if (!check_block_(hdrsize, fdt_totalsize(fdt), 132243ce5d5SMadhukar Pappireddy fdt_off_dt_strings(fdt), 133243ce5d5SMadhukar Pappireddy fdt_size_dt_strings(fdt))) 134243ce5d5SMadhukar Pappireddy return -FDT_ERR_TRUNCATED; 135243ce5d5SMadhukar Pappireddy } 136243ce5d5SMadhukar Pappireddy 137630b011fSAntonio Nino Diaz return 0; 138630b011fSAntonio Nino Diaz } 139630b011fSAntonio Nino Diaz 140630b011fSAntonio Nino Diaz const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) 141630b011fSAntonio Nino Diaz { 1423b456661SAndre Przywara unsigned int uoffset = offset; 1433b456661SAndre Przywara unsigned int absoffset = offset + fdt_off_dt_struct(fdt); 1443b456661SAndre Przywara 1453b456661SAndre Przywara if (offset < 0) 1463b456661SAndre Przywara return NULL; 147630b011fSAntonio Nino Diaz 148243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_INPUT)) 1493b456661SAndre Przywara if ((absoffset < uoffset) 150630b011fSAntonio Nino Diaz || ((absoffset + len) < absoffset) 151630b011fSAntonio Nino Diaz || (absoffset + len) > fdt_totalsize(fdt)) 152630b011fSAntonio Nino Diaz return NULL; 153630b011fSAntonio Nino Diaz 154243ce5d5SMadhukar Pappireddy if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) 1553b456661SAndre Przywara if (((uoffset + len) < uoffset) 156630b011fSAntonio Nino Diaz || ((offset + len) > fdt_size_dt_struct(fdt))) 157630b011fSAntonio Nino Diaz return NULL; 158630b011fSAntonio Nino Diaz 159630b011fSAntonio Nino Diaz return fdt_offset_ptr_(fdt, offset); 160630b011fSAntonio Nino Diaz } 161630b011fSAntonio Nino Diaz 162630b011fSAntonio Nino Diaz uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) 163630b011fSAntonio Nino Diaz { 164630b011fSAntonio Nino Diaz const fdt32_t *tagp, *lenp; 165*058e017eSMaksims Svecovs uint32_t tag, len, sum; 166630b011fSAntonio Nino Diaz int offset = startoffset; 167630b011fSAntonio Nino Diaz const char *p; 168630b011fSAntonio Nino Diaz 169630b011fSAntonio Nino Diaz *nextoffset = -FDT_ERR_TRUNCATED; 170630b011fSAntonio Nino Diaz tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); 171243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !tagp) 172630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 173630b011fSAntonio Nino Diaz tag = fdt32_to_cpu(*tagp); 174630b011fSAntonio Nino Diaz offset += FDT_TAGSIZE; 175630b011fSAntonio Nino Diaz 176630b011fSAntonio Nino Diaz *nextoffset = -FDT_ERR_BADSTRUCTURE; 177630b011fSAntonio Nino Diaz switch (tag) { 178630b011fSAntonio Nino Diaz case FDT_BEGIN_NODE: 179630b011fSAntonio Nino Diaz /* skip name */ 180630b011fSAntonio Nino Diaz do { 181630b011fSAntonio Nino Diaz p = fdt_offset_ptr(fdt, offset++, 1); 182630b011fSAntonio Nino Diaz } while (p && (*p != '\0')); 183243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !p) 184630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 185630b011fSAntonio Nino Diaz break; 186630b011fSAntonio Nino Diaz 187630b011fSAntonio Nino Diaz case FDT_PROP: 188630b011fSAntonio Nino Diaz lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); 189243ce5d5SMadhukar Pappireddy if (!can_assume(VALID_DTB) && !lenp) 190630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 191*058e017eSMaksims Svecovs 192*058e017eSMaksims Svecovs len = fdt32_to_cpu(*lenp); 193*058e017eSMaksims Svecovs sum = len + offset; 194*058e017eSMaksims Svecovs if (!can_assume(VALID_DTB) && 195*058e017eSMaksims Svecovs (INT_MAX <= sum || sum < (uint32_t) offset)) 196*058e017eSMaksims Svecovs return FDT_END; /* premature end */ 197*058e017eSMaksims Svecovs 198630b011fSAntonio Nino Diaz /* skip-name offset, length and value */ 199*058e017eSMaksims Svecovs offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len; 200*058e017eSMaksims Svecovs 201243ce5d5SMadhukar Pappireddy if (!can_assume(LATEST) && 202*058e017eSMaksims Svecovs fdt_version(fdt) < 0x10 && len >= 8 && 203*058e017eSMaksims Svecovs ((offset - len) % 8) != 0) 204630b011fSAntonio Nino Diaz offset += 4; 205630b011fSAntonio Nino Diaz break; 206630b011fSAntonio Nino Diaz 207630b011fSAntonio Nino Diaz case FDT_END: 208630b011fSAntonio Nino Diaz case FDT_END_NODE: 209630b011fSAntonio Nino Diaz case FDT_NOP: 210630b011fSAntonio Nino Diaz break; 211630b011fSAntonio Nino Diaz 212630b011fSAntonio Nino Diaz default: 213630b011fSAntonio Nino Diaz return FDT_END; 214630b011fSAntonio Nino Diaz } 215630b011fSAntonio Nino Diaz 216630b011fSAntonio Nino Diaz if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) 217630b011fSAntonio Nino Diaz return FDT_END; /* premature end */ 218630b011fSAntonio Nino Diaz 219630b011fSAntonio Nino Diaz *nextoffset = FDT_TAGALIGN(offset); 220630b011fSAntonio Nino Diaz return tag; 221630b011fSAntonio Nino Diaz } 222630b011fSAntonio Nino Diaz 223630b011fSAntonio Nino Diaz int fdt_check_node_offset_(const void *fdt, int offset) 224630b011fSAntonio Nino Diaz { 2253b456661SAndre Przywara if (!can_assume(VALID_INPUT) 2263b456661SAndre Przywara && ((offset < 0) || (offset % FDT_TAGSIZE))) 2273b456661SAndre Przywara return -FDT_ERR_BADOFFSET; 2283b456661SAndre Przywara 2293b456661SAndre Przywara if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) 230630b011fSAntonio Nino Diaz return -FDT_ERR_BADOFFSET; 231630b011fSAntonio Nino Diaz 232630b011fSAntonio Nino Diaz return offset; 233630b011fSAntonio Nino Diaz } 234630b011fSAntonio Nino Diaz 235630b011fSAntonio Nino Diaz int fdt_check_prop_offset_(const void *fdt, int offset) 236630b011fSAntonio Nino Diaz { 2373b456661SAndre Przywara if (!can_assume(VALID_INPUT) 2383b456661SAndre Przywara && ((offset < 0) || (offset % FDT_TAGSIZE))) 2393b456661SAndre Przywara return -FDT_ERR_BADOFFSET; 2403b456661SAndre Przywara 2413b456661SAndre Przywara if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) 242630b011fSAntonio Nino Diaz return -FDT_ERR_BADOFFSET; 243630b011fSAntonio Nino Diaz 244630b011fSAntonio Nino Diaz return offset; 245630b011fSAntonio Nino Diaz } 246630b011fSAntonio Nino Diaz 247630b011fSAntonio Nino Diaz int fdt_next_node(const void *fdt, int offset, int *depth) 248630b011fSAntonio Nino Diaz { 249630b011fSAntonio Nino Diaz int nextoffset = 0; 250630b011fSAntonio Nino Diaz uint32_t tag; 251630b011fSAntonio Nino Diaz 252630b011fSAntonio Nino Diaz if (offset >= 0) 253630b011fSAntonio Nino Diaz if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) 254630b011fSAntonio Nino Diaz return nextoffset; 255630b011fSAntonio Nino Diaz 256630b011fSAntonio Nino Diaz do { 257630b011fSAntonio Nino Diaz offset = nextoffset; 258630b011fSAntonio Nino Diaz tag = fdt_next_tag(fdt, offset, &nextoffset); 259630b011fSAntonio Nino Diaz 260630b011fSAntonio Nino Diaz switch (tag) { 261630b011fSAntonio Nino Diaz case FDT_PROP: 262630b011fSAntonio Nino Diaz case FDT_NOP: 263630b011fSAntonio Nino Diaz break; 264630b011fSAntonio Nino Diaz 265630b011fSAntonio Nino Diaz case FDT_BEGIN_NODE: 266630b011fSAntonio Nino Diaz if (depth) 267630b011fSAntonio Nino Diaz (*depth)++; 268630b011fSAntonio Nino Diaz break; 269630b011fSAntonio Nino Diaz 270630b011fSAntonio Nino Diaz case FDT_END_NODE: 271630b011fSAntonio Nino Diaz if (depth && ((--(*depth)) < 0)) 272630b011fSAntonio Nino Diaz return nextoffset; 273630b011fSAntonio Nino Diaz break; 274630b011fSAntonio Nino Diaz 275630b011fSAntonio Nino Diaz case FDT_END: 276630b011fSAntonio Nino Diaz if ((nextoffset >= 0) 277630b011fSAntonio Nino Diaz || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) 278630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 279630b011fSAntonio Nino Diaz else 280630b011fSAntonio Nino Diaz return nextoffset; 281630b011fSAntonio Nino Diaz } 282630b011fSAntonio Nino Diaz } while (tag != FDT_BEGIN_NODE); 283630b011fSAntonio Nino Diaz 284630b011fSAntonio Nino Diaz return offset; 285630b011fSAntonio Nino Diaz } 286630b011fSAntonio Nino Diaz 287630b011fSAntonio Nino Diaz int fdt_first_subnode(const void *fdt, int offset) 288630b011fSAntonio Nino Diaz { 289630b011fSAntonio Nino Diaz int depth = 0; 290630b011fSAntonio Nino Diaz 291630b011fSAntonio Nino Diaz offset = fdt_next_node(fdt, offset, &depth); 292630b011fSAntonio Nino Diaz if (offset < 0 || depth != 1) 293630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 294630b011fSAntonio Nino Diaz 295630b011fSAntonio Nino Diaz return offset; 296630b011fSAntonio Nino Diaz } 297630b011fSAntonio Nino Diaz 298630b011fSAntonio Nino Diaz int fdt_next_subnode(const void *fdt, int offset) 299630b011fSAntonio Nino Diaz { 300630b011fSAntonio Nino Diaz int depth = 1; 301630b011fSAntonio Nino Diaz 302630b011fSAntonio Nino Diaz /* 303630b011fSAntonio Nino Diaz * With respect to the parent, the depth of the next subnode will be 304630b011fSAntonio Nino Diaz * the same as the last. 305630b011fSAntonio Nino Diaz */ 306630b011fSAntonio Nino Diaz do { 307630b011fSAntonio Nino Diaz offset = fdt_next_node(fdt, offset, &depth); 308630b011fSAntonio Nino Diaz if (offset < 0 || depth < 1) 309630b011fSAntonio Nino Diaz return -FDT_ERR_NOTFOUND; 310630b011fSAntonio Nino Diaz } while (depth > 1); 311630b011fSAntonio Nino Diaz 312630b011fSAntonio Nino Diaz return offset; 313630b011fSAntonio Nino Diaz } 314630b011fSAntonio Nino Diaz 315630b011fSAntonio Nino Diaz const char *fdt_find_string_(const char *strtab, int tabsize, const char *s) 316630b011fSAntonio Nino Diaz { 317630b011fSAntonio Nino Diaz int len = strlen(s) + 1; 318630b011fSAntonio Nino Diaz const char *last = strtab + tabsize - len; 319630b011fSAntonio Nino Diaz const char *p; 320630b011fSAntonio Nino Diaz 321630b011fSAntonio Nino Diaz for (p = strtab; p <= last; p++) 322630b011fSAntonio Nino Diaz if (memcmp(p, s, len) == 0) 323630b011fSAntonio Nino Diaz return p; 324630b011fSAntonio Nino Diaz return NULL; 325630b011fSAntonio Nino Diaz } 326630b011fSAntonio Nino Diaz 327630b011fSAntonio Nino Diaz int fdt_move(const void *fdt, void *buf, int bufsize) 328630b011fSAntonio Nino Diaz { 3293b456661SAndre Przywara if (!can_assume(VALID_INPUT) && bufsize < 0) 3303b456661SAndre Przywara return -FDT_ERR_NOSPACE; 3313b456661SAndre Przywara 332243ce5d5SMadhukar Pappireddy FDT_RO_PROBE(fdt); 333630b011fSAntonio Nino Diaz 3343b456661SAndre Przywara if (fdt_totalsize(fdt) > (unsigned int)bufsize) 335630b011fSAntonio Nino Diaz return -FDT_ERR_NOSPACE; 336630b011fSAntonio Nino Diaz 337630b011fSAntonio Nino Diaz memmove(buf, fdt, fdt_totalsize(fdt)); 338630b011fSAntonio Nino Diaz return 0; 339630b011fSAntonio Nino Diaz } 340