diff --git a/arch.mk b/arch.mk index 1973d5fdc0..5f8bb8acd9 100644 --- a/arch.mk +++ b/arch.mk @@ -74,6 +74,8 @@ ifeq ($(ARCH),AARCH64) # Support detection and skip of U-Boot legacy header */ CFLAGS+=-DWOLFBOOT_UBOOT_LEGACY CFLAGS+=-DWOLFBOOT_DUALBOOT + # FPGA bitstream-from-FIT is supported via the PMU firmware + # (PM_FPGA_LOAD). Enable with FPGA_BITSTREAM=1 (opt-in). ifeq ($(HW_SHA3),1) # Use HAL for hash (see zynqmp.c) @@ -90,6 +92,9 @@ ifeq ($(ARCH),AARCH64) CFLAGS+=-DWOLFBOOT_DUALBOOT # Support detection and skip of U-Boot legacy header CFLAGS+=-DWOLFBOOT_UBOOT_LEGACY + # NOTE: FPGA_BITSTREAM is stubbed on Versal (PL config is a PDI loaded + # by the PLM via XilLoader Load-PDI IPI - not yet implemented). Leave + # FPGA_BITSTREAM off until that path lands. # PLM owns RVBAR on Versal in JTAG boot; skip RVBAR writes CFLAGS+=-DSKIP_RVBAR=1 # Disable SDMA for multi-block transfers - use PIO instead. @@ -350,6 +355,8 @@ ifeq ($(ARCH),ARM) # positive probability from ~2^-32 to ~2^-64, matching what U-Boot's # own mkimage/bootm does. CFLAGS+=-DWOLFBOOT_UBOOT_LEGACY + # FPGA bitstream-from-FIT is supported via the DevC/PCAP DMA engine + # (full bitstream). Enable with FPGA_BITSTREAM=1 (opt-in). endif ifeq ($(TARGET),va416x0) diff --git a/docs/Targets.md b/docs/Targets.md index 1581f3a224..4e00fe734b 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -4070,6 +4070,58 @@ FDT: Set chosen (...), linux,initrd-start=1073741824 FDT: Set chosen (...), linux,initrd-end=... ``` +**FPGA bitstream from FIT** + +wolfBoot can program the PL (FPGA fabric) from an `fpga` sub-image carried in the same signed FIT, using the standard U-Boot convention: a sub-image with `type = "fpga"`, referenced from the configuration node via an optional `fpga = ""` property, with a `compatible` string naming the load method. The PL is programmed before the kernel/DTB are loaded so PL-dependent clocks and peripherals come up first. The outer wolfBoot signature authenticates the whole FIT (bitstream included), so no per-image hashing is required. + +Enable with `FPGA_BITSTREAM=1` (off by default). A failed PL load is fatal (`wolfBoot_panic`) unless `FPGA_NONFATAL=1` is also set, which downgrades it to a logged warning that continues the boot. + +```sh +cp config/examples/zynqmp.config .config +make FPGA_BITSTREAM=1 +``` + +Per-target support: +- **ZynqMP** (`TARGET=zynq`): supported. The bitstream (a bootgen `.bin`, not `.bit`) is staged to its `load` address in DDR and handed to the PMU firmware via the `PM_FPGA_LOAD` EEMI call (xilfpga over the CSU DMA / PCAP). +- **Zynq-7000** (`TARGET=zynq7000`): supported (full bitstream). Programmed directly through the DevC/PCAP DMA engine (UG585 ch.6). Partial reconfiguration is not yet implemented. +- **Versal** (`TARGET=versal`): not yet implemented - Versal programs the PL with a PDI loaded by the PLM (XilLoader Load-PDI IPI), not a raw bitstream. `hal_fpga_load` is a stub; leave `FPGA_BITSTREAM` off on Versal until that path lands. + +The `compatible` string selects full vs partial: any value containing `partial` requests partial reconfiguration, otherwise a full bitstream is loaded. Typical full-bitstream values are `u-boot,zynqmp-fpga-ddrauth`, `u-boot,zynqmp-fpga-enc`, or `u-boot,fpga-legacy`. + +Example FIT layout: + +```dts +images { + kernel-1 { ... }; + fdt-1 { ... }; + fpga-1 { + description = "FPGA bitstream"; + data = /incbin/("system.bit.bin"); /* bootgen .bin */ + type = "fpga"; + arch = "arm64"; /* "arm" for zynq7000 */ + compression = "none"; + load = <0x10000000>; /* DDR staging address */ + compatible = "u-boot,zynqmp-fpga-ddrauth"; + hash-1 { algo = "sha256"; }; + }; +}; +configurations { + default = "conf-zcu102"; + conf-zcu102 { + kernel = "kernel-1"; + fdt = "fdt-1"; + fpga = "fpga-1"; + }; +}; +``` + +Successful programming prints (ZynqMP): +``` +FIT: programming FPGA 'fpga-1' (N bytes, full) +FPGA status: 0x... +FIT: FPGA programmed +``` + ## Xilinx Zynq-7000 (ZC702) diff --git a/hal/versal.c b/hal/versal.c index 8be09fc5a7..29b5211547 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -1354,6 +1354,25 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) return -1; } +#ifdef WOLFBOOT_FPGA_BITSTREAM +/* Versal programs the PL with a PDI (Programmable Device Image), not a raw + * bitstream. Runtime PL configuration is done by the PLM via the XilLoader + * "Load PDI" command (XLOADER_CMD_ID_LOAD_PDI), reached by building an IPI + * request to the PLM channel with the DDR PDI address/size and polling the + * PLM response. That path is not yet implemented; until it lands, leave + * FPGA_BITSTREAM disabled for Versal (the FIT loader treats a failed load + * as fatal unless FPGA_NONFATAL is set). */ +int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size) +{ + (void)flags; + (void)addr; + (void)size; + wolfBoot_printf("Versal FPGA/PDI load not implemented " + "(needs PLM XilLoader Load-PDI IPI)\n"); + return -1; +} +#endif /* WOLFBOOT_FPGA_BITSTREAM */ + /* ============================================================================ * External Flash Interface diff --git a/hal/versal.its b/hal/versal.its index 2b160cb3a1..8fb13aa36c 100644 --- a/hal/versal.its +++ b/hal/versal.its @@ -29,6 +29,27 @@ algo = "sha256"; }; }; + /* FPGA bitstream sub-image (requires FPGA_BITSTREAM=1). Add the + * "fpga = ..." reference to the configuration node below. The + * bitstream must be a bootgen .bin staged to its load address. + * NOTE: Versal PL config is a PDI loaded by the PLM (XilLoader + * Load-PDI IPI) - the wolfBoot Versal hal_fpga_load is currently + * a stub, so leave this disabled on Versal. On ZynqMP/Zynq-7000 + * this is the working convention. + * + * fpga-1 { + * description = "FPGA bitstream"; + * data = /incbin/("../system.bit.bin"); + * type = "fpga"; + * arch = "arm64"; + * compression = "none"; + * load = <0x10000000>; + * compatible = "u-boot,zynqmp-fpga-ddrauth"; + * hash-1 { + * algo = "sha256"; + * }; + * }; + */ }; configurations { default = "conf1"; @@ -36,6 +57,7 @@ description = "Linux kernel and FDT blob"; kernel = "kernel-1"; fdt = "fdt-1"; + /* fpga = "fpga-1"; */ hash-1 { algo = "sha256"; }; diff --git a/hal/zynq.c b/hal/zynq.c index fb1262345d..6538b32d70 100644 --- a/hal/zynq.c +++ b/hal/zynq.c @@ -197,6 +197,13 @@ static void smc_call(struct pt_regs *args) #define PM_MMIO_WRITE 0x13 #define PM_MMIO_READ 0x14 +/* FPGA / PL programming (xilfpga via PMU firmware / TF-A) */ +#define PM_FPGA_LOAD 0x16 /* 22 */ +#define PM_FPGA_GET_STATUS 0x17 /* 23 */ +/* pm_fpga_load flags (bit 0 selects full vs partial bitstream) */ +#define XFPGA_FULLBIT_EN 0x0 +#define XFPGA_PARTIAL_EN 0x1 + /* AES */ /* requires PMU built with -DENABLE_SECURE_VAL=1 */ #define PM_SECURE_AES 0x2F @@ -1713,6 +1720,43 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) return 0; } +#ifdef WOLFBOOT_FPGA_BITSTREAM +/* Program the PL by handing the bitstream to the PMU firmware (xilfpga) + * via the PM_FPGA_LOAD EEMI call. The bitstream must be a bootgen .bin + * resident in DDR; it is flushed from the D-cache so the CSU DMA sees + * the committed bytes. */ +int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size) +{ + uint32_t ret_payload[PM_ARGS_CNT]; + uint32_t pmflags; + uint32_t words; + + /* PMU firmware expects the size as a count of 32-bit words. */ + words = (uint32_t)((size + 3U) / 4U); + pmflags = (flags == HAL_FPGA_PARTIAL) ? XFPGA_PARTIAL_EN : XFPGA_FULLBIT_EN; + + /* Ensure the bitstream is committed to DDR before the CSU DMA reads it. */ + flush_dcache_range((unsigned long)addr, (unsigned long)(addr + size)); + + memset(ret_payload, 0, sizeof(ret_payload)); + /* arg0=addr_low, arg1=addr_high, arg2=size(words), arg3=flags */ + pmu_request(PM_FPGA_LOAD, + (uint32_t)(addr & 0xFFFFFFFF), (uint32_t)((uint64_t)addr >> 32), + words, pmflags, ret_payload); + if (ret_payload[0] != 0) { + wolfBoot_printf("PM_FPGA_LOAD failed: %u\n", ret_payload[0]); + return -1; + } + + /* Confirm the PL reports configured (PCAP status). */ + memset(ret_payload, 0, sizeof(ret_payload)); + pmu_request(PM_FPGA_GET_STATUS, 0, 0, 0, 0, ret_payload); + wolfBoot_printf("FPGA status: 0x%x\n", ret_payload[1]); + + return 0; +} +#endif /* WOLFBOOT_FPGA_BITSTREAM */ + /* Xilinx Write uses SPI mode and Page Program 0x02 */ /* Issues using write with QSPI mode */ int RAMFUNCTION ext_flash_write(uintptr_t address, const uint8_t *data, int len) diff --git a/hal/zynq7000.c b/hal/zynq7000.c index a6a9850f33..2bee9385ef 100644 --- a/hal/zynq7000.c +++ b/hal/zynq7000.c @@ -871,6 +871,114 @@ uint64_t hal_get_timer_us(void) return (count * 1000000ULL) / (uint64_t)Z7_GTIMER_FREQ_HZ; } +#ifdef WOLFBOOT_FPGA_BITSTREAM +/* PL programming timeout (microseconds). */ +#ifndef Z7_FPGA_TIMEOUT_US +#define Z7_FPGA_TIMEOUT_US 1000000ULL /* 1 second */ +#endif + +/* Clean the D-cache over [start, start+len) by MVA so the DevC DMA sees + * the committed bitstream bytes (DCCMVAC, L1 line = 32B). Mirrors the + * SDMA coherency path. */ +static void z7_dcache_clean_range(uintptr_t start, uint32_t len) +{ + uintptr_t addr; + uintptr_t end = (start + len + 31U) & ~31U; + start &= ~31U; + for (addr = start; addr < end; addr += 32U) { + __asm__ volatile("mcr p15, 0, %0, c7, c10, 1" : : "r"(addr) : "memory"); + } + __asm__ volatile("dsb sy" : : : "memory"); +} + +/* Program the PL from a bootgen .bin bitstream resident in DDR using the + * DevC PCAP DMA engine (UG585 ch.6 / Xilinx XDcfg full-bitstream flow). + * Only the full-bitstream path is implemented; partial reconfiguration + * returns an error. */ +int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size) +{ + uint32_t sts; + uint32_t words = (uint32_t)((size + 3U) / 4U); + uint64_t t0; + + if (flags == HAL_FPGA_PARTIAL) { + /* Partial reconfig leaves PROG_B asserted and routes via PCAP_PR; + * not implemented in this initial full-bitstream path. */ + wolfBoot_printf("Z7 FPGA: partial reconfig not implemented\n"); + return -1; + } + if (addr > 0xFFFFFFFFU || size == 0) { + return -1; + } + + /* 1. Unlock DevC and select PCAP (not ICAP). */ + Z7_DEVC_UNLOCK = Z7_DEVC_UNLOCK_KEY; + Z7_DEVC_CTRL |= (Z7_DEVC_CTRL_PCAP_MODE | Z7_DEVC_CTRL_PCAP_PR); + /* Disable internal PCAP loopback. */ + Z7_DEVC_MCTRL &= ~Z7_DEVC_MCTRL_PCAP_LPBK; + + /* 2. Clear sticky interrupts. */ + Z7_DEVC_INT_STS = Z7_DEVC_INT_ALL; + + /* 3. Pulse PROG_B low then high to clear the PL (full bitstream). */ + Z7_DEVC_CTRL &= ~Z7_DEVC_CTRL_PCFG_PROG_B; + t0 = hal_get_timer_us(); + while (Z7_DEVC_STATUS & Z7_DEVC_STATUS_PCFG_INIT) { + if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) { + wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_INIT clear\n"); + return -1; + } + } + Z7_DEVC_CTRL |= Z7_DEVC_CTRL_PCFG_PROG_B; + t0 = hal_get_timer_us(); + while (!(Z7_DEVC_STATUS & Z7_DEVC_STATUS_PCFG_INIT)) { + if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) { + wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_INIT set\n"); + return -1; + } + } + + /* 4. Clear interrupts and flush the config FIFOs. */ + Z7_DEVC_INT_STS = Z7_DEVC_INT_ALL; + Z7_DEVC_MCTRL |= (Z7_DEVC_MCTRL_RFIFO_FLUSH | Z7_DEVC_MCTRL_WFIFO_FLUSH); + + /* 5. Make the bitstream coherent in DDR for the DMA. */ + z7_dcache_clean_range(addr, (uint32_t)size); + + /* 6. Program the DMA: src = DDR bitstream (LSB=1 marks last descriptor), + * dst = PCAP sentinel. Lengths are in 32-bit words. */ + Z7_DEVC_DMA_SRC = ((uint32_t)addr) | Z7_DEVC_DMA_LAST; + Z7_DEVC_DMA_DST = Z7_DEVC_DMA_DEST_PCAP; + Z7_DEVC_DMA_SRC_LEN = words; + Z7_DEVC_DMA_DST_LEN = words; + + /* 7. Wait for DMA done (and check error bits). */ + t0 = hal_get_timer_us(); + do { + sts = Z7_DEVC_INT_STS; + if (sts & Z7_DEVC_INT_ERR_MASK) { + wolfBoot_printf("Z7 FPGA: DMA error, INT_STS=0x%x\n", sts); + return -1; + } + if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) { + wolfBoot_printf("Z7 FPGA: timeout waiting DMA done\n"); + return -1; + } + } while (!(sts & Z7_DEVC_INT_DMA_DONE)); + + /* 8. Wait for the PL to report configuration complete. */ + t0 = hal_get_timer_us(); + while (!(Z7_DEVC_INT_STS & Z7_DEVC_INT_PCFG_DONE)) { + if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) { + wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_DONE\n"); + return -1; + } + } + + return 0; +} +#endif /* WOLFBOOT_FPGA_BITSTREAM */ + #if defined(DISK_SDCARD) || defined(DISK_EMMC) /* ============================================================================ * SDHCI (SD Card / eMMC) Platform Support diff --git a/hal/zynq7000.h b/hal/zynq7000.h index cfbb46b946..183e318d99 100644 --- a/hal/zynq7000.h +++ b/hal/zynq7000.h @@ -199,8 +199,51 @@ #define Z7_GTIMER_FREQ_HZ 333333333UL #endif -/* DevC (Device Configuration: AES + bitstream loader). UG585 ch.6. */ +/* DevC (Device Configuration: AES + bitstream loader). UG585 ch.6. + * Register offsets/bits mirror Xilinx xdevcfg_hw.h (XDCFG_*). */ #define Z7_DEVC_BASE 0xF8007000UL +#define Z7_DEVC_CTRL (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x00))) +#define Z7_DEVC_LOCK (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x04))) +#define Z7_DEVC_CFG (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x08))) +#define Z7_DEVC_INT_STS (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x0C))) +#define Z7_DEVC_INT_MASK (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x10))) +#define Z7_DEVC_STATUS (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x14))) +#define Z7_DEVC_DMA_SRC (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x18))) +#define Z7_DEVC_DMA_DST (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x1C))) +#define Z7_DEVC_DMA_SRC_LEN (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x20))) +#define Z7_DEVC_DMA_DST_LEN (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x24))) +#define Z7_DEVC_UNLOCK (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x34))) +#define Z7_DEVC_MCTRL (*((volatile uint32_t*)(Z7_DEVC_BASE + 0x80))) + +/* CTRL bits */ +#define Z7_DEVC_CTRL_FORCE_RST 0x80000000U /* bit 31 */ +#define Z7_DEVC_CTRL_PCFG_PROG_B 0x40000000U /* bit 30 */ +#define Z7_DEVC_CTRL_PCAP_PR 0x08000000U /* bit 27: 1=PCAP, 0=ICAP */ +#define Z7_DEVC_CTRL_PCAP_MODE 0x04000000U /* bit 26: PCAP enable */ +#define Z7_DEVC_CTRL_QUARTER_RATE 0x02000000U /* bit 25 */ + +/* INT_STS bits */ +#define Z7_DEVC_INT_DMA_DONE 0x00002000U /* bit 13 */ +#define Z7_DEVC_INT_D_P_DONE 0x00001000U /* bit 12: DMA+PCAP done */ +#define Z7_DEVC_INT_PCFG_DONE 0x00000004U /* bit 2 */ +/* DMA / AXI / config error aggregate (UG585 Table 6-x). */ +#define Z7_DEVC_INT_ERR_MASK 0x00F0C860U +#define Z7_DEVC_INT_ALL 0xFFFFFFFFU + +/* STATUS bits */ +#define Z7_DEVC_STATUS_DMA_CMD_FULL 0x80000000U /* bit 31 */ +#define Z7_DEVC_STATUS_PCFG_INIT 0x00000010U /* bit 4 */ + +/* MCTRL bits */ +#define Z7_DEVC_MCTRL_PCAP_LPBK 0x00000010U /* internal loopback */ +#define Z7_DEVC_MCTRL_RFIFO_FLUSH 0x00000400U /* bit 10 */ +#define Z7_DEVC_MCTRL_WFIFO_FLUSH 0x00000200U /* bit 9 */ + +/* UNLOCK magic and PCAP DMA sentinel (UG585 ch.6 / XDcfg). The DMA + * address LSB set to 1 marks the last (only) descriptor in the chain. */ +#define Z7_DEVC_UNLOCK_KEY 0x757BDF0DU +#define Z7_DEVC_DMA_DEST_PCAP 0xFFFFFFFFU +#define Z7_DEVC_DMA_LAST 0x00000001U /* GIC (PL390 / GIC-400 v1) - per-CPU interface and distributor. */ #define Z7_GIC_CPUIF_BASE 0xF8F00100UL diff --git a/include/fdt.h b/include/fdt.h index 69941c03ac..6c67acb45d 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -169,7 +169,10 @@ int fdt_shrink(void* fdt); /* FIT */ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, - const char** pramdisk); + const char** pramdisk, const char** pfpga); +/* Return the value of a subimage's "compatible" property (or NULL). Used + * to select the FPGA load method (full vs partial). */ +const char* fit_get_compatible(void* fdt, const char* image); void* fit_load_image(void* fdt, const char* image, int* lenp); void* fit_load_image_ex(void* fdt, const char* image, int* lenp, uint32_t out_max); /* Load (and, if compressed, decompress) a FIT subimage directly to a @@ -192,6 +195,15 @@ int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size); int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr); #endif +#ifdef WOLFBOOT_FPGA_BITSTREAM +/* Locate a FIT fpga subimage, stage it to its `load` address (or use + * the in-FIT data pointer) 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(void* fdt, const char* fpga_node); +#endif + #ifdef __cplusplus } #endif diff --git a/include/hal.h b/include/hal.h index bf2f5b138b..a8c7208268 100644 --- a/include/hal.h +++ b/include/hal.h @@ -108,6 +108,20 @@ void hal_prepare_boot(void); void *hal_get_dts_update_address(void); #endif +#ifdef WOLFBOOT_FPGA_BITSTREAM + /* FPGA load mode (flags argument to hal_fpga_load) */ + #define HAL_FPGA_FULL 0u /* full bitstream / device image */ + #define HAL_FPGA_PARTIAL 1u /* partial reconfiguration */ + /* + * Program the PL/FPGA fabric from an in-DDR bitstream/PDI image. + * addr/size describe the staged image buffer; the implementation + * is responsible for any required cache maintenance before the + * configuration engine reads it. Returns 0 on success, negative on + * error. The weak default returns -1 (not implemented). + */ + int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size); +#endif + #if !defined(SPI_FLASH) && !defined(QSPI_FLASH) && !defined(OCTOSPI_FLASH) /* user supplied external flash interfaces */ int ext_flash_write(uintptr_t address, const uint8_t *data, int len); diff --git a/options.mk b/options.mk index 52146818d1..f9192b5bb7 100644 --- a/options.mk +++ b/options.mk @@ -916,6 +916,20 @@ ifeq ($(FIT_RAMDISK),1) CFLAGS+=-DWOLFBOOT_FIT_RAMDISK endif +# FPGA_BITSTREAM=1 enables loading an "fpga" sub-image from a FIT and +# programming the PL before booting (Xilinx ZynqMP/Zynq-7000; Versal is +# stubbed pending a PLM Load-PDI path). Off by default. +FPGA_BITSTREAM ?= 0 +ifeq ($(FPGA_BITSTREAM),1) + CFLAGS+=-DWOLFBOOT_FPGA_BITSTREAM +endif +# FPGA_NONFATAL=1 downgrades a failed PL load from fatal (panic) to a +# logged warning that continues the boot. +FPGA_NONFATAL ?= 0 +ifeq ($(FPGA_NONFATAL),1) + CFLAGS+=-DWOLFBOOT_FPGA_NONFATAL +endif + ifeq ($(ARMORED),1) CFLAGS+=-DWOLFBOOT_ARMORED endif diff --git a/src/fdt.c b/src/fdt.c index b31feee971..157a719d34 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -807,10 +807,11 @@ int fdt_fixup_val64(void* fdt, int off, const char* node, const char* name, /* FIT Specific */ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, - const char** pramdisk) + const char** pramdisk, const char** pfpga) { const void* val; const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; + const char *fpga = NULL; int off, len = 0; /* Find the default configuration (optional) */ @@ -827,6 +828,7 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ kernel = fdt_getprop(fdt, off, "kernel", &len); flat_dt = fdt_getprop(fdt, off, "fdt", &len); ramdisk = fdt_getprop(fdt, off, "ramdisk", &len); + fpga = fdt_getprop(fdt, off, "fpga", &len); } } if (kernel == NULL) { @@ -859,6 +861,16 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ } } } + if (fpga == NULL) { + /* find node with "type" == fpga */ + off = fdt_find_prop_offset(fdt, -1, "type", "fpga"); + if (off > 0) { + val = fdt_get_name(fdt, off, &len); + if (val != NULL && len > 0) { + fpga = (const char*)val; + } + } + } if (pkernel) *pkernel = kernel; @@ -866,10 +878,31 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ *pflat_dt = flat_dt; if (pramdisk) *pramdisk = ramdisk; + if (pfpga) + *pfpga = fpga; return conf; } +const char* fit_get_compatible(void* fdt, const char* image) +{ + const char* val; + int off, len = 0; + + if (image == NULL) { + return NULL; + } + off = fdt_find_node_offset(fdt, -1, image); + if (off <= 0) { + return NULL; + } + val = (const char*)fdt_getprop(fdt, off, "compatible", &len); + if (val != NULL && len > 0) { + return val; + } + return NULL; +} + int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size) { int off, ret; @@ -1124,4 +1157,74 @@ void* fit_load_image_to(void* fdt, const char* image, void* dst, return fit_load_image_inner(fdt, image, lenp, dst_max, dst); } +#ifdef WOLFBOOT_FPGA_BITSTREAM +/* Minimal substring search (strstr is not provided by wolfBoot's + * freestanding string.c). Returns 1 if needle occurs in haystack. */ +static int fit_str_contains(const char* haystack, const char* needle) +{ + size_t hlen, nlen, i; + + if (haystack == NULL || needle == NULL) { + return 0; + } + hlen = strlen(haystack); + nlen = strlen(needle); + if (nlen == 0 || nlen > hlen) { + return 0; + } + for (i = 0; i + nlen <= hlen; i++) { + if (strncmp(haystack + i, needle, nlen) == 0) { + return 1; + } + } + return 0; +} + +int fit_load_fpga(void* fdt, const char* fpga_node) +{ + void* data; + const char* comp; + uint32_t flags = HAL_FPGA_FULL; + int len = 0; + int ret; + + if (fpga_node == NULL) { + /* No fpga subimage present - nothing to do. */ + return 0; + } + + /* Stage the bitstream to its `load` address (the FIT loader copies + * to `load` when distinct from `data`, and rejects unsupported + * compression). The returned pointer is where the bytes now live. */ + data = fit_load_image(fdt, fpga_node, &len); + if (data == NULL || len <= 0) { + wolfBoot_printf("FIT: failed to load fpga '%s'\n", fpga_node); + return -1; + } + + /* Select full vs partial reconfiguration from the U-Boot-style + * "compatible" string (e.g. "...fpga-partial"). Default is full. */ + comp = fit_get_compatible(fdt, fpga_node); + if (fit_str_contains(comp, "partial")) { + flags = HAL_FPGA_PARTIAL; + } + + wolfBoot_printf("FIT: programming FPGA '%s' (%d bytes, %s)\n", + fpga_node, len, (flags == HAL_FPGA_PARTIAL) ? "partial" : "full"); + + ret = hal_fpga_load(flags, (uintptr_t)data, (size_t)len); + if (ret != 0) { + wolfBoot_printf("FIT: hal_fpga_load failed: %d\n", ret); +#ifdef WOLFBOOT_FPGA_NONFATAL + wolfBoot_printf("FIT: continuing without FPGA (non-fatal)\n"); + return 0; +#else + return -1; +#endif + } + wolfBoot_printf("FIT: FPGA programmed\n"); + return 0; +} +#endif /* WOLFBOOT_FPGA_BITSTREAM */ + #endif /* (MMU || WOLFBOOT_FDT) && !BUILD_LOADER_STAGE1 */ diff --git a/src/libwolfboot.c b/src/libwolfboot.c index fed3205273..1273b38c25 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -248,6 +248,17 @@ void WEAKFUNCTION hal_cache_invalidate(void) { /* if cache flushing is required implement in hal */ } + +#ifdef WOLFBOOT_FPGA_BITSTREAM +int WEAKFUNCTION hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size) +{ + /* No FPGA loader for this target; implement in the target hal. */ + (void)flags; + (void)addr; + (void)size; + return -1; +} +#endif #ifdef __CCRX__ #pragma section FRAM #endif diff --git a/src/update_disk.c b/src/update_disk.c index 62631ae721..ca1d2f53ff 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -542,11 +542,24 @@ void RAMFUNCTION wolfBoot_start(void) if (wolfBoot_get_dts_size(load_address) > 0) { void* fit = (void*)load_address; const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; + const char *fpga = NULL; wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", fdt_version(fit), fdt_totalsize(fit)); - (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk); + (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga); +#ifdef WOLFBOOT_FPGA_BITSTREAM + /* Program the PL before booting so PL-dependent clocks and + * peripherals are up first. */ + if (fpga != NULL) { + if (fit_load_fpga(fit, fpga) != 0) { + wolfBoot_printf("FIT: FPGA load failed\r\n"); + wolfBoot_panic(); + } + } +#else + (void)fpga; +#endif if (kernel != NULL) { void *new_load = fit_load_image(fit, kernel, NULL); if (new_load == NULL) { diff --git a/src/update_ram.c b/src/update_ram.c index 79292b2f65..1708674df5 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -448,11 +448,24 @@ void RAMFUNCTION wolfBoot_start(void) if (wolfBoot_get_dts_size(load_address) > 0) { void* fit = (void*)load_address; const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; + const char *fpga = NULL; wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", fdt_version(fit), fdt_totalsize(fit)); - (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk); + (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga); +#ifdef WOLFBOOT_FPGA_BITSTREAM + /* Program the PL before booting so PL-dependent clocks and + * peripherals are up first. */ + if (fpga != NULL) { + if (fit_load_fpga(fit, fpga) != 0) { + wolfBoot_printf("FIT: FPGA load failed\n"); + wolfBoot_panic(); + } + } +#else + (void)fpga; +#endif if (kernel != NULL) { void *new_load = fit_load_image(fit, kernel, NULL); if (new_load == NULL) { diff --git a/tools/fdt-parser/fdt-parser.c b/tools/fdt-parser/fdt-parser.c index d9ac4da403..6bfa64b2c3 100644 --- a/tools/fdt-parser/fdt-parser.c +++ b/tools/fdt-parser/fdt-parser.c @@ -373,8 +373,9 @@ void dts_parse_fit_image(void* fit, const char* image, const char* desc) int dts_parse_fit(void* image) { const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; + const char *fpga = NULL; - conf = fit_find_images(image, &kernel, &flat_dt, &ramdisk); + conf = fit_find_images(image, &kernel, &flat_dt, &ramdisk, &fpga); if (conf != NULL) { printf("FIT: Found '%s' configuration\n", conf); dts_fit_image_item(image, fdt_find_node_offset(image, -1, conf), @@ -387,6 +388,9 @@ int dts_parse_fit(void* image) if (ramdisk != NULL) { dts_parse_fit_image(image, ramdisk, "Ramdisk"); } + if (fpga != NULL) { + dts_parse_fit_image(image, fpga, "FPGA"); + } return 0; } diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index ee0b6398e4..9a0758e303 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -60,6 +60,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 TESTS+=unit-tpm-check-rot-auth TESTS+=unit-tpm-api-names TESTS+=unit-fit-gzip unit-fit-nogzip +TESTS+=unit-fit-fpga include unit-sign-encrypted-output.mkfrag @@ -338,6 +339,11 @@ unit-fit-nogzip: ../../include/target.h unit-fit-gzip.c -DWOLFBOOT_NO_PRINTF \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections +# FIT fpga-subimage discovery (fit_find_images / fit_get_compatible). +unit-fit-fpga: ../../include/target.h unit-fit-fpga.c + gcc -o $@ unit-fit-fpga.c $(CFLAGS) -DWOLFBOOT_FDT \ + -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections + unit-update-flash: ../../include/target.h unit-update-flash.c gcc -o $@ unit-update-flash.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) diff --git a/tools/unit-tests/unit-fit-fpga.c b/tools/unit-tests/unit-fit-fpga.c new file mode 100644 index 0000000000..9902d8a467 --- /dev/null +++ b/tools/unit-tests/unit-fit-fpga.c @@ -0,0 +1,274 @@ +/* unit-fit-fpga.c + * + * Unit tests for the FIT-image FPGA-subimage discovery added to + * fit_find_images() and fit_get_compatible() in src/fdt.c. The tests + * build minimal FIT blobs in memory (a tiny DTB writer below) and check + * that the fpga node is found via the configuration "fpga" property, via + * the type=="fpga" fallback, and that it is NULL when absent. + * + * 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 + */ + +#include +#include +#include +#include +#include + +#include "../../include/fdt.h" + +/* fdt.c calls wolfBoot_printf; supply a silent stub. */ +void wolfBoot_printf(const char *fmt, ...) +{ + (void)fmt; +} + +/* Pull in the production code under test (gated on WOLFBOOT_FDT). */ +#include "../../src/fdt.c" + +/* ------------------------------------------------------------------------- */ +/* Minimal big-endian DTB writer (FDT_* tokens come from fdt.h) */ +/* ------------------------------------------------------------------------- */ +static uint8_t g_struct[4096]; +static uint32_t g_struct_len; +static char g_strings[1024]; +static uint32_t g_strings_len; +static uint8_t g_blob[8192]; + +static void be32_put(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)(v); +} + +static void struct_u32(uint32_t v) +{ + be32_put(&g_struct[g_struct_len], v); + g_struct_len += 4; +} + +/* Append name+nul, 4-byte aligned (used by node names). */ +static void struct_str(const char* s) +{ + uint32_t n = (uint32_t)strlen(s) + 1U; + memcpy(&g_struct[g_struct_len], s, n); + g_struct_len += n; + while (g_struct_len & 3U) { + g_struct[g_struct_len++] = 0; + } +} + +/* Intern a property name into the strings block, return its offset. */ +static uint32_t strings_off(const char* name) +{ + uint32_t n = (uint32_t)strlen(name) + 1U; + uint32_t off = g_strings_len; + memcpy(&g_strings[g_strings_len], name, n); + g_strings_len += n; + return off; +} + +static void node_begin(const char* name) +{ + struct_u32(FDT_BEGIN_NODE); + struct_str(name); +} + +static void node_end(void) +{ + struct_u32(FDT_END_NODE); +} + +static void prop_str(const char* name, const char* val) +{ + uint32_t vlen = (uint32_t)strlen(val) + 1U; + struct_u32(FDT_PROP); + struct_u32(vlen); + struct_u32(strings_off(name)); + memcpy(&g_struct[g_struct_len], val, vlen); + g_struct_len += vlen; + while (g_struct_len & 3U) { + g_struct[g_struct_len++] = 0; + } +} + +/* Assemble the header + reserve map + struct + strings into g_blob. */ +static void* fit_finish(void) +{ + uint32_t off_rsv = 40; /* header is 40 bytes (v17) */ + uint32_t off_struct = off_rsv + 16; /* one terminating rsv entry */ + uint32_t off_strings = off_struct + g_struct_len; + uint32_t total = off_strings + g_strings_len; + uint8_t* h = g_blob; + + memset(g_blob, 0, sizeof(g_blob)); + be32_put(h + 0, 0xD00DFEEDU); /* magic */ + be32_put(h + 4, total); /* totalsize */ + be32_put(h + 8, off_struct); /* off_dt_struct */ + be32_put(h + 12, off_strings); /* off_dt_strings */ + be32_put(h + 16, off_rsv); /* off_mem_rsvmap */ + be32_put(h + 20, 17); /* version */ + be32_put(h + 24, 16); /* last_comp_version */ + be32_put(h + 28, 0); /* boot_cpuid_phys */ + be32_put(h + 32, g_strings_len); /* size_dt_strings */ + be32_put(h + 36, g_struct_len); /* size_dt_struct */ + /* reserve map terminator already zeroed */ + memcpy(g_blob + off_struct, g_struct, g_struct_len); + memcpy(g_blob + off_strings, g_strings, g_strings_len); + return g_blob; +} + +static void fit_reset(void) +{ + g_struct_len = 0; + g_strings_len = 0; + memset(g_struct, 0, sizeof(g_struct)); + memset(g_strings, 0, sizeof(g_strings)); +} + +/* ------------------------------------------------------------------------- */ +/* Tests */ +/* ------------------------------------------------------------------------- */ + +/* Configuration node carries an explicit "fpga" reference. */ +START_TEST(test_fit_fpga_via_config) +{ + const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL, *fpga = NULL; + const char* comp; + void* fit; + + fit_reset(); + struct_u32(FDT_BEGIN_NODE); struct_str(""); /* root */ + node_begin("images"); + node_begin("fpga-1"); + prop_str("type", "fpga"); + prop_str("compatible", "u-boot,zynqmp-fpga-ddrauth"); + node_end(); + node_end(); + node_begin("configurations"); + prop_str("default", "conf1"); + node_begin("conf1"); + prop_str("kernel", "kernel-1"); + prop_str("fpga", "fpga-1"); + node_end(); + node_end(); + node_end(); + struct_u32(FDT_END); + fit = fit_finish(); + + fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga); + ck_assert_ptr_nonnull(fpga); + ck_assert_str_eq(fpga, "fpga-1"); + + comp = fit_get_compatible(fit, fpga); + ck_assert_ptr_nonnull(comp); + ck_assert_str_eq(comp, "u-boot,zynqmp-fpga-ddrauth"); +} +END_TEST + +/* No "fpga" config property: fall back to a node with type=="fpga". */ +START_TEST(test_fit_fpga_via_type_fallback) +{ + const char *fpga = NULL; + void* fit; + + fit_reset(); + struct_u32(FDT_BEGIN_NODE); struct_str(""); + node_begin("images"); + node_begin("the-bitstream"); + prop_str("type", "fpga"); + node_end(); + node_end(); + node_end(); + struct_u32(FDT_END); + fit = fit_finish(); + + fit_find_images(fit, NULL, NULL, NULL, &fpga); + ck_assert_ptr_nonnull(fpga); + ck_assert_str_eq(fpga, "the-bitstream"); +} +END_TEST + +/* No fpga subimage at all: pfpga must be left NULL. */ +START_TEST(test_fit_fpga_absent) +{ + const char *fpga = (const char*)0x1; /* poison */ + void* fit; + + fit_reset(); + struct_u32(FDT_BEGIN_NODE); struct_str(""); + node_begin("images"); + node_begin("kernel-1"); + prop_str("type", "kernel"); + node_end(); + node_end(); + node_end(); + struct_u32(FDT_END); + fit = fit_finish(); + + fit_find_images(fit, NULL, NULL, NULL, &fpga); + ck_assert_ptr_null(fpga); +} +END_TEST + +/* fit_get_compatible returns NULL when the property is absent. */ +START_TEST(test_fit_compatible_absent) +{ + const char* comp; + void* fit; + + fit_reset(); + struct_u32(FDT_BEGIN_NODE); struct_str(""); + node_begin("images"); + node_begin("fpga-1"); + prop_str("type", "fpga"); + node_end(); + node_end(); + node_end(); + struct_u32(FDT_END); + fit = fit_finish(); + + comp = fit_get_compatible(fit, "fpga-1"); + ck_assert_ptr_null(comp); +} +END_TEST + +static Suite* fit_fpga_suite(void) +{ + Suite* s = suite_create("fit-fpga"); + TCase* tc = tcase_create("discovery"); + tcase_add_test(tc, test_fit_fpga_via_config); + tcase_add_test(tc, test_fit_fpga_via_type_fallback); + tcase_add_test(tc, test_fit_fpga_absent); + tcase_add_test(tc, test_fit_compatible_absent); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int failed; + SRunner* sr = srunner_create(fit_fpga_suite()); + srunner_run_all(sr, CK_NORMAL); + failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (failed == 0) ? 0 : 1; +}