From d69a92771a9f99949c76ceae36bfc7443ba52ada Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 17:08:13 +0200 Subject: [PATCH 1/2] build: boot SO3 standalone on virt64 again, fix deploy races and usr clean MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default virt64 configuration did not boot. Since the boot chain became one ordered value, the default is IB_BOOT_CHAIN="uboot", and on virt64 the uboot recipe then builds upstream qemu_arm64_defconfig — the bare-Linux U-Boot of infrabase. That U-Boot loads the SO3 ITB and stops on "Could not find configuration node / can't get kernel image". Before, the default chain was "" and took virt64_defconfig. This tree now always builds ${IB_PLATFORM}_defconfig on virt64. virt64_defconfig is linked at a RAM address that QEMU -kernel honours, boots both SO3 and Linux, and unlike the PIE U-Boot it can be read off the card by the boot ROM once the QEMU bootrom-* properties are in this tree's virt.c patch. Three independent defects, all generic, found while aligning the pos course trees onto this build system. do_deploy_boot_chain ran before the storage image existed. With IB_FIRST_STAGE_FROM_CARD the virt64 chain dd's U-Boot into the raw area of sdcard.img, but nothing ordered the task after the image creation: on a fresh tree it failed with "no storage image ... run init_storage.sh first", and with bsp-linux it raced filesystem:do_fs_check. It now depends on filesystem:do_fs_check, which is a no-op when the image exists. Partitioning waited a fixed 2 s for the loop partition nodes. Inside the build container they come from the host's udev and regularly take longer: mkfs.fat then ran on a missing /dev/loopNp1, failed silently (no check), and the next deploy died on "wrong fs type" when mounting p1. The nodes are now polled (up to 20 s, fatal otherwise) and both mkfs calls check their exit status. usr.bbclass defines do_clean in Python, but the usr recipes appended shell to it; bitbake pastes the append verbatim, so `-c clean` failed on a SyntaxError. The shell bodies now live in named shell functions called from a Python append. Running them for real surfaced two latent bugs, fixed here too: the module clean ran make against a kernel tree that does not exist before the first linux build, and (where present) the lvgl clean of usr-so3 wiped WORKDIR/temp, which holds the running task's own fifo. Also: dbuild.sh now forwards IB_FORCE_ATTACH and IB_PARTITION_LAYOUT to the container. The attach guard tells the user to set IB_FORCE_ATTACH=1, which never reached bitbake under dbuild.sh. --- build/meta-bsp/classes/bsp.bbclass | 5 +++- .../recipes-bsp/bsp/files/bsp_virt64.inc | 15 ++++++----- .../classes/fs_arm_common.bbclass | 25 ++++++++++++++----- .../recipes-uboot/uboot/uboot_2022.04.bb | 20 +++++++-------- .../recipes-usr/linux/usr-linux_1.0.bb | 9 ++++++- .../recipes-usr/lvgl/usr-linux_1.0.bbappend | 9 ++++++- .../recipes-usr/soo/usr-linux_1.0.bbappend | 9 ++++++- scripts/dbuild.sh | 10 ++++++++ 8 files changed, 74 insertions(+), 28 deletions(-) diff --git a/build/meta-bsp/classes/bsp.bbclass b/build/meta-bsp/classes/bsp.bbclass index 5a30424046..513e2094f8 100644 --- a/build/meta-bsp/classes/bsp.bbclass +++ b/build/meta-bsp/classes/bsp.bbclass @@ -149,7 +149,10 @@ inherit filesystem # `deploy.sh -a` without a prior build still pulls them through sstate). do_deploy_boot_chain[nostamp] = "1" -do_deploy_boot_chain[depends] = "uboot:do_build" +# filesystem:do_fs_check because a chain may write its first stage into the +# raw area of the card (bsp_virt64.inc, IB_FIRST_STAGE_FROM_CARD): on a fresh +# tree the storage image must exist before, not be created by do_deploy after. +do_deploy_boot_chain[depends] = "uboot:do_build filesystem:do_fs_check" python () { extra = [] diff --git a/build/meta-bsp/recipes-bsp/bsp/files/bsp_virt64.inc b/build/meta-bsp/recipes-bsp/bsp/files/bsp_virt64.inc index fb6447931b..a6dda3c2de 100644 --- a/build/meta-bsp/recipes-bsp/bsp/files/bsp_virt64.inc +++ b/build/meta-bsp/recipes-bsp/bsp/files/bsp_virt64.inc @@ -104,14 +104,13 @@ def __do_platform_boot_chain(d): # the image, not of the chain: a ROM copies bytes to an address and jumps, # so the image has to be one that runs from RAM. # - # MCUboot always is. U-Boot depends on how it was configured, and this - # tree builds it two ways (see uboot_2022.04.bb): IB_BOOT_CHAIN="uboot" - # with no hypervisor takes upstream qemu_arm64_defconfig, which is - # position-independent with a text base of 0 — an image meant to execute - # in place from flash, which is why QEMU is handed it as -kernel today and - # why -bios would be the other right answer. Every other combination takes - # ${IB_PLATFORM}_defconfig, linked at a RAM address, and that one can be - # read off the card like any other first stage. + # MCUboot always is. U-Boot depends on how it was configured: this tree + # always builds ${IB_PLATFORM}_defconfig (see uboot_2022.04.bb), linked at + # a RAM address, which can be read off the card like any other first + # stage. Infrabase takes upstream qemu_arm64_defconfig on the bare "uboot" + # chain instead — position-independent with a text base of 0, an image + # meant to execute in place from flash — and the check below keeps such a + # U-Boot on -kernel, should a tree build one. # # The ELF entry point is what says which: zero means "wherever flash is". uboot_entry = 0 diff --git a/build/meta-filesystem/classes/fs_arm_common.bbclass b/build/meta-filesystem/classes/fs_arm_common.bbclass index a4ae6cf62e..aec76d76ce 100644 --- a/build/meta-filesystem/classes/fs_arm_common.bbclass +++ b/build/meta-filesystem/classes/fs_arm_common.bbclass @@ -27,6 +27,7 @@ IB_PARTITION_LAYOUT ?= "${@'ab' if d.getVar('IB_ZEPHYR_BOOT_APP') else 'rootfs'} def __platform_init_storage(d): import os import subprocess + import time IB_STORAGE_MODE = d.getVar('IB_STORAGE_MODE') IB_ROOTFS_SIZE = d.getVar('IB_ROOTFS_SIZE') @@ -128,14 +129,26 @@ def __platform_init_storage(d): print("Waiting ...") - # TODO: use ionotify(7) - # Give a chance to the real SD-card to be sync'd - time.sleep(2) - if devname[-1].isdigit(): devname += "p" - utils_sudo(["mkfs.fat", "-F32", "-a", "-v", "-n", "boot", f"/dev/{devname}1"]) + # Wait for the partition nodes rather than a fixed delay. They are created + # asynchronously by the host's udev, and inside the build container + # (dbuild.sh bind-mounts the host /dev) that regularly takes longer than + # the 2 s this used to sleep: mkfs then ran on a node that did not exist + # yet, failed, and the card was left with an unformatted p1 that only + # showed up as a failed mount in the next deploy. + parts = ("1", "2", "3") if layout == "ab" else ("1", "2") + for _ in range(100): + if all(os.path.exists(f"/dev/{devname}{n}") for n in parts): + break + time.sleep(0.2) + else: + bb.fatal(f"/dev/{devname}{{{','.join(parts)}}} did not appear after " + "partitioning /dev/" + devname.rstrip("p")) + + utils_sudo(["mkfs.fat", "-F32", "-a", "-v", "-n", "boot", f"/dev/{devname}1"], + check=True) if layout == "ab": # p2 and p3 are raw slots. A filesystem on them would be a filesystem @@ -146,7 +159,7 @@ def __platform_init_storage(d): utils_sudo(["dd", "if=/dev/zero", f"of=/dev/{devname}{part}", "bs=1M", "count=1", "conv=fsync"]) else: - utils_sudo(["mkfs.ext4", "-L", "rootfs1", f"/dev/{devname}2"]) + utils_sudo(["mkfs.ext4", "-L", "rootfs1", f"/dev/{devname}2"], check=True) if IB_STORAGE_MODE == "soft": utils_sudo(["losetup", "-D"]) diff --git a/build/meta-uboot/recipes-uboot/uboot/uboot_2022.04.bb b/build/meta-uboot/recipes-uboot/uboot/uboot_2022.04.bb index 0226c7268f..1364f09070 100644 --- a/build/meta-uboot/recipes-uboot/uboot/uboot_2022.04.bb +++ b/build/meta-uboot/recipes-uboot/uboot/uboot_2022.04.bb @@ -41,11 +41,15 @@ do_configure () { cd ${IB_TARGET} # Defconfig selection on virt64: - # - IB_BOOT_CHAIN="uboot" (bare bsp-linux, no ATF): - # upstream qemu_arm64_defconfig — CONFIG_POSITION_INDEPENDENT=y - # and CONFIG_ARCH_QEMU=y, both required for QEMU `-kernel` to - # load the U-Boot ELF at any address. Linux runs at EL1 (no - # secure / no virtualization in QEMU machine). + # - IB_BOOT_CHAIN="uboot" (no ATF): virt64_defconfig too. This tree + # departs from infrabase here, which takes upstream + # qemu_arm64_defconfig for a bare Linux: that U-Boot does not boot + # the SO3 ITB ("Could not find configuration node"), which made the + # default chain unbootable for SO3 standalone, and being + # position-independent with a text base of 0 it can never be read + # off the card by the boot ROM (bsp_virt64.inc). virt64_defconfig + # is linked at a RAM address that QEMU `-kernel` honours, and boots + # both SO3 and Linux. # - IB_BOOT_CHAIN="atf+uboot" and "full" (capsule) both use # virt64_defconfig — same FIP-aware U-Boot, the boot chain # difference lives in ATF (no SPD vs SPD=opteed) and the guest @@ -64,11 +68,7 @@ do_configure () { # absent and Kconfig dies with "syntax error". A host that happens to # have the 64-bit toolchain installed hides the bug. - if [ "${IB_PLATFORM}" = "virt64" ] && [ "${IB_BOOT_CHAIN}" = "uboot" ]; then - make CROSS_COMPILE=${IB_TOOLCHAIN}- qemu_arm64_defconfig - else - make CROSS_COMPILE=${IB_TOOLCHAIN}- ${IB_PLATFORM}_defconfig - fi + make CROSS_COMPILE=${IB_TOOLCHAIN}- ${IB_PLATFORM}_defconfig # Specific handling for bbb platform if [ "${IB_PLATFORM}" = "bbb" ]; then diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index 604464c73e..54ac85f0f7 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -165,7 +165,10 @@ do_install_apps () { usr_do_install_file_root "${IB_TARGET}/src/modules/*.ko" } -do_clean:append () { +# usr.bbclass defines do_clean in Python, and a shell :append is pasted +# verbatim into that Python function, so `-c clean` died on a SyntaxError. +# The shell stays shell, in its own function, called from a Python append. +usr_linux_clean () { rm -f ${TMPDIR}/stamps/usr-linux* rm -f ${WORKDIR}/*.patch @@ -174,3 +177,7 @@ do_clean:append () { # so a clean removes it entirely (tree, re-attach backup and manifest). rm -rf ${IB_TARGET} ${IB_TARGET}.back ${IB_TARGET}.attach.sha256 } + +python do_clean:append () { + bb.build.exec_func('usr_linux_clean', d) +} diff --git a/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend b/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend index 5393977a31..97b486f29f 100644 --- a/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend +++ b/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend @@ -47,10 +47,17 @@ do_install_apps:append () { fi } -do_clean:append () { +# usr.bbclass defines do_clean in Python, and a shell :append is pasted +# verbatim into that Python function, so `-c clean` died on a SyntaxError. +# The shell stays shell, in its own function, called from a Python append. +usr_linux_clean_lvgl () { if echo ":${OVERRIDES}:" | grep -q ":lvgl"; then rm -rf ${IB_TARGET}/lib/lv_port_linux rm -rf ${WORKDIR}/git fi } + +python do_clean:append () { + bb.build.exec_func('usr_linux_clean_lvgl', d) +} diff --git a/build/meta-usr/recipes-usr/soo/usr-linux_1.0.bbappend b/build/meta-usr/recipes-usr/soo/usr-linux_1.0.bbappend index c9343a4e6d..ee1bdaad47 100644 --- a/build/meta-usr/recipes-usr/soo/usr-linux_1.0.bbappend +++ b/build/meta-usr/recipes-usr/soo/usr-linux_1.0.bbappend @@ -27,7 +27,10 @@ do_install_apps:append () { fi } -do_clean:append() { +# usr.bbclass defines do_clean in Python, and a shell :append is pasted +# verbatim into that Python function, so `-c clean` died on a SyntaxError. +# The shell stays shell, in its own function, called from a Python append. +usr_linux_clean_soo () { if echo ":${OVERRIDES}:" | grep -q ":soo"; then rm -rf ${IB_TARGET}/src/soo @@ -41,3 +44,7 @@ do_clean:append() { fi } + +python do_clean:append () { + bb.build.exec_func('usr_linux_clean_soo', d) +} diff --git a/scripts/dbuild.sh b/scripts/dbuild.sh index 9aef98610f..b46eba72d3 100755 --- a/scripts/dbuild.sh +++ b/scripts/dbuild.sh @@ -119,6 +119,16 @@ fi set -- -e IB_TREE="$IB_ROOT" -e IB_CWD="$cwd" "$@" +# Forward the per-invocation knobs env.sh lets through to bitbake +# (BB_ENV_PASSTHROUGH_ADDITIONS) when they are set on the host. Without it +# `IB_FORCE_ATTACH=1 dbuild.sh build.sh ` — what the attach guard +# itself advises — never reached the container, and the guard kept refusing. + +for _v in IB_FORCE_ATTACH IB_PARTITION_LAYOUT; do + eval "_val=\${$_v:-}" + [ -n "$_val" ] && set -- -e "$_v=$_val" "$@" +done + # Hardware deployment: make any IB_HTTP_DEPLOY_PATH feed directory # visible at its own path so `deploy.sh` can publish into it from inside # (IB_STORAGE_MODE=http). This serves the verdin-imx8mp TEZI flow; on the From 4b393ff10a0a5c480e0270fd0f9bae3b8dcb4f1c Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 18:46:29 +0200 Subject: [PATCH 2/2] usr-linux: drop the extracted rootfs when the copy into it fails In the IB_RAMFS_SOURCE = "rootfs" case, do_deploy extracts rootfs.cpio, rsyncs the user space into it and re-packs it. A failed rsync left the root-owned extracted tree behind, where the p2 path unmounts in a finally (raised in review of the edgem1 MR). Not a plain finally here: __do_rootfs_umount re-packs the tree INTO rootfs.cpio, so it would bake a half-copied user space into the archive. On failure the tree is dropped and rootfs.cpio is left as it was; the next deploy starts over from it. Verified in pos_student with IB_RAMFS_SOURCE = "rootfs": a normal deploy re-packs rootfs.cpio with the apps; a deploy made to fail (a file where the rootfs has a directory, rsync exit 23) fails the task, leaves rootfs.cpio byte-identical and removes the extracted tree. --- .../meta-usr/recipes-usr/linux/usr-linux_1.0.bb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index 54ac85f0f7..d07086bcb0 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -93,8 +93,20 @@ python do_deploy() { d.setVar('ROOTFS_FILENAME', 'rootfs') __do_rootfs_mount(d) - utils_sudo(["rsync", "-a", "--keep-dirlinks", - deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + + # A failed copy must not leave the extracted, root-owned tree behind, + # as the p2 path below makes sure of for its mount. But not through a + # plain finally: __do_rootfs_umount re-packs the tree INTO rootfs.cpio, + # and a half-copied user space must not end up there. On failure the + # tree is dropped and rootfs.cpio stays as it was; the next deploy + # starts over from it. + try: + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + except Exception: + utils_sudo(["rm", "-rf", os.path.join(d.getVar('WORKDIR'), "fs")]) + raise + __do_rootfs_umount(d) bb.plain("usr deployed into rootfs.cpio (IB_RAMFS_SOURCE = rootfs)")