Skip to content

Commit 6f7903b

Browse files
committed
feat(rootfs): native armhf on aarch64 host via runtime-disable of qemu-arm
Continues #9284 (arm64-compat-vdso extension + custom_kernel_make_params hook). That PR enabled COMPAT_VDSO in arm64 kernels and wired CROSS_COMPILE_COMPAT through the build; this PR uses the resulting 32-bit-capable kernels to run 32-bit ARM ELF directly instead of through qemu-arm emulation in the image-build chroot work. Disable qemu-arm in /proc/sys/fs/binfmt_misc for the duration of the image-build so 32-bit ARM ELF falls through to kernel binfmt_elf and runs natively via CONFIG_COMPAT — typically ~12x faster than qemu-arm emulation on common ARM cores (Cortex-A53/A55/A72/A73/A76). A cleanup handler re-enables qemu-arm on build exit (success or failure). Empirical: helios4 BUILD_MINIMAL armhf rootfs cache-miss build on RK3568 (Cortex-A55) drops from 60:35 to 19:27 — 3.12x speedup; same build with rootfs cache-hit drops to 6:38 (~5x). Activation point is delayed to AFTER mmdebstrap (in create_new_rootfs_cache_via_debootstrap, right past the bash-presence check) and also at build_rootfs_and_image entry for the rootfs-cache hit path. Earlier activation breaks mmdebstrap because its cross-arch machinery requires a working qemu-arm in binfmt_misc and the chroot is not yet populated for native exec. Out-of-scope cases (target arch != armhf, host arch != aarch64, not running in container) return silently with no log noise. In-scope cases log the capability-check outcome at info level: native enabled, already-effective no-op, or 32-bit ARM execution unsupported (no COMPAT_VDSO in kernel, Apple Silicon, ARMv9-only cores) — falling back to qemu-arm-static emulation. Killswitch: NATIVE_ARMHF_ON_ARM64=no disables this path entirely, before any detection runs — useful on shared build farms or whenever opting out is desired (synonyms 'never' and 'disabled' are accepted by the parser). Sets ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes when active so that deploy_qemu_binary_to_chroot / undeploy_qemu_binary_from_chroot skip copying qemu-arm-static into the chroot during image-stage (the kernel handles the ELF directly). Caveat: the container shares /proc/sys/fs/binfmt_misc with the host's init user-ns (Armbian's image-build container has CAP_SYS_ADMIN regardless of DOCKER_PRIVILEGED), so disabling qemu-arm affects the host's other concurrent processes for the duration of the build. Acceptable for single-user builders, problematic for shared build farms where NATIVE_ARMHF_ON_ARM64=no is the right setting. Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Igor Velkov <325961+iav@users.noreply.github.com>
1 parent 3ce7dec commit 6f7903b

3 files changed

Lines changed: 102 additions & 1 deletion

File tree

lib/functions/main/rootfs-image.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ function build_rootfs_and_image() {
1313
# get a basic rootfs, either from cache or from scratch
1414
get_or_create_rootfs_cache_chroot_sdcard # only occurrence of this; has its own logging sections
1515

16+
# Cache-hit path also benefits — the extracted rootfs has libc/ld-linux, so kernel
17+
# binfmt_elf can run 32-bit ARM ELF natively. Idempotent on cache-miss path where
18+
# create_new_rootfs_cache_via_debootstrap already activated this.
19+
_native_armhf_setup_binfmt_elf || true
20+
1621
# deploy the qemu binary, no matter where the rootfs came from (built or cached)
1722
LOG_SECTION="deploy_qemu_binary_to_chroot_image" do_with_logging deploy_qemu_binary_to_chroot "${SDCARD}" "image" # undeployed at end of this function
1823

lib/functions/rootfs/qemu-static.sh

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ function deploy_qemu_binary_to_chroot() {
1717
return 0
1818
fi
1919

20+
# Native armhf path is active: kernel binfmt_elf executes 32-bit ARM ELF via
21+
# CONFIG_COMPAT, no qemu-arm-static needed inside the chroot.
22+
if [[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]]; then
23+
display_alert "Native armhf via binfmt_elf" "skipping qemu binary deployment during ${caller}" "info"
24+
return 0
25+
fi
26+
2027
declare src_host="/usr/bin/${QEMU_BINARY}"
2128
declare dst_target="${chroot_target}/usr/bin/${QEMU_BINARY}"
2229
declare dst_target_bkp="${dst_target}.armbian.orig"
@@ -45,6 +52,12 @@ function undeploy_qemu_binary_from_chroot() {
4552
return 0
4653
fi
4754

55+
# Pair with deploy_qemu_binary_to_chroot — nothing was deployed, nothing to undeploy.
56+
if [[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]]; then
57+
display_alert "Native armhf via binfmt_elf" "no qemu binary to remove during ${caller}" "debug"
58+
return 0
59+
fi
60+
4861
declare dst_target="${chroot_target}/usr/bin/${QEMU_BINARY}"
4962
declare dst_target_bkp="${dst_target}.armbian.orig"
5063

@@ -97,6 +110,83 @@ function prepare_host_binfmt_qemu() {
97110
return 0
98111
}
99112

113+
# Native armhf on aarch64 host via runtime-disable of qemu-arm in binfmt_misc.
114+
# With qemu-arm disabled, 32-bit ARM ELF falls through to kernel binfmt_elf, which
115+
# executes it natively via CONFIG_COMPAT (~12x faster than qemu-arm emulation on common
116+
# ARM cores). Under docker --privileged, /proc/sys/fs/binfmt_misc is shared with the
117+
# host (init user-ns), so this WILL affect host's other concurrent processes for the
118+
# duration of the build — acceptable for single-user builders, problematic for shared
119+
# build farms. Setting NATIVE_ARMHF_ON_ARM64=no (or never/disabled) disables the entire
120+
# code path before any detection runs.
121+
#
122+
# Returns 0 on success (caller sets ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes); 1 if the
123+
# preconditions are not met, the user opted out, or disable failed — caller falls back
124+
# to the qemu-arm path. Cleanup (re-enable qemu-arm) is registered via add_cleanup_handler.
125+
function _native_armhf_setup_binfmt_elf() {
126+
# Killswitch — accept no/never/disabled as synonyms; bail before any detection runs.
127+
case "${NATIVE_ARMHF_ON_ARM64:-auto}" in
128+
no | never | disabled) return 1 ;;
129+
esac
130+
131+
# Idempotent: callers may invoke this both after debootstrap (in
132+
# create_new_rootfs_cache_via_debootstrap) and after rootfs-cache extract (in
133+
# build_rootfs_and_image, for cache-hit). Once active, return success.
134+
[[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]] && return 0
135+
136+
# This optimisation only applies to armhf-target on aarch64-host inside a container.
137+
# Out-of-scope cases stay completely silent — no alerts, no syscalls past the guards.
138+
[[ "${ARCH}" == "armhf" ]] || return 1
139+
[[ "$(arch)" == "aarch64" ]] || return 1
140+
[[ "${ARMBIAN_RUNNING_IN_CONTAINER}" == "yes" ]] || return 1
141+
142+
# In-scope from here on: armhf-on-aarch64 in a container, killswitch off — log the
143+
# capability check result so the user sees whether the acceleration kicked in.
144+
145+
# Pre-flight: 32-bit ARM execution must actually work on this host. Fails when the
146+
# kernel lacks CONFIG_COMPAT/COMPAT_VDSO (extensions/arm64-compat-vdso, see PR #9284),
147+
# or when the CPU itself lacks 32-bit user mode (Apple Silicon, ARMv9-only cores).
148+
if ! arch-test armhf > /dev/null 2>&1; then
149+
display_alert "Native armhf via binfmt_elf" "32-bit ARM execution not supported on this kernel/host; falling back to qemu-arm-static emulation" "info"
150+
return 1
151+
fi
152+
153+
# Need a registered, enabled qemu-arm handler to disable; otherwise the kernel will
154+
# already fall through to binfmt_elf — mark active and report the no-op outcome.
155+
if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-arm ]]; then
156+
display_alert "Native armhf via binfmt_elf" "qemu-arm not registered in binfmt_misc; native armhf already in effect" "info"
157+
declare -g ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes
158+
return 0
159+
fi
160+
161+
if ! head -1 /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null | grep -q '^enabled'; then
162+
display_alert "Native armhf via binfmt_elf" "qemu-arm already disabled in binfmt_misc; native armhf already in effect" "info"
163+
declare -g ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes
164+
return 0
165+
fi
166+
167+
# Disable qemu-arm in the (shared) binfmt_misc; the kernel will fall through to
168+
# binfmt_elf for 32-bit ARM ELF and execute natively via CONFIG_COMPAT.
169+
if ! echo 0 > /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null; then
170+
display_alert "Native armhf via binfmt_elf" "could not disable qemu-arm (no CAP_SYS_ADMIN?); falling back to qemu-arm-static emulation" "wrn"
171+
return 1
172+
fi
173+
174+
# Register cleanup so qemu-arm gets re-enabled on build exit (success or fail).
175+
add_cleanup_handler trap_handler_native_armhf_restore_qemu_arm
176+
177+
display_alert "Native armhf via binfmt_elf" "kernel $(uname -r), aarch64 host with COMPAT_VDSO; qemu-arm disabled, kernel binfmt_elf takes over" "info"
178+
declare -g ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes
179+
return 0
180+
}
181+
182+
# Cleanup handler: re-enable qemu-arm after build (whether success or failure).
183+
function trap_handler_native_armhf_restore_qemu_arm() {
184+
[[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]] || return 0
185+
[[ -e /proc/sys/fs/binfmt_misc/qemu-arm ]] || return 0
186+
echo 1 > /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null || true
187+
display_alert "Native armhf via binfmt_elf" "qemu-arm re-enabled in binfmt_misc" "info"
188+
}
189+
100190
# The actual binfmt manipulations when cross-build is confirmed above.
101191
function prepare_host_binfmt_qemu_cross() {
102192
local failed_binfmt_modprobe=0
@@ -171,7 +261,7 @@ function prepare_host_binfmt_qemu_cross_arm64_host_armhf_target() {
171261
display_alert "Host can run armhf natively or emulation is correctly setup already" "no need to enable qemu-arm" "debug"
172262
else
173263
display_alert "arm64 host can't run armhf natively" "importing enabling qemu-arm" "debug"
174-
cat <<-BINFMT_ARM_MAGIC >/usr/share/binfmts/qemu-arm
264+
cat <<- BINFMT_ARM_MAGIC > /usr/share/binfmts/qemu-arm
175265
package qemu-user-static
176266
interpreter /usr/bin/qemu-arm-static
177267
magic \x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00

lib/functions/rootfs/rootfs-create.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ function create_new_rootfs_cache_via_debootstrap() {
142142

143143
[[ ! -f "${SDCARD}/bin/bash" ]] && exit_with_error "mmdebstrap did not produce /bin/bash"
144144

145+
# mmdebstrap done, libc/ld-linux are in ${SDCARD}. Disable qemu-arm in binfmt_misc
146+
# so subsequent chroot apt-get/dpkg/customize calls fall through to kernel binfmt_elf
147+
# and run 32-bit ARM ELF natively via CONFIG_COMPAT. mmdebstrap above used qemu-arm
148+
# because its cross-arch path requires that registration to be present.
149+
_native_armhf_setup_binfmt_elf || true
150+
145151
# Done with mmdebstrap. Clean-up its litterbox.
146152
display_alert "Cleaning up after mmdebstrap" "mmdebstrap cleanup" "info"
147153
run_host_command_logged rm -rf "${SDCARD}/var/cache/apt" "${SDCARD}/var/lib/apt/lists"

0 commit comments

Comments
 (0)