diff --git a/.github/workflows/test-hooks-simulator.yml b/.github/workflows/test-hooks-simulator.yml index 2db52abe72..27f8f97d56 100644 --- a/.github/workflows/test-hooks-simulator.yml +++ b/.github/workflows/test-hooks-simulator.yml @@ -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 }} diff --git a/docs/hooks.md b/docs/hooks.md index 48223eb905..29ab73b9c0 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -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. diff --git a/include/fdt.h b/include/fdt.h index b25646d89d..f14889de83 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -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 diff --git a/include/hooks.h b/include/hooks.h index 9e027c9d25..68295dfa60 100644 --- a/include/hooks.h +++ b/include/hooks.h @@ -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 diff --git a/options.mk b/options.mk index ce6915d098..da4fb4d548 100644 --- a/options.mk +++ b/options.mk @@ -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 diff --git a/src/fdt.c b/src/fdt.c index fe08da552b..1bfb96caaa 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -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 diff --git a/src/update_disk.c b/src/update_disk.c index 0d77858c6d..9a2b3036e2 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -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); @@ -964,6 +975,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 diff --git a/src/update_flash.c b/src/update_flash.c index 58a23f9e86..bebb4adae0 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -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(); diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index 888dface14..cf5ff29276 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -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 diff --git a/src/update_ram.c b/src/update_ram.c index d5a541dfee..3b935fe476 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -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 @@ void RAMFUNCTION wolfBoot_start(void) 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(); diff --git a/tools/unit-tests/unit-fdt.c b/tools/unit-tests/unit-fdt.c index 83044cf115..8316cfae05 100644 --- a/tools/unit-tests/unit-fdt.c +++ b/tools/unit-tests/unit-fdt.c @@ -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);