From ca2044554c412cfed7932e7b590659418d380b59 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 2 Oct 2026 09:19:12 -0700 Subject: [PATCH] wolfboot: build the PolarFire SoC M-mode target with the Yocto RISC-V toolchain and sign its FIT with ECC384 --- files/wic/mpfs-wolfboot.wks | 15 ++++ recipes-wolfssl/wolfboot/README.md | 53 ++++++++++++- .../wolfboot/wolfboot-signed-image.bb | 24 ++++-- recipes-wolfssl/wolfboot/wolfboot.inc | 5 +- recipes-wolfssl/wolfboot/wolfboot_git.bb | 75 +++++++++++++++---- 5 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 files/wic/mpfs-wolfboot.wks diff --git a/files/wic/mpfs-wolfboot.wks b/files/wic/mpfs-wolfboot.wks new file mode 100644 index 0000000..f46324d --- /dev/null +++ b/files/wic/mpfs-wolfboot.wks @@ -0,0 +1,15 @@ +# short-description: PolarFire SoC SD card for standalone M-mode wolfBoot +# long-description: Partitions 1 and 2 are wolfBoot's raw A/B slots +# (polarfire_mpfs250_m.config: BOOT_PART_A=0, BOOT_PART_B=1); partition 1 is +# filled with the signed FIT (image_signed.bin, the version-independent link +# wolfboot-signed-image deploys) and partition 2 is left empty for updates. +# The rootfs stays partition 3 as in the BSP layout. Partition 4 keeps the HSS +# payload, found by its GUID, so the same card still boots under HSS + U-Boot +# if the eNVM is reprogrammed. + +part --source rawcopy --sourceparams="file=image_signed.bin" --ondisk mmcblk0 --part-name boot --fixed-size 65536K --align 4096 +part --ondisk mmcblk0 --part-name update --fixed-size 65536K --align 4096 +part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096 +part --source rawcopy --sourceparams="file=payload.bin" --ondisk mmcblk0 --part-name hssboot --fixed-size 8192K --align 1 --part-type 21686148-6449-6E6F-744E-656564454649 + +bootloader --ptable gpt diff --git a/recipes-wolfssl/wolfboot/README.md b/recipes-wolfssl/wolfboot/README.md index c750df0..9c4e282 100644 --- a/recipes-wolfssl/wolfboot/README.md +++ b/recipes-wolfssl/wolfboot/README.md @@ -7,9 +7,9 @@ portable secure bootloader, as part of a Yocto / OpenEmbedded image. | Recipe | Purpose | |---|---| -| `wolfboot-keytools-native_git.bb` | Host-side `wolfboot-keygen` and `wolfboot-sign` utilities (RSA4096 key generation, RSA4096+SHA3-384 image signing). | +| `wolfboot-keytools-native_git.bb` | Host-side `wolfboot-keygen` and `wolfboot-sign` utilities (key generation and image signing: RSA4096+SHA3-384 by default, ECC384+SHA-384 for the PolarFire config). | | `wolfboot_git.bb` | Cross-compiles `wolfboot.elf` (bare-metal, AArch64 / AArch32 / RISC-V). Picks a config from `${S}/config/examples/` per the `WOLFBOOT_CONFIG` variable. Consumes a user-supplied signing key via `WOLFBOOT_SIGNING_KEY` (see "Signing-key provisioning" below). | -| `wolfboot-signed-image.bb` | Signs the kernel FIT image (default `fitImage`) with RSA4096+SHA3-384 and emits `image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin` into `DEPLOY_DIR_IMAGE`. | +| `wolfboot-signed-image.bb` | Signs the kernel FIT image (default `fitImage`) with `WOLFBOOT_SIGN_ALGO` + `WOLFBOOT_HASH_ALGO` (RSA4096+SHA3-384 by default) and emits `image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin` into `DEPLOY_DIR_IMAGE`. | A companion `recipes-bsp/bootbin/xilinx-bootbin_%.bbappend` overrides the AMD/Xilinx `xilinx-bootbin` recipe to swap U-Boot for `wolfboot.elf` in @@ -19,7 +19,9 @@ set in configuration (`local.conf` or a machine `.conf`). ## Signing-key provisioning (required) `wolfboot_git.bb` and `wolfboot-signed-image.bb` both require you to -supply a pre-generated RSA4096 private key via `WOLFBOOT_SIGNING_KEY`. +supply a pre-generated private key via `WOLFBOOT_SIGNING_KEY` (RSA4096 by +default; ECC384 for the PolarFire config, see the PolarFire SoC quick start +below). The recipes will **not** auto-generate one — that would leak the private half through sstate and DEPLOY_DIR_IMAGE. @@ -83,6 +85,51 @@ Artifacts deployed to `tmp/deploy/images//`: Note: the private signing key is **not** deployed — it stays on the workstation / secrets store you pointed `WOLFBOOT_SIGNING_KEY` at. +## Quick start (Microchip PolarFire SoC, standalone M-mode, SD-card boot) + +On PolarFire SoC wolfBoot can replace the Hart Software Services (HSS) entirely: it boots from eNVM on the E51, initialises the PLL and LPDDR4, loads a signed Linux FIT from the SD card and hands off to S-mode (`polarfire_mpfs250_m.config`). The eNVM image is programmed with Microchip's `mpfsBootmodeProgrammer`, so the recipe deploys `wolfboot.elf` and leaves the eNVM programming to the user. + +Three things differ from the ZynqMP flow: + +- The M-mode build needs the Libero design's `fpga_design_config` header directory (DDR, clock and SEG settings). Pass it through `WOLFBOOT_EXTRA_MAKE_FLAGS`; the build has no way to generate it. +- The PolarFire example config signs with ECC384 / SHA-384, so the signing key is an ECC384 pair from `wolfboot-keygen --ecc384 -g .der`. The private file keygen writes is `Qx || Qy || d`; the public half for `WOLFBOOT_PUBLIC_KEY` is its first 96 bytes (`head -c 96 .der > .der`), which `keygen -i` reads as a raw public key. +- Partitions 1 and 2 of the SD card are the raw A/B slots (`BOOT_PART_A = 0`, `BOOT_PART_B = 1`); `files/wic/mpfs-wolfboot.wks` fills slot A with the signed FIT, leaves slot B empty, keeps the rootfs at partition 3 and moves the BSP's GUID-tagged HSS payload to partition 4, so the same card still boots under HSS + U-Boot if the eNVM is reprogrammed. +- The PolarFire config is in wolfBoot v2.10.0 and later, and the link flags the recipe adds need wolfBoot's `LDFLAGS_EXTRA` (wolfSSL/wolfBoot#921; on older revisions the recipe falls back to `CC` and `LD` overrides). Point `SRCREV_wolfboot` at a revision with both, as shown below. + +```bitbake +WOLFBOOT_CONFIG = "polarfire_mpfs250_m.config" +WOLFBOOT_SIGNING_KEY = "/secure/path/wolfboot_signing_private_key.der" +WOLFBOOT_PUBLIC_KEY = "/secure/path/wolfboot_signing_public_key.der" +WOLFBOOT_EXTRA_MAKE_FLAGS = "LIBERO_FPGA_CONFIG_DIR=/path/to/boards/mpfs-video-kit/fpga_design_config" +# The M-mode image is soft-float lp64; the Linux toolchain's libgcc is lp64d +# and cannot be linked, and wolfBoot needs nothing from it on this target. +WOLFBOOT_NOSTDLIB = "1" +# The Microchip BSP builds its FIT with a gzip-compressed kernel. +WOLFBOOT_EXTRA_CONFIG_LINES = "GZIP=1" + +# Match the config's SIGN / HASH. +WOLFBOOT_SIGN_ALGO = "ecc384" +WOLFBOOT_HASH_ALGO = "sha384" +KERNEL_PN:pn-wolfboot-signed-image = "linux-mchp" + +EXTRA_IMAGEDEPENDS:append = " wolfboot wolfboot-signed-image" +WKS_FILE = "mpfs-wolfboot.wks" +WKS_FILE_DEPENDS:append = " wolfboot-signed-image" +# Releases before wrynose search /wic, not /files/wic, for .wks files. +WKS_SEARCH_PATH:prepend = "${WOLFSSL_LAYERDIR}/files/wic:" +``` + +Build with `MACHINE=mpfs-video-kit bitbake mchp-base-image`. The `.wic` is written to the SD card as-is; `wolfboot.elf` goes to eNVM with `mpfsBootmodeProgrammer.jar --bootmode 1`. + +To build a wolfBoot branch that is not on `wolfssl/wolfBoot` master, point the fetcher at it instead of editing the recipe: + +```bitbake +WOLFBOOT_GIT_URI = "git://github.com//wolfBoot.git;protocol=https" +WOLFBOOT_GIT_BRANCH = "" +SRCREV_wolfboot = "" +SRCREV_wolfssl = "" +``` + ## Using an existing wolfSSL source tree By default `wolfboot.inc` fetches a pinned wolfSSL (`SRCREV_wolfssl`) into diff --git a/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb b/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb index 67a82af..01100f0 100644 --- a/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb +++ b/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb @@ -1,8 +1,8 @@ SUMMARY = "wolfBoot signed kernel FIT image" DESCRIPTION = "Signs the kernel FIT image (fitImage / image.ub) with \ -wolfBoot RSA4096+SHA3-384 for verified secure boot. The signed output is \ -placed in DEPLOY_DIR_IMAGE as image_v_signed.bin, ready for \ -flashing to the OFP_A / OFP_B partitions of an A/B-enabled SD card or QSPI." +wolfBoot (RSA4096+SHA3-384 by default) for verified secure boot. The signed \ +output is placed in DEPLOY_DIR_IMAGE as image_v_signed.bin, ready \ +for flashing to the OFP_A / OFP_B partitions of an A/B-enabled SD card or QSPI." HOMEPAGE = "https://github.com/wolfssl/wolfBoot" SECTION = "bootloaders" @@ -44,6 +44,11 @@ WOLFBOOT_IMAGE_VERSION ?= "1" # linux-xlnx on ZynqMP / Versal. WOLFBOOT_FIT_IMAGE ?= "fitImage" +# wolfboot-sign algorithm options (no dashes); must match the config's +# SIGN / HASH and the key type. +WOLFBOOT_SIGN_ALGO ?= "rsa4096" +WOLFBOOT_HASH_ALGO ?= "sha3" + # Validate WOLFBOOT_SIGNING_KEY only when this recipe actually builds # (see wolfboot_git.bb for the rationale re: parse-time vs task-time). python check_wolfboot_signing_key() { @@ -79,12 +84,12 @@ do_compile() { # leaves publishing to do_deploy below. cp "$fit_image" ${B}/${WOLFBOOT_FIT_IMAGE} - # Sign the FIT image with RSA4096 + SHA3-384 using the user-supplied - # signing key. wolfboot-sign emits the output NEXT TO the input file, - # naming it _v_signed.bin. Run from ${B} with a - # relative path so the output lands inside ${B} predictably. + # Sign the FIT image with the configured algorithms using the + # user-supplied signing key. wolfboot-sign emits the output NEXT TO the + # input file, naming it _v_signed.bin. Run from ${B} + # with a relative path so the output lands inside ${B} predictably. cd ${B} - wolfboot-sign --rsa4096 --sha3 \ + wolfboot-sign --${WOLFBOOT_SIGN_ALGO} --${WOLFBOOT_HASH_ALGO} \ ${WOLFBOOT_FIT_IMAGE} \ ${WOLFBOOT_SIGNING_KEY} \ ${WOLFBOOT_IMAGE_VERSION} @@ -96,6 +101,9 @@ do_deploy() { install -d ${DEPLOYDIR} install -m 0644 ${B}/${WOLFBOOT_FIT_IMAGE}_v${WOLFBOOT_IMAGE_VERSION}_signed.bin \ ${DEPLOYDIR}/image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin + # Version-independent name for wic layouts (WOLFBOOT_IMAGE_VERSION is not + # visible in the image recipe's datastore). + ln -sf image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin ${DEPLOYDIR}/image_signed.bin } addtask deploy before do_build after do_compile diff --git a/recipes-wolfssl/wolfboot/wolfboot.inc b/recipes-wolfssl/wolfboot/wolfboot.inc index 973e03b..e815cb4 100644 --- a/recipes-wolfssl/wolfboot/wolfboot.inc +++ b/recipes-wolfssl/wolfboot/wolfboot.inc @@ -37,8 +37,11 @@ WOLFBOOT_WOLFSSL_STAGED_SRC = "${WORKDIR}/wolfssl-external" # upstream evolves. Downstream users can override via local.conf: # SRCREV_wolfboot:pn-wolfboot = "" # SRCREV_wolfboot:pn-wolfboot-keytools-native = "" +# Repository and branch; override in local.conf to build a fork or branch. +WOLFBOOT_GIT_URI ?= "git://github.com/wolfssl/wolfBoot.git;protocol=https" +WOLFBOOT_GIT_BRANCH ?= "master" SRC_URI = " \ - git://github.com/wolfssl/wolfBoot.git;protocol=https;branch=master;name=wolfboot;destsuffix=git \ + ${WOLFBOOT_GIT_URI};branch=${WOLFBOOT_GIT_BRANCH};name=wolfboot;destsuffix=git \ ${@'' if d.getVar('WOLFBOOT_WOLFSSL_SRC') else 'git://github.com/wolfssl/wolfssl.git;protocol=https;branch=master;name=wolfssl;destsuffix=git/lib/wolfssl'} \ " SRCREV_wolfboot ?= "9a667f2a7527da2b8e490ae7923665321af2d3ac" diff --git a/recipes-wolfssl/wolfboot/wolfboot_git.bb b/recipes-wolfssl/wolfboot/wolfboot_git.bb index 1d6ac1f..1e252ae 100644 --- a/recipes-wolfssl/wolfboot/wolfboot_git.bb +++ b/recipes-wolfssl/wolfboot/wolfboot_git.bb @@ -56,6 +56,10 @@ WOLFBOOT_EXTRA_MAKE_FLAGS ?= "" # coexist with the template's own CFLAGS_EXTRA lines. WOLFBOOT_EXTRA_CONFIG_LINES ?= "" +# Link with -nostdlib: for configs built for an ABI the sysroot's libgcc does +# not match (PolarFire SoC M-mode is soft-float lp64 on an lp64d toolchain). +WOLFBOOT_NOSTDLIB ?= "0" + COMPATIBLE_MACHINE = ".*" PACKAGE_ARCH = "${MACHINE_ARCH}" @@ -80,8 +84,8 @@ python check_wolfboot_signing_key() { pub = d.getVar('WOLFBOOT_PUBLIC_KEY') or '' if not pub: bb.fatal("WOLFBOOT_PUBLIC_KEY is not set. Extract the public half " - "from the private key with: openssl rsa -in .der " - "-inform DER -pubout -outform DER -out .der") + "from the private key (RSA: openssl rsa -pubout; ECC: the " + "first Qx||Qy bytes, see recipes-wolfssl/wolfboot/README.md).") if not os.path.isfile(pub): bb.fatal("WOLFBOOT_PUBLIC_KEY='%s' does not exist or is not a " "regular file." % pub) @@ -89,6 +93,14 @@ python check_wolfboot_signing_key() { do_compile[prefuncs] += "check_wolfboot_signing_key" do_compile() { + # A re-run of do_compile keeps the tree; drop objects built with the + # previous .config (make tracks sources, not flags). + find ${S} \( -name '*.o' -o -name '*.d' \) -not -path '*/.git/*' -delete + if [ -n "${WOLFBOOT_WOLFSSL_SRC}" ] && [ -d "${WOLFBOOT_WOLFSSL_STAGED_SRC}" ]; then + find ${WOLFBOOT_WOLFSSL_STAGED_SRC} \( -name '*.o' -o -name '*.d' \) -delete + fi + rm -f ${S}/wolfboot.elf ${S}/wolfboot.map ${S}/src/keystore.c + # Seed wolfBoot's Makefile with the requested example .config. if [ ! -f ${S}/config/examples/${WOLFBOOT_CONFIG} ]; then bbfatal "WOLFBOOT_CONFIG='${WOLFBOOT_CONFIG}' not found under ${S}/config/examples/" @@ -110,8 +122,8 @@ do_compile() { # wolfBoot is a bare-metal bootloader (-nostdlib -ffreestanding), so we # use raw make (not oe_runmake) to prevent Yocto's CC/CFLAGS/LDFLAGS # from overriding wolfBoot's own toolchain settings. The Yocto cross - # compiler still needs --sysroot to find headers and libgcc; we embed - # it in CC so it applies to both compilation and linking. + # compiler still needs --sysroot to find headers and libgcc; it is + # appended to the .config below so it applies to compilation and linking. # # USER_PRIVATE_KEY + USER_PUBLIC_KEY tell wolfBoot's Makefile to use a # pre-generated key pair instead of regenerating one inside the build @@ -125,20 +137,54 @@ do_compile() { # Both must be supplied by the user (generate via wolfboot-keygen). if [ -z "${WOLFBOOT_PUBLIC_KEY}" ] || [ ! -f "${WOLFBOOT_PUBLIC_KEY}" ]; then bbfatal "WOLFBOOT_PUBLIC_KEY is not set or the file does not exist. " \ - "Generate both keys with 'wolfboot-keygen --rsa4096 -g .der' " \ - "then extract the public half with 'openssl rsa -in .der " \ - "-inform DER -pubout -outform DER -out .der'. Point both " \ - "WOLFBOOT_SIGNING_KEY and WOLFBOOT_PUBLIC_KEY at the respective files." + "Generate the key pair with wolfboot-keygen and extract the public " \ + "half (RSA: openssl rsa -pubout; ECC: the first Qx||Qy bytes), see " \ + "recipes-wolfssl/wolfboot/README.md. Point WOLFBOOT_SIGNING_KEY and " \ + "WOLFBOOT_PUBLIC_KEY at the respective files." fi PUBKEY_FOR_MAKE=${WOLFBOOT_PUBLIC_KEY} unset CFLAGS CPPFLAGS CXXFLAGS LDFLAGS SYSROOT_FLAG="--sysroot=${RECIPE_SYSROOT}" - # KEYGEN_TOOL override: wolfBoot's Makefile otherwise tries to build - # tools/keytools/keygen using the target cross-compiler and then run - # the resulting AArch64 binary on the x86_64 build host. Point it at - # the native keygen from wolfboot-keytools-native instead. + + # glibc's includes a per-ABI stubs-.h; when wolfBoot's + # -mabi differs from the sysroot's that file is missing. It only lists + # libc stubs, meaningless to a -nostdlib image, so supply empty ones. + LIBC_STUBS="${WORKDIR}/libc-stubs" + rm -rf "$LIBC_STUBS"; mkdir -p "$LIBC_STUBS/gnu" + for abi in lp64 lp64d ilp32 ilp32d 64 32 soft hard; do + if [ ! -e "${RECIPE_SYSROOT}/usr/include/gnu/stubs-$abi.h" ]; then + : > "$LIBC_STUBS/gnu/stubs-$abi.h" + fi + done + SYSROOT_FLAG="$SYSROOT_FLAG -isystem $LIBC_STUBS" + + # Toolchain flags go through .config (CFLAGS_EXTRA / LDFLAGS_EXTRA are + # additive there); CC/LD on the make command line would leak the cross + # compiler into the sub-makes that build the host tools. No PIE and no + # build-id note: the image is linked at fixed addresses from its boot vector. + echo "CFLAGS_EXTRA+=$SYSROOT_FLAG -fno-pie" >> ${S}/.config + LD_EXTRA="$SYSROOT_FLAG -no-pie -Wl,--build-id=none" + if [ "${WOLFBOOT_NOSTDLIB}" = "1" ]; then + LD_EXTRA="$LD_EXTRA -nostdlib" + fi + # A wolfBoot without the LDFLAGS_EXTRA consumer (options.mk) gets the link + # flags the old way, as CC and LD overrides on the command line. Both, + # not just LD: such a wolfBoot also builds its in-tree keytools, whose + # sub-make inherits the command-line variables, and the host objects it + # compiles with its own CC do not link with the cross LD. + CC_ARG="" + LD_ARG="" + if grep -Eq 'LDFLAGS[[:space:]]*\+=[[:space:]]*\$\(LDFLAGS_EXTRA\)' ${S}/options.mk; then + echo "LDFLAGS_EXTRA+=$LD_EXTRA" >> ${S}/.config + else + CC_ARG="CC=${TARGET_PREFIX}gcc $SYSROOT_FLAG" + LD_ARG="LD=${TARGET_PREFIX}gcc $LD_EXTRA" + fi + + # Native keytools; with both given, wolfBoot skips its in-tree build. NATIVE_KEYGEN="$(command -v wolfboot-keygen)" + NATIVE_SIGN="$(command -v wolfboot-sign)" # Build wolfCrypt from a caller-supplied wolfSSL tree when asked. # WOLFBOOT_LIB_WOLFSSL is wolfBoot's way to set an external wolfSSL source @@ -156,11 +202,12 @@ do_compile() { make wolfboot.elf \ CROSS_COMPILE=${TARGET_PREFIX} \ - CC="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \ - LD="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \ + ${CC_ARG:+"$CC_ARG"} \ + ${LD_ARG:+"$LD_ARG"} \ USER_PRIVATE_KEY="${WOLFBOOT_SIGNING_KEY}" \ USER_PUBLIC_KEY="$PUBKEY_FOR_MAKE" \ KEYGEN_TOOL="$NATIVE_KEYGEN" \ + SIGN_TOOL="$NATIVE_SIGN" \ $WOLFSSL_LIB_ARG \ ${WOLFBOOT_EXTRA_MAKE_FLAGS} \ V=1