hooks: add pre-boot hook, DTB accessor and FDT alias/reg helpers

pull/906/head
David Garske 2026-09-15 10:53:08 -07:00 committed by Daniele Lacamera
parent 4093fa0e17
commit fae9e53653
11 changed files with 687 additions and 0 deletions

View File

@ -21,6 +21,7 @@ jobs:
test_script: sim-sunnyday-update.sh
expected_preinit: 2
expected_postinit: 2
expected_preboot: 2
expected_boot: 2
expected_panic: 0
- mechanism: dualbank
@ -28,6 +29,7 @@ jobs:
test_script: sim-dualbank-swap-update.sh
expected_preinit: 3
expected_postinit: 3
expected_preboot: 3
expected_boot: 3
expected_panic: 0
- mechanism: panic
@ -35,6 +37,7 @@ jobs:
test_script: ""
expected_preinit: 1
expected_postinit: 1
expected_preboot: 0
expected_boot: 0
expected_panic: 1
@ -67,10 +70,34 @@ jobs:
void wolfBoot_hook_preinit(void) { log_hook("preinit"); }
void wolfBoot_hook_postinit(void) { log_hook("postinit"); }
void wolfBoot_hook_preboot(struct wolfBoot_image *boot_img)
{
(void)boot_img;
#if defined(MMU) || defined(WOLFBOOT_FDT)
(void)wolfBoot_get_dts_address();
#endif
log_hook("preboot");
}
void wolfBoot_hook_boot(struct wolfBoot_image *boot_img) { (void)boot_img; log_hook("boot"); }
void wolfBoot_hook_panic(void) { log_hook("panic"); }
EOF
- name: Every update strategy defines wolfBoot_get_dts_address()
run: |
# hooks.h advertises it for any MMU/WOLFBOOT_FDT build, so each
# strategy must define it or a conforming hook fails to link.
fail=0
for f in src/update_ram.c src/update_disk.c src/update_flash.c \
src/update_flash_hwswap.c; do
# Tolerate either pointer spelling: void* x() and void *x().
if ! grep -Eq 'void[[:space:]]*\*[[:space:]]*wolfBoot_get_dts_address\(void\)' "$f"; then
echo "FAIL: $f does not define wolfBoot_get_dts_address()"
fail=1
fi
done
test "$fail" = "0" || exit 1
echo "OK: all four update strategies define the accessor"
- name: Select config
run: |
cp config/examples/${{ matrix.config }} .config
@ -85,6 +112,7 @@ jobs:
WOLFBOOT_HOOKS_FILE=test_hooks.c \
WOLFBOOT_HOOK_LOADER_PREINIT=1 \
WOLFBOOT_HOOK_LOADER_POSTINIT=1 \
WOLFBOOT_HOOK_PREBOOT=1 \
WOLFBOOT_HOOK_BOOT=1 \
WOLFBOOT_HOOK_PANIC=1
@ -146,6 +174,7 @@ jobs:
check_count "preinit" ${{ matrix.expected_preinit }}
check_count "postinit" ${{ matrix.expected_postinit }}
check_count "preboot" ${{ matrix.expected_preboot }}
check_count "boot" ${{ matrix.expected_boot }}
check_count "panic" ${{ matrix.expected_panic }}

View File

@ -15,6 +15,7 @@ failure, etc.
|------|-------|-----------|------------|
| Preinit | `WOLFBOOT_HOOK_LOADER_PREINIT` | `void wolfBoot_hook_preinit(void)` | Before `hal_init()` in the loader |
| Postinit | `WOLFBOOT_HOOK_LOADER_POSTINIT` | `void wolfBoot_hook_postinit(void)` | After all loader initialization, just before `wolfBoot_start()` |
| Preboot | `WOLFBOOT_HOOK_PREBOOT` | `void wolfBoot_hook_preboot(struct wolfBoot_image *boot_img)` | After verification, immediately **before** `hal_prepare_boot()` |
| Boot | `WOLFBOOT_HOOK_BOOT` | `void wolfBoot_hook_boot(struct wolfBoot_image *boot_img)` | After `hal_prepare_boot()` but before `do_boot()` |
| Panic | `WOLFBOOT_HOOK_PANIC` | `void wolfBoot_hook_panic(void)` | Inside `wolfBoot_panic()`, before halt |
@ -36,6 +37,8 @@ loader main()
|
+-- (image verification, update logic)
|
+-- [HOOK: wolfBoot_hook_preboot()] <-- WOLFBOOT_HOOK_PREBOOT
|
+-- hal_prepare_boot()
|
+-- [HOOK: wolfBoot_hook_boot()] <-- WOLFBOOT_HOOK_BOOT
@ -60,6 +63,7 @@ WOLFBOOT_HOOKS_FILE=path/to/my_hooks.c
# Enable individual hooks (each is independent)
WOLFBOOT_HOOK_LOADER_PREINIT=1
WOLFBOOT_HOOK_LOADER_POSTINIT=1
WOLFBOOT_HOOK_PREBOOT=1
WOLFBOOT_HOOK_BOOT=1
WOLFBOOT_HOOK_PANIC=1
```
@ -82,6 +86,8 @@ make WOLFBOOT_HOOKS_FILE=my_hooks.c WOLFBOOT_HOOK_LOADER_PREINIT=1
use functionality that does not depend on `hal_init()` having been called.
- The boot hook receives a pointer to the verified `wolfBoot_image` struct,
allowing inspection of firmware version, type, and other metadata before boot.
- The preboot hook gets the same struct but fires on the other side of `hal_prepare_boot()`, where a port tears the environment down for handoff: flushing or disabling caches, disabling the MMU (`hal/cm4.c`), leaving 4-byte flash addressing (`hal/zynq.c`). Anything touching DMA, a live MMU mapping or external flash belongs in the preboot hook; use the boot hook only for the last thing before `do_boot()`.
- On `MMU` / `WOLFBOOT_FDT` builds, `wolfBoot_get_dts_address()` returns the device tree wolfBoot is about to pass to the OS, or `NULL`. It is published just before the preboot hook, so it is only meaningful from there on. Parse it with the `fdt_*` API in `include/fdt.h`; `fdt_get_alias()` and `fdt_get_reg()` turn a board label into a peripheral base address.
- The panic hook fires inside `wolfBoot_panic()` just before the system halts.
Use it to set the system to a safe state, log errors, toggle GPIOs, notify
external systems, etc.

View File

@ -272,6 +272,23 @@ 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);
/* Offset of the node containing `nodeoffset`. Walks from the root (no
* back-pointers in a flat tree), so O(tree). -FDT_ERR_NOTFOUND for root. */
int fdt_parent_offset(const fdt_ctx* ctx, int nodeoffset);
/* Resolve an /aliases entry to the node it names. Returns the node offset,
* -FDT_ERR_NOTFOUND if there is no /aliases or no such entry, or
* -FDT_ERR_BADSTRUCTURE if the value is not a NUL-terminated abs path. */
int fdt_get_alias(const fdt_ctx* ctx, const char* name);
/* Decode entry `index` of a node's "reg", honoring the parent's
* #address-cells / #size-cells (spec defaults 2 and 1). Handles the 2+2 cell
* shape fdt_getprop_address() cannot. `addr` and `size` are optional. Cell
* counts above 2 do not fit uint64_t and give -FDT_ERR_BADSTRUCTURE, as does
* a "reg" that is not a whole number of entries. */
int fdt_get_reg(const fdt_ctx* ctx, int nodeoffset, int index,
uint64_t* addr, uint64_t* size);
/* 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

View File

@ -42,6 +42,10 @@ void wolfBoot_hook_preinit(void);
void wolfBoot_hook_postinit(void);
#endif
#ifdef WOLFBOOT_HOOK_PREBOOT
void wolfBoot_hook_preboot(struct wolfBoot_image *boot_img);
#endif
#ifdef WOLFBOOT_HOOK_BOOT
void wolfBoot_hook_boot(struct wolfBoot_image *boot_img);
#endif
@ -50,6 +54,15 @@ void wolfBoot_hook_boot(struct wolfBoot_image *boot_img);
void wolfBoot_hook_panic(void);
#endif
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* The device tree wolfBoot is about to hand to the OS, or NULL. Valid from
* wolfBoot_hook_preboot() on, once the DTB is located, relocated and (where
* a digest is bound) authenticated. Not const because fdt_open() takes a
* mutable blob: a hook that does edit the tree edits what the OS boots, and
* must stay within WOLFBOOT_DTS_MAX_SIZE. */
void* wolfBoot_get_dts_address(void);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -1700,6 +1700,10 @@ ifeq ($(WOLFBOOT_HOOK_LOADER_POSTINIT),1)
CFLAGS += -DWOLFBOOT_HOOK_LOADER_POSTINIT
WOLFBOOT_HOOKS_ENABLED := 1
endif
ifeq ($(WOLFBOOT_HOOK_PREBOOT),1)
CFLAGS += -DWOLFBOOT_HOOK_PREBOOT
WOLFBOOT_HOOKS_ENABLED := 1
endif
ifeq ($(WOLFBOOT_HOOK_BOOT),1)
CFLAGS += -DWOLFBOOT_HOOK_BOOT
WOLFBOOT_HOOKS_ENABLED := 1

128
src/fdt.c
View File

@ -894,6 +894,134 @@ int fdt_path_offset(const fdt_ctx* ctx, const char* path)
return off;
}
int fdt_parent_offset(const fdt_ctx* ctx, int nodeoffset)
{
int off, depth, target_depth, parent;
if (!fdt_ctx_ok(ctx) || nodeoffset < 0) {
return -FDT_ERR_BADARG;
}
if (nodeoffset == 0) {
return -FDT_ERR_NOTFOUND; /* the root node has no parent */
}
/* No back-pointers in a flat tree: walk down from the root. Pass one
* finds the target's depth (root 0, children 1). */
target_depth = -1;
depth = 0;
for (off = fdt_next_node(ctx, 0, &depth); off >= 0;
off = fdt_next_node(ctx, off, &depth)) {
if (off == nodeoffset) {
target_depth = depth;
break;
}
}
if (target_depth < 1) {
return -FDT_ERR_NOTFOUND;
}
if (target_depth == 1) {
return 0; /* direct child of the root */
}
/* Pass two: the last node seen one level shallower. */
parent = -FDT_ERR_NOTFOUND;
depth = 0;
for (off = fdt_next_node(ctx, 0, &depth); off >= 0;
off = fdt_next_node(ctx, off, &depth)) {
if (off == nodeoffset) {
return parent;
}
if (depth == target_depth - 1) {
parent = off;
}
}
return -FDT_ERR_NOTFOUND;
}
int fdt_get_alias(const fdt_ctx* ctx, const char* name)
{
const char* path;
int aliases, len = 0;
if (!fdt_ctx_ok(ctx) || name == NULL) {
return -FDT_ERR_BADARG;
}
aliases = fdt_subnode_offset(ctx, 0, "aliases");
if (aliases < 0) {
return aliases;
}
path = (const char*)fdt_getprop(ctx, aliases, name, &len);
if (path == NULL || len <= 1) {
return -FDT_ERR_NOTFOUND;
}
/* Must be a NUL-terminated absolute path. */
if (path[len - 1] != '\0' || path[0] != '/') {
return -FDT_ERR_BADSTRUCTURE;
}
return fdt_path_offset(ctx, path);
}
int fdt_get_reg(const fdt_ctx* ctx, int nodeoffset, int index,
uint64_t* addr, uint64_t* size)
{
const uint8_t* reg;
const uint8_t* cell;
const void* val;
/* Devicetree spec defaults when a parent omits the properties. */
uint32_t ac = 2, sc = 1;
uint32_t entry;
int parent, len = 0;
if (!fdt_ctx_ok(ctx) || index < 0) {
return -FDT_ERR_BADARG;
}
parent = fdt_parent_offset(ctx, nodeoffset);
if (parent >= 0) {
val = fdt_getprop(ctx, parent, "#address-cells", &len);
if (val != NULL && len == 4) {
ac = fdt_rd32(val);
}
len = 0;
val = fdt_getprop(ctx, parent, "#size-cells", &len);
if (val != NULL && len == 4) {
sc = fdt_rd32(val);
}
}
/* Only 1 or 2 cells fit uint64_t. sc may be 0; ac may not. */
if (ac < 1U || ac > 2U || sc > 2U) {
return -FDT_ERR_BADSTRUCTURE;
}
entry = (ac + sc) * 4U;
len = 0;
reg = (const uint8_t*)fdt_getprop(ctx, nodeoffset, "reg", &len);
if (reg == NULL || len <= 0) {
return -FDT_ERR_NOTFOUND;
}
/* A whole number of entries, or the property is malformed: trailing
* cells would otherwise be ignored silently. */
if (((uint32_t)len % entry) != 0U) {
return -FDT_ERR_BADSTRUCTURE;
}
/* By division: (index + 1) * entry is 32-bit and a large index wraps. */
if ((uint32_t)index >= ((uint32_t)len / entry)) {
return -FDT_ERR_NOTFOUND;
}
cell = reg + ((uint32_t)index * entry);
if (addr != NULL) {
*addr = (ac == 2U) ? fdt_rd64u(cell) : (uint64_t)fdt_rd32(cell);
}
if (size != NULL) {
cell += ac * 4U;
if (sc == 0U) {
*size = 0;
}
else {
*size = (sc == 2U) ? fdt_rd64u(cell) : (uint64_t)fdt_rd32(cell);
}
}
return 0;
}
/* Shared walk for the tree-wide searches. `propname` NULL matches the
* node name; otherwise the named property must equal `needle` whole. The
* compatible search keeps its own loop so a target that never does one

View File

@ -449,6 +449,17 @@ static int slot_prepare(struct boot_slot *s, int part, const char *label,
* the OS image from disk partitions. It then verifies the integrity and
* authenticity of the loaded image before initiating the boot.
*/
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* File scope so wolfBoot_get_dts_address() can hand it to a hook; exactly
* one update strategy object is linked per build. */
static void* wolfboot_dts_addr = NULL;
void* wolfBoot_get_dts_address(void)
{
return wolfboot_dts_addr;
}
#endif
void RAMFUNCTION wolfBoot_start(void)
{
uint8_t p_hdr[IMAGE_HEADER_SIZE] XALIGNED_STACK(16);
@ -967,6 +978,16 @@ void RAMFUNCTION wolfBoot_start(void)
/* Deferred from just after verification (see NOTE above): close the boot
* disk now that all env / DTB reads and writes are done, before handoff. */
disk_close(BOOT_DISK);
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* After every relocation/fallback/digest check, so a hook never sees
* an unvalidated blob. */
wolfboot_dts_addr = (void*)dts_addr;
#endif
#ifdef WOLFBOOT_HOOK_PREBOOT
/* Before hal_prepare_boot(), so a hook still has the MMU and caches as
* wolfBoot set them up. */
wolfBoot_hook_preboot(&os_image);
#endif
hal_prepare_boot();
#ifdef WOLFBOOT_HOOK_BOOT

View File

@ -1535,6 +1535,15 @@ int wolfBoot_unlock_disk(void)
#ifdef __CCRX__
#pragma section FRAM
#endif
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* No device tree here, but hooks.h advertises the accessor for every
* MMU/WOLFBOOT_FDT build, so a conforming hook must still link. */
void* wolfBoot_get_dts_address(void)
{
return NULL;
}
#endif
void RAMFUNCTION wolfBoot_start(void)
{
int bootRet;
@ -1741,6 +1750,11 @@ void RAMFUNCTION wolfBoot_start(void)
wolfBoot_printf("Error protecting bootloader flash region\n");
wolfBoot_panic();
}
#endif
#ifdef WOLFBOOT_HOOK_PREBOOT
/* Before hal_prepare_boot(), so a hook still has the MMU and caches as
* wolfBoot set them up. */
wolfBoot_hook_preboot(&boot);
#endif
hal_prepare_boot();

View File

@ -42,6 +42,15 @@ static inline void boot_panic(void)
;
}
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* No device tree here, but hooks.h advertises the accessor for every
* MMU/WOLFBOOT_FDT build, so a conforming hook must still link. */
void* wolfBoot_get_dts_address(void)
{
return NULL;
}
#endif
void RAMFUNCTION wolfBoot_start(void)
{
int active;
@ -133,6 +142,11 @@ void RAMFUNCTION wolfBoot_start(void)
#ifndef TZEN
if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0)
boot_panic();
#endif
#ifdef WOLFBOOT_HOOK_PREBOOT
/* Before hal_prepare_boot(), so a hook still has the MMU and caches as
* wolfBoot set them up. */
wolfBoot_hook_preboot(&fw_image);
#endif
hal_prepare_boot();
#ifdef WOLFBOOT_HOOK_BOOT

View File

@ -267,6 +267,17 @@ static int uboot_legacy_header_valid(const uint8_t *hdr, uint32_t total)
}
#endif /* WOLFBOOT_UBOOT_LEGACY */
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* File scope so wolfBoot_get_dts_address() can hand it to a hook; exactly
* one update strategy object is linked per build. */
static void* wolfboot_dts_addr = NULL;
void* wolfBoot_get_dts_address(void)
{
return wolfboot_dts_addr;
}
#endif
void RAMFUNCTION wolfBoot_start(void)
{
int active = -1, ret = 0;
@ -807,6 +818,16 @@ backup_on_failure:
wolfBoot_printf("Error protecting bootloader flash region\n");
wolfBoot_panic();
}
#endif
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* After every relocation/fallback/digest check, so a hook never sees
* an unvalidated blob. */
wolfboot_dts_addr = (void*)dts_addr;
#endif
#ifdef WOLFBOOT_HOOK_PREBOOT
/* Before hal_prepare_boot(), so a hook still has the MMU and caches as
* wolfBoot set them up. */
wolfBoot_hook_preboot(&os_image);
#endif
hal_prepare_boot();

View File

@ -917,6 +917,417 @@ START_TEST(test_fit_find_images_rejects_unterminated_image_name)
}
END_TEST
/* ------------------------------------------------------------------ */
/* fdt_parent_offset / fdt_get_alias / fdt_get_reg */
/* ------------------------------------------------------------------ */
/* A real dtc-compiled v17 blob, so these tests exercise the same byte
* layout a board hands wolfBoot rather than a hand-assembled structure
* block. The serial@80020000 node and the Chassis_Manager alias are
* copied verbatim from a customer device tree (an AXI 16550 in the PL).
* Regenerate with: dtc -I dts -O dtb -o chassis.dtb chassis.dts
*
* /dts-v1/;
* / {
* #address-cells = <0x02>;
* #size-cells = <0x02>;
* compatible = "xlnx,zynqmp";
* aliases {
* serial0 = "/axi/serial@ff000000";
* serial2 = "/amba_pl@0/serial@80020000";
* Chassis_Manager = "/amba_pl@0/serial@80020000";
* broken_alias = "no-leading-slash";
* };
* axi {
* #address-cells = <0x02>;
* #size-cells = <0x02>;
* compatible = "simple-bus";
* serial@ff000000 {
* compatible = "cdns,uart-r1p12";
* reg = <0x00 0xff000000 0x00 0x1000>;
* current-speed = <0x1c200>;
* };
* };
* amba_pl@0 {
* #address-cells = <0x02>;
* #size-cells = <0x02>;
* compatible = "simple-bus";
* ranges;
* serial@80020000 {
* clock-frequency = <0x5f5dd19>;
* clock-names = "s_axi_aclk";
* compatible = "xlnx,xps-uart16550-2.00.a", "ns16550a";
* current-speed = <0x1c200>;
* device_type = "serial";
* port-number = <0x03>;
* reg = <0x00 0x80020000 0x00 0x10000>;
* reg-offset = <0x1000>;
* reg-shift = <0x02>;
* xlnx,is-a-16550 = <0x01>;
* };
* };
* onecell {
* #address-cells = <0x01>;
* #size-cells = <0x01>;
* widget@1000 {
* reg = <0x1000 0x40 0x2000 0x80>;
* };
* };
* threecell {
* #address-cells = <0x03>;
* #size-cells = <0x01>;
* widget@0 {
* reg = <0x00 0x00 0x1000 0x40>;
* };
* };
* };
*/
static const uint8_t chassis_dtb[] = {
0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x04, 0x99, 0x00, 0x00, 0x00, 0x38,
0x00, 0x00, 0x03, 0xd4, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5,
0x00, 0x00, 0x03, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1b,
0x78, 0x6c, 0x6e, 0x78, 0x2c, 0x7a, 0x79, 0x6e, 0x71, 0x6d, 0x70, 0x00,
0x00, 0x00, 0x00, 0x01, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x26,
0x2f, 0x61, 0x78, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x40,
0x66, 0x66, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x2e,
0x2f, 0x61, 0x6d, 0x62, 0x61, 0x5f, 0x70, 0x6c, 0x40, 0x30, 0x2f, 0x73,
0x65, 0x72, 0x69, 0x61, 0x6c, 0x40, 0x38, 0x30, 0x30, 0x32, 0x30, 0x30,
0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1b,
0x00, 0x00, 0x00, 0x36, 0x2f, 0x61, 0x6d, 0x62, 0x61, 0x5f, 0x70, 0x6c,
0x40, 0x30, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x40, 0x38, 0x30,
0x30, 0x32, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x6e, 0x6f, 0x2d, 0x6c,
0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x61, 0x78, 0x69, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1b,
0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x62, 0x75, 0x73, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x40, 0x66,
0x66, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1b, 0x63, 0x64, 0x6e, 0x73,
0x2c, 0x75, 0x61, 0x72, 0x74, 0x2d, 0x72, 0x31, 0x70, 0x31, 0x32, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x53,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x57, 0x00, 0x01, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x61, 0x6d, 0x62, 0x61,
0x5f, 0x70, 0x6c, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b,
0x00, 0x00, 0x00, 0x1b, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x62,
0x75, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x01, 0x73, 0x65, 0x72, 0x69,
0x61, 0x6c, 0x40, 0x38, 0x30, 0x30, 0x32, 0x30, 0x30, 0x30, 0x30, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6c,
0x05, 0xf5, 0xdd, 0x19, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b,
0x00, 0x00, 0x00, 0x7c, 0x73, 0x5f, 0x61, 0x78, 0x69, 0x5f, 0x61, 0x63,
0x6c, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x23,
0x00, 0x00, 0x00, 0x1b, 0x78, 0x6c, 0x6e, 0x78, 0x2c, 0x78, 0x70, 0x73,
0x2d, 0x75, 0x61, 0x72, 0x74, 0x31, 0x36, 0x35, 0x35, 0x30, 0x2d, 0x32,
0x2e, 0x30, 0x30, 0x2e, 0x61, 0x00, 0x6e, 0x73, 0x31, 0x36, 0x35, 0x35,
0x30, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x57, 0x00, 0x01, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x88, 0x73, 0x65, 0x72, 0x69,
0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00,
0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa0,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xb5, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x6f, 0x6e, 0x65, 0x63, 0x65, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x77, 0x69, 0x64, 0x67,
0x65, 0x74, 0x40, 0x31, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x74, 0x68, 0x72, 0x65, 0x65, 0x63, 0x65, 0x6c, 0x6c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x77, 0x69, 0x64, 0x67, 0x65, 0x74, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x53,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x23, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x23,
0x73, 0x69, 0x7a, 0x65, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x63,
0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x00, 0x73, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x30, 0x00, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
0x32, 0x00, 0x43, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x5f, 0x4d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x72, 0x00, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e,
0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x00, 0x72, 0x65, 0x67, 0x00, 0x63,
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x70, 0x65, 0x65, 0x64,
0x00, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x00, 0x63, 0x6c, 0x6f, 0x63,
0x6b, 0x2d, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x00,
0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x00,
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x00,
0x70, 0x6f, 0x72, 0x74, 0x2d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x00,
0x72, 0x65, 0x67, 0x2d, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x72,
0x65, 0x67, 0x2d, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x78, 0x6c, 0x6e,
0x78, 0x2c, 0x69, 0x73, 0x2d, 0x61, 0x2d, 0x31, 0x36, 0x35, 0x35, 0x30,
0x00,
};
START_TEST(test_fdt_get_alias_resolves_named_node)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
int off, pl;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
off = fdt_get_alias(&ctx, "Chassis_Manager");
ck_assert_int_ge(off, 0);
/* same node the literal path resolves to */
ck_assert_int_eq(off, fdt_path_offset(&ctx, "/amba_pl@0/serial@80020000"));
/* and the same node as the conventional serial2 alias */
ck_assert_int_eq(off, fdt_get_alias(&ctx, "serial2"));
/* it really is the PL UART, not the PS one */
ck_assert_ptr_nonnull(fdt_getprop(&ctx, off, "xlnx,is-a-16550", NULL));
pl = fdt_path_offset(&ctx, "/axi/serial@ff000000");
ck_assert_int_ge(pl, 0);
ck_assert_int_ne(off, pl);
}
END_TEST
START_TEST(test_fdt_get_alias_rejects_bad_input)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
ck_assert_int_eq(fdt_get_alias(&ctx, NULL), -FDT_ERR_BADARG);
ck_assert_int_eq(fdt_get_alias(NULL, "serial2"), -FDT_ERR_BADARG);
ck_assert_int_eq(fdt_get_alias(&ctx, "no_such_alias"), -FDT_ERR_NOTFOUND);
/* value is not an absolute path */
ck_assert_int_eq(fdt_get_alias(&ctx, "broken_alias"),
-FDT_ERR_BADSTRUCTURE);
}
END_TEST
START_TEST(test_fdt_get_reg_two_address_two_size_cells)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
uint64_t addr = 0, size = 0;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
off = fdt_get_alias(&ctx, "Chassis_Manager");
ck_assert_int_ge(off, 0);
/* the shape fdt_getprop_address() cannot decode: 2 + 2 cells */
ck_assert_ptr_null(fdt_getprop_address(&ctx, off, "reg"));
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, &size), 0);
ck_assert_uint_eq(addr, 0x80020000ULL);
ck_assert_uint_eq(size, 0x10000ULL);
/* both outputs are optional */
addr = 0;
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, NULL), 0);
ck_assert_uint_eq(addr, 0x80020000ULL);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, NULL, NULL), 0);
/* only one entry in this reg */
ck_assert_int_eq(fdt_get_reg(&ctx, off, 1, &addr, &size),
-FDT_ERR_NOTFOUND);
}
END_TEST
START_TEST(test_fdt_get_reg_one_cell_and_index)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
uint64_t addr = 0, size = 0;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
/* parent declares 1 address + 1 size cell and the reg holds two
* entries, so indexing has to honor the parent's cell counts. */
off = fdt_path_offset(&ctx, "/onecell/widget@1000");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, &size), 0);
ck_assert_uint_eq(addr, 0x1000U);
ck_assert_uint_eq(size, 0x40U);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 1, &addr, &size), 0);
ck_assert_uint_eq(addr, 0x2000U);
ck_assert_uint_eq(size, 0x80U);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 2, &addr, &size),
-FDT_ERR_NOTFOUND);
ck_assert_int_eq(fdt_get_reg(&ctx, off, -1, &addr, &size),
-FDT_ERR_BADARG);
/* An index whose (index + 1) * entry_size wraps 32 bits must still be
* rejected: a product-based bounds check would pass it and then read
* far outside the property. */
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0x1FFFFFFF, &addr, &size),
-FDT_ERR_NOTFOUND);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0x7FFFFFFF, &addr, &size),
-FDT_ERR_NOTFOUND);
}
END_TEST
START_TEST(test_fdt_get_reg_rejects_uncodable_cell_count)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
uint64_t addr = 0, size = 0;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
/* A 3-cell address does not fit the uint64_t output. Fail closed rather
* than silently truncating it to the low two cells. */
off = fdt_path_offset(&ctx, "/threecell/widget@0");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, &size),
-FDT_ERR_BADSTRUCTURE);
}
END_TEST
START_TEST(test_fdt_get_reg_rejects_partial_entry)
{
static uint8_t buf[sizeof(chassis_dtb) + 64] __attribute__((aligned(8)));
/* 1 address + 1 size cell, so entries are 8 bytes: this is one whole
* entry plus a stray cell. */
static const uint8_t partial[12] = {
0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x40,
0x00, 0x00, 0x20, 0x00
};
fdt_ctx ctx;
uint64_t addr = 0, size = 0;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(buf)), 0);
off = fdt_path_offset(&ctx, "/onecell/widget@1000");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_setprop(&ctx, off, "reg", partial, sizeof(partial)),
0);
/* Even index 0, which lies entirely inside the property, is refused:
* a reg that is not a whole number of entries is malformed. */
off = fdt_path_offset(&ctx, "/onecell/widget@1000");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, &size),
-FDT_ERR_BADSTRUCTURE);
}
END_TEST
/* With no usable #address-cells/#size-cells on the parent, the Devicetree
* spec defaults apply: two address cells and one size cell. The reg below
* decodes to 0x1000/0x40 only under those defaults - at 1/1 it would give
* 0x0/0x1000 instead, so this case discriminates. */
START_TEST(test_fdt_get_reg_uses_spec_default_cells)
{
static uint8_t buf[sizeof(chassis_dtb) + 64] __attribute__((aligned(8)));
/* <0x0 0x1000 0x40>: one entry of 2 address cells + 1 size cell. */
static const uint8_t reg3[12] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x40
};
fdt_ctx ctx;
uint64_t addr = 0, size = 0;
int parent;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(buf)), 0);
/* Strip the parent's cell declarations, leaving nothing usable. */
parent = fdt_path_offset(&ctx, "/onecell");
ck_assert_int_ge(parent, 0);
ck_assert_int_eq(fdt_setprop(&ctx, parent, "#address-cells", NULL, 0), 0);
parent = fdt_path_offset(&ctx, "/onecell");
ck_assert_int_ge(parent, 0);
ck_assert_int_eq(fdt_setprop(&ctx, parent, "#size-cells", NULL, 0), 0);
off = fdt_path_offset(&ctx, "/onecell/widget@1000");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_setprop(&ctx, off, "reg", reg3, sizeof(reg3)), 0);
off = fdt_path_offset(&ctx, "/onecell/widget@1000");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, &size), 0);
ck_assert_uint_eq(addr, 0x1000U);
ck_assert_uint_eq(size, 0x40U);
/* Only one entry is present at that width. */
ck_assert_int_lt(fdt_get_reg(&ctx, off, 1, &addr, &size), 0);
}
END_TEST
START_TEST(test_fdt_get_reg_missing_reg_property)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
uint64_t addr = 0;
int off;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
off = fdt_path_offset(&ctx, "/aliases");
ck_assert_int_ge(off, 0);
ck_assert_int_eq(fdt_get_reg(&ctx, off, 0, &addr, NULL),
-FDT_ERR_NOTFOUND);
}
END_TEST
START_TEST(test_fdt_parent_offset_walks_up)
{
static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8)));
fdt_ctx ctx;
int uart, bus;
memcpy(buf, chassis_dtb, sizeof(chassis_dtb));
ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(chassis_dtb)), 0);
uart = fdt_get_alias(&ctx, "Chassis_Manager");
ck_assert_int_ge(uart, 0);
bus = fdt_parent_offset(&ctx, uart);
ck_assert_int_eq(bus, fdt_path_offset(&ctx, "/amba_pl@0"));
/* one more level up lands on the root */
ck_assert_int_eq(fdt_parent_offset(&ctx, bus), 0);
/* the root itself has no parent */
ck_assert_int_eq(fdt_parent_offset(&ctx, 0), -FDT_ERR_NOTFOUND);
ck_assert_int_eq(fdt_parent_offset(&ctx, -1), -FDT_ERR_BADARG);
}
END_TEST
static Suite *fdt_suite(void)
{
Suite *s = suite_create("fdt");
@ -951,6 +1362,15 @@ static Suite *fdt_suite(void)
tcase_add_test(tc, test_fdt_fixup_initrd_rejects_wrapped_end);
tcase_add_test(tc, test_fdt_peek_size_header_only);
tcase_add_test(tc, test_fit_find_images_rejects_unterminated_image_name);
tcase_add_test(tc, test_fdt_get_alias_resolves_named_node);
tcase_add_test(tc, test_fdt_get_alias_rejects_bad_input);
tcase_add_test(tc, test_fdt_get_reg_two_address_two_size_cells);
tcase_add_test(tc, test_fdt_get_reg_one_cell_and_index);
tcase_add_test(tc, test_fdt_get_reg_rejects_partial_entry);
tcase_add_test(tc, test_fdt_get_reg_uses_spec_default_cells);
tcase_add_test(tc, test_fdt_get_reg_missing_reg_property);
tcase_add_test(tc, test_fdt_get_reg_rejects_uncodable_cell_count);
tcase_add_test(tc, test_fdt_parent_offset_walks_up);
suite_add_tcase(s, tc);
tcase_set_timeout(tc_dos, 5);