mirror of https://github.com/wolfSSL/wolfBoot.git
403 lines
17 KiB
C
403 lines
17 KiB
C
/* fdt.h
|
|
*
|
|
* Flattened device tree (DTB) parser.
|
|
*
|
|
* Written from the Devicetree Specification v0.4, section 5 ("Flattened
|
|
* Devicetree (DTB) Format"). The header field names below are the ones
|
|
* the specification itself defines in section 5.2.
|
|
*
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#ifndef FDT_H
|
|
#define FDT_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* On-disk format (Devicetree Specification v0.4, section 5) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#define FDT_MAGIC 0xD00DFEEDUL
|
|
|
|
/* Only v17 blobs are accepted. v16 and earlier placed property values at
|
|
* a different alignment; dtc has emitted v17 for many years and wolfBoot
|
|
* has never had a target that needed the older layout. */
|
|
#define FDT_SUPPORTED_VERSION 17
|
|
|
|
struct fdt_header {
|
|
uint32_t magic;
|
|
uint32_t totalsize;
|
|
uint32_t off_dt_struct;
|
|
uint32_t off_dt_strings;
|
|
uint32_t off_mem_rsvmap;
|
|
uint32_t version;
|
|
uint32_t last_comp_version;
|
|
uint32_t boot_cpuid_phys;
|
|
uint32_t size_dt_strings;
|
|
uint32_t size_dt_struct;
|
|
};
|
|
|
|
#define FDT_HEADER_SIZE 40 /* sizeof(struct fdt_header), spec section 5.2 */
|
|
|
|
/* Byte offset of each header field, in spec order. Used to read/write
|
|
* the header without depending on struct padding. */
|
|
#define FDT_H_MAGIC 0U
|
|
#define FDT_H_TOTALSIZE 4U
|
|
#define FDT_H_OFF_STRUCT 8U
|
|
#define FDT_H_OFF_STRINGS 12U
|
|
#define FDT_H_OFF_RSVMAP 16U
|
|
#define FDT_H_VERSION 20U
|
|
#define FDT_H_LAST_COMP 24U
|
|
#define FDT_H_BOOT_CPUID 28U
|
|
#define FDT_H_SIZE_STRINGS 32U
|
|
#define FDT_H_SIZE_STRUCT 36U
|
|
|
|
struct fdt_reserve_entry {
|
|
uint64_t address;
|
|
uint64_t size;
|
|
};
|
|
|
|
#define FDT_RSV_ENTRY_SIZE 16 /* sizeof(struct fdt_reserve_entry) */
|
|
|
|
/* Structure block tokens, spec section 5.4.1 */
|
|
#define FDT_BEGIN_NODE 0x00000001UL
|
|
#define FDT_END_NODE 0x00000002UL
|
|
#define FDT_PROP 0x00000003UL
|
|
#define FDT_NOP 0x00000004UL
|
|
#define FDT_END 0x00000009UL
|
|
|
|
#define FDT_TAGSIZE ((uint32_t)sizeof(uint32_t))
|
|
#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1))
|
|
#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE))
|
|
|
|
/* Deepest node nesting accepted. Real trees are well under 10 levels;
|
|
* the cap keeps a hostile blob from driving an unbounded walk in a
|
|
* consumer that tracks depth. */
|
|
#ifndef FDT_MAX_DEPTH
|
|
#define FDT_MAX_DEPTH 32
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Errors */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* wolfBoot's own error set and ordering - most frequently tested first.
|
|
*
|
|
* The int-returning functions return 0 or a positive offset on success,
|
|
* and the NEGATED value of one of these on failure. The pointer-returning
|
|
* ones return NULL on failure, reporting the reason through their length
|
|
* output where they have one (fdt_get_name, fdt_get_string, fdt_getprop,
|
|
* fdt_getprop_by_offset). fdt_size() reports a size, not a status: a
|
|
* context that is not open reads as 0. */
|
|
#define FDT_ERR_NOTFOUND 1 /* no such node, property or string */
|
|
#define FDT_ERR_EXISTS 2 /* node/property already present */
|
|
#define FDT_ERR_NOSPACE 3 /* would not fit in the caller's buffer */
|
|
#define FDT_ERR_BADOFFSET 4 /* offset does not name the expected tag */
|
|
#define FDT_ERR_BADARG 5 /* NULL or otherwise unusable argument */
|
|
#define FDT_ERR_BADMAGIC 6 /* header magic is not FDT_MAGIC */
|
|
#define FDT_ERR_BADVERSION 7 /* not a v17 blob */
|
|
#define FDT_ERR_BADLAYOUT 8 /* header block offsets/sizes inconsistent */
|
|
#define FDT_ERR_BADSTRUCTURE 9 /* structure block is malformed */
|
|
#define FDT_ERR_INTERNAL 10 /* should not happen; a bug if it does */
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* PCI address cell flags (used by the NXP QorIQ dts fixups) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#define FDT_PCI_PREFETCH (0x40000000)
|
|
#define FDT_PCI_MEM32 (0x02000000)
|
|
#define FDT_PCI_IO (0x01000000)
|
|
#define FDT_PCI_MEM64 (0x03000000)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Buffer sizing */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Size of the DTB staging window at WOLFBOOT_LOAD_DTS_ADDRESS, and so
|
|
* the capacity the boot paths hand to fdt_open() and hal_dts_fixup().
|
|
* MIN is the v17 header size (also enforced by the signer).
|
|
*
|
|
* This is per-target, not a global ceiling: a target whose window is
|
|
* under the 1 MiB default MUST override it (hal/nxp_ppc.h uses 64 KiB).
|
|
* Too large is the dangerous direction - every parser bound is checked
|
|
* against the capacity given, so an over-large value lets an in-place
|
|
* fixup grow the blob past the real region. Pass a tighter bound
|
|
* directly when one is known (a FIT sub-image's declared length, say). */
|
|
#ifndef WOLFBOOT_DTS_MAX_SIZE
|
|
#define WOLFBOOT_DTS_MAX_SIZE (1024U * 1024U)
|
|
#endif
|
|
#define WOLFBOOT_DTS_MIN_SIZE ((uint32_t)FDT_HEADER_SIZE)
|
|
|
|
/* Headroom (bytes) that fdt_grow() reserves before wolfBoot inserts
|
|
* /chosen properties. Sized to comfortably hold a full LINUX_BOOTARGS
|
|
* plus, when WOLFBOOT_FIT_RAMDISK is enabled, two 64-bit
|
|
* linux,initrd-{start,end} cells with property-name overhead. A target
|
|
* whose hal_dts_fixup() inserts more chosen entries can override this
|
|
* with -DWOLFBOOT_FDT_FIXUP_HEADROOM=<bytes>. */
|
|
#ifndef WOLFBOOT_FDT_FIXUP_HEADROOM
|
|
#define WOLFBOOT_FDT_FIXUP_HEADROOM 768
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Parse context */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* A validated view of one blob, produced by fdt_open(). Offsets are held
|
|
* in host byte order, known consistent and known to fit in `capacity`.
|
|
* API node/property offsets are structure-block relative, so the root
|
|
* node is always offset 0. */
|
|
struct fdt_ctx {
|
|
uint8_t* blob; /* base of the blob; NULL when closed */
|
|
uint32_t capacity; /* usable bytes at blob, supplied by the caller */
|
|
uint32_t totalsize; /* <= capacity */
|
|
uint32_t off_rsv; /* memory reservation block, 8-byte aligned */
|
|
uint32_t off_struct; /* structure block, 4-byte aligned */
|
|
uint32_t size_struct;
|
|
uint32_t off_strings; /* strings block */
|
|
uint32_t size_strings;
|
|
};
|
|
typedef struct fdt_ctx fdt_ctx;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Byte order */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
uint32_t cpu_to_fdt32(uint32_t x);
|
|
uint64_t cpu_to_fdt64(uint64_t x);
|
|
uint32_t fdt32_to_cpu(uint32_t x);
|
|
uint64_t fdt64_to_cpu(uint64_t x);
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Open, size, resize */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Validate `blob` and populate `ctx`. `capacity` is the bytes the caller
|
|
* can safely address at `blob`, NOT the blob's own idea of its size:
|
|
* every later bound is checked against it, so an over-large value
|
|
* defeats the parser's memory safety. `blob` must be 4-byte aligned. A
|
|
* property-less tree (empty strings block) is accepted.
|
|
*
|
|
* Returns 0, or a negative FDT_ERR_*. On failure ctx->blob is NULL. */
|
|
int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity);
|
|
|
|
/* Declared totalsize from the header alone, for callers that must learn
|
|
* how many bytes to fetch before they hold the whole blob (a two-step
|
|
* read out of external flash, say). Checks magic, version and that
|
|
* totalsize is in [WOLFBOOT_DTS_MIN_SIZE, WOLFBOOT_DTS_MAX_SIZE]; no
|
|
* structural checks, so the complete blob still needs fdt_open(). */
|
|
int fdt_peek_size(const void* hdr, uint32_t hdr_len, uint32_t* totalsize);
|
|
|
|
/* Declared size of the blob (bytes). 0 if ctx is not open. */
|
|
uint32_t fdt_size(const fdt_ctx* ctx);
|
|
|
|
/* Record `extra` bytes of headroom past the tree's content in totalsize,
|
|
* so a later stage sees a blob with room to grow. -FDT_ERR_NOSPACE if the
|
|
* capacity cannot hold it. Mutations are bounded by capacity directly and
|
|
* do not need this. */
|
|
int fdt_grow(fdt_ctx* ctx, uint32_t extra);
|
|
|
|
/* Shrink totalsize down to exactly the bytes the tree occupies. */
|
|
int fdt_shrink(fdt_ctx* ctx);
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Reading */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Next node in tree order; offset < 0 starts. Returns the offset, or
|
|
* -FDT_ERR_NOTFOUND at the end of the tree or when the walk leaves the
|
|
* subtree it started in.
|
|
*
|
|
* `depth`, if given, is incremented on entering a node and decremented on
|
|
* leaving one, so it counts levels BELOW the starting point. Initialise it
|
|
* to 0. Starting from offset < 0 the root is therefore reported at depth 1
|
|
* and its children at depth 2; starting from a node offset, that node's
|
|
* direct children are reported at depth 1, which is how fdt_subnode_offset()
|
|
* distinguishes them from deeper descendants. */
|
|
int fdt_next_node(const fdt_ctx* ctx, int offset, int* depth);
|
|
|
|
/* Property iteration within one node. */
|
|
int fdt_first_property_offset(const fdt_ctx* ctx, int nodeoffset);
|
|
int fdt_next_property_offset(const fdt_ctx* ctx, int propoffset);
|
|
|
|
/* Value, name and length of the property at `propoffset`. Either output
|
|
* pointer may be NULL. Returns the value, or NULL on error (in which
|
|
* case *lenp, if given, holds the negative FDT_ERR_*). */
|
|
const void* fdt_getprop_by_offset(const fdt_ctx* ctx, int propoffset,
|
|
const char** namep, int* lenp);
|
|
|
|
/* Node name. *len receives its length, or a negative FDT_ERR_* if the
|
|
* offset is not a node. The root node's name is the empty string. */
|
|
const char* fdt_get_name(const fdt_ctx* ctx, int nodeoffset, int* len);
|
|
|
|
/* String from the strings block by its offset within that block. */
|
|
const char* fdt_get_string(const fdt_ctx* ctx, int stroffset, int* lenp);
|
|
|
|
/* Value of a named property, or NULL. *lenp receives the length, or a
|
|
* negative FDT_ERR_* on failure. */
|
|
const void* fdt_getprop(const fdt_ctx* ctx, int nodeoffset,
|
|
const char* name, int* lenp);
|
|
|
|
/* A 4- or 8-byte property interpreted as an address. NULL if absent or
|
|
* not one of those two widths. */
|
|
void* fdt_getprop_address(const fdt_ctx* ctx, int nodeoffset,
|
|
const char* name);
|
|
|
|
/* Resolve an absolute path such as "/" or "/soc/serial@21c0500". Path
|
|
* components match either the full node name or the part before the
|
|
* "@" unit address. Returns the node offset or a negative FDT_ERR_*. */
|
|
int fdt_path_offset(const fdt_ctx* ctx, const char* path);
|
|
|
|
/* Direct child of `parentoff` by name. */
|
|
int fdt_subnode_offset(const fdt_ctx* ctx, int parentoff, const char* name);
|
|
|
|
/* Search the whole tree from `startoff` (< 0 for the beginning) for an
|
|
* exact node-name match, at any depth - prefer fdt_path_offset() when
|
|
* the location is known. This and the two searches below report a
|
|
* genuine no-match as exactly -FDT_ERR_NOTFOUND and never as some other
|
|
* walk error, so an `off != -FDT_ERR_NOTFOUND` test cannot mistake one
|
|
* for an offset. A NULL argument or closed ctx still gives
|
|
* -FDT_ERR_BADARG. */
|
|
int fdt_find_node_offset(const fdt_ctx* ctx, int startoff,
|
|
const char* nodename);
|
|
|
|
/* Search the whole tree for a node carrying `propname` with the exact
|
|
* NUL-terminated string value `propval`. */
|
|
int fdt_find_prop_offset(const fdt_ctx* ctx, int startoff,
|
|
const char* propname, const char* propval);
|
|
|
|
/* Shorthand for fdt_find_prop_offset(..., "device_type", devtype). */
|
|
int fdt_find_devtype(const fdt_ctx* ctx, int startoff, const char* devtype);
|
|
|
|
/* Search the whole tree for a node whose "compatible" string list
|
|
* contains `compatible` as a complete entry. */
|
|
int fdt_node_offset_by_compatible(const fdt_ctx* ctx, int startoff,
|
|
const char* compatible);
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Writing (in place) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Set (or add) a property. Bounded by ctx->capacity. */
|
|
int fdt_setprop(fdt_ctx* ctx, int nodeoffset, const char* name,
|
|
const void* val, int len);
|
|
|
|
/* Add a child node. Returns its offset, -FDT_ERR_EXISTS if already
|
|
* present, or another negative FDT_ERR_*. */
|
|
int fdt_add_subnode(fdt_ctx* ctx, int parentoff, const char* name);
|
|
|
|
/* Remove a node and its entire subtree. */
|
|
int fdt_del_node(fdt_ctx* ctx, int nodeoffset);
|
|
|
|
/* Append an entry to the memory reservation block. */
|
|
int fdt_add_mem_rsv(fdt_ctx* ctx, uint64_t address, uint64_t size);
|
|
|
|
/* Logging wrappers around fdt_setprop() used by the HAL dts fixups. */
|
|
int fdt_fixup_str(fdt_ctx* ctx, int off, const char* node, const char* name,
|
|
const char* str);
|
|
int fdt_fixup_val(fdt_ctx* ctx, int off, const char* node, const char* name,
|
|
uint32_t val);
|
|
int fdt_fixup_val64(fdt_ctx* ctx, int off, const char* node, const char* name,
|
|
uint64_t val);
|
|
|
|
/* Write /chosen/linux,initrd-{start,end} as 64-bit big-endian values,
|
|
* creating /chosen if needed. Returns 0 or a negative FDT_ERR_*. */
|
|
int fdt_fixup_initrd(fdt_ctx* ctx, uint64_t start, uint64_t size);
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Flattened uImage Tree (FIT) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Boot configuration and the sub-image names it references. Output
|
|
* pointers may be NULL. Returns the configuration name, or NULL.
|
|
*
|
|
* Sub-images resolve as direct children of /images, configurations as
|
|
* direct children of /configurations. A tree-wide search by bare name is
|
|
* used only when the FIT has no /images node at all, so a node planted
|
|
* elsewhere cannot stand in for the real sub-image. */
|
|
const char* fit_find_images(fdt_ctx* ctx, const char** pkernel,
|
|
const char** pflat_dt, const char** pramdisk, const char** pfpga);
|
|
|
|
/* A sub-image's "compatible" property, or NULL. NOTE: this returns the
|
|
* raw property data, i.e. only the FIRST string of the list. To inspect
|
|
* every entry, take the length from fdt_getprop() and walk the NUL
|
|
* separators (fit_load_fpga() does that instead of using this). */
|
|
const char* fit_get_compatible(fdt_ctx* ctx, const char* image);
|
|
|
|
/* Stage a sub-image to its FIT-declared `load` address, decompressing
|
|
* if it declares compression="gzip", and return its entry address. */
|
|
void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp);
|
|
|
|
/* As fit_load_image(), with an explicit ceiling on the decompressed
|
|
* size at the FIT-declared destination. */
|
|
void* fit_load_image_ex(fdt_ctx* ctx, const char* image, int* lenp,
|
|
uint32_t out_max);
|
|
|
|
/* Load (and, if compressed, decompress) a sub-image directly to a
|
|
* caller-supplied destination, overriding the FIT image's `load` and
|
|
* `entry` properties. dst is both the destination and the value
|
|
* returned; dst_max bounds the decompressed size. */
|
|
void* fit_load_image_to(fdt_ctx* ctx, const char* image, void* dst,
|
|
uint32_t dst_max, int* lenp);
|
|
|
|
#ifdef WOLFBOOT_FIT_RAMDISK
|
|
/* Load a FIT ramdisk sub-image (optionally relocated to
|
|
* WOLFBOOT_LOAD_RAMDISK_ADDRESS) and patch /chosen/linux,initrd-* in
|
|
* `dts`. Pass dts as NULL to skip the fixup. Returns 0 on success, -1
|
|
* on load failure. Callers typically log and continue. */
|
|
int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts);
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_FPGA_BITSTREAM
|
|
/* Locate a FIT fpga sub-image, stage it and program the PL via
|
|
* hal_fpga_load(). Returns 0 on success (including when fpga_node is
|
|
* NULL), negative on failure. When WOLFBOOT_FPGA_NONFATAL is defined a
|
|
* programming failure is logged and 0 is returned instead. */
|
|
int fit_load_fpga(fdt_ctx* ctx, const char* fpga_node);
|
|
#endif
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Malformed-blob corpus (test builds only) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#ifdef WOLFBOOT_FDT_CORPUS
|
|
/* Number of cases; short name and one-line description of each. */
|
|
int fdt_corpus_count(void);
|
|
const char* fdt_corpus_name(int idx);
|
|
const char* fdt_corpus_desc(int idx);
|
|
|
|
/* Build case idx, mutating the supplied known-good blob or synthesizing
|
|
* one. Returns a malloc'd buffer of exactly *outlen bytes - so a read
|
|
* past the declared size is a real heap overrun - or NULL. Caller frees. */
|
|
uint8_t* fdt_corpus_build(int idx, const uint8_t* base, uint32_t baselen,
|
|
uint32_t* outlen);
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* !FDT_H */
|