[RFC PATCH v1 3/3] ipq40xx: add Cambium E410 A/B sysupgrade support
Phil Taylor
phil at m0vse.uk
Sun Sep 20 15:51:56 PDT 2026
The E410 factory layout provides fixed linux0/rootfs0 and
linux1/rootfs1 UBI volume pairs. Add a metadata-bearing sysupgrade image
and update only the pair which is not currently running.
Validate the archive, FIT and UBIFS magic, payload sizes and destination
volumes before writing. Make the active slot the persistent boot target
before modifying its peer, then verify both newly written volumes by
reading back the exact payload length and comparing SHA-256 hashes.
When configuration preservation is requested, place sysupgrade.tgz in
the new UBIFS root. Finally arm a one-shot trial command which restores
the previous slot as the persistent fallback before attempting to boot
the candidate. A late userspace service commits the candidate only after
ubus, Ethernet and both Wi-Fi PHYs are present.
Batch related environment changes to limit erase cycles on the single
non-redundant APPSBLENV sector, and read the critical values back before
continuing.
The image passed sysupgrade -T and a configuration-preserving hardware
upgrade. The AP booted the inactive pair, retained its configuration and
completed a subsequent persistent reboot. Power-interruption fault
injection at each write and environment-update boundary remains to be
completed and is called out in the RFC cover letter.
Signed-off-by: Phil Taylor <phil at m0vse.uk>
---
.../base-files/etc/init.d/e410-slot-commit | 14 ++
.../base-files/etc/rc.d/S99e410-slot-commit | 1 +
.../base-files/lib/upgrade/platform.sh | 178 +++++++++++++++++-
.../base-files/usr/sbin/e410-slot-commit | 48 +++++
target/linux/ipq40xx/image/generic.mk | 5 +-
5 files changed, 244 insertions(+), 2 deletions(-)
create mode 100755 target/linux/ipq40xx/base-files/etc/init.d/e410-slot-commit
create mode 120000 target/linux/ipq40xx/base-files/etc/rc.d/S99e410-slot-commit
create mode 100755 target/linux/ipq40xx/base-files/usr/sbin/e410-slot-commit
diff --git a/target/linux/ipq40xx/base-files/etc/init.d/e410-slot-commit b/target/linux/ipq40xx/base-files/etc/init.d/e410-slot-commit
new file mode 100755
index 0000000..7f9a133
--- /dev/null
+++ b/target/linux/ipq40xx/base-files/etc/init.d/e410-slot-commit
@@ -0,0 +1,14 @@
+#!/bin/sh /etc/rc.common
+# SPDX-License-Identifier: GPL-2.0-only
+
+. /lib/functions/system.sh
+
+START=99
+USE_PROCD=1
+
+start_service() {
+ [ "$(board_name 2>/dev/null)" = 'cambiumnetworks,e410' ] || return 0
+ procd_open_instance
+ procd_set_param command /usr/sbin/e410-slot-commit
+ procd_close_instance
+}
diff --git a/target/linux/ipq40xx/base-files/etc/rc.d/S99e410-slot-commit b/target/linux/ipq40xx/base-files/etc/rc.d/S99e410-slot-commit
new file mode 120000
index 0000000..1a1bb2c
--- /dev/null
+++ b/target/linux/ipq40xx/base-files/etc/rc.d/S99e410-slot-commit
@@ -0,0 +1 @@
+../init.d/e410-slot-commit
\ No newline at end of file
diff --git a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
index 29f77ab..5013235 100644
--- a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
+++ b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
@@ -1,11 +1,75 @@
PART_NAME=firmware
REQUIRE_IMAGE_METADATA=1
-RAMFS_COPY_BIN='fw_printenv fw_setenv'
+RAMFS_COPY_BIN='fw_printenv fw_setenv head sha256sum'
RAMFS_COPY_DATA='/etc/fw_env.config /var/lock/fw_printenv.lock'
+cambiumnetworks_e410_active_slot() {
+ sed -n 's/.*root=ubi0:rootfs\([01]\).*/\1/p' /proc/cmdline
+}
+
+cambiumnetworks_e410_image_info() {
+ local image="$1"
+ local board_dir kernel_magic rootfs_magic
+
+ board_dir=$(tar tf "$image" |
+ grep -m 1 '^sysupgrade-cambiumnetworks_e410/$')
+ board_dir=${board_dir%/}
+ [ -n "$board_dir" ] || return 1
+
+ E410_BOARD_DIR="$board_dir"
+ E410_KERNEL_SIZE=$(tar xOf "$image" "$board_dir/kernel" | wc -c)
+ E410_ROOTFS_SIZE=$(tar xOf "$image" "$board_dir/root" | wc -c)
+ [ "$E410_KERNEL_SIZE" -gt 0 ] 2>/dev/null || return 1
+ [ "$E410_ROOTFS_SIZE" -gt 0 ] 2>/dev/null || return 1
+ [ "$E410_KERNEL_SIZE" -le 4317184 ] || return 1
+ [ "$E410_ROOTFS_SIZE" -le 47235072 ] || return 1
+
+ kernel_magic=$(tar xOf "$image" "$board_dir/kernel" |
+ head -c 4 | hexdump -v -e '1/1 "%02x"')
+ rootfs_magic=$(tar xOf "$image" "$board_dir/root" |
+ head -c 4 | hexdump -v -e '1/1 "%02x"')
+ [ "$kernel_magic" = d00dfeed ] || return 1
+ [ "$rootfs_magic" = 31181006 ] || return 1
+}
+
+cambiumnetworks_e410_check_image() {
+ local image="$1"
+ local active target kernel_vol rootfs_vol
+
+ nand_do_platform_check "$(board_name)" "$image" || return 1
+ cambiumnetworks_e410_image_info "$image" || {
+ echo "Invalid Cambium E410 A/B sysupgrade image"
+ return 1
+ }
+
+ active=$(cambiumnetworks_e410_active_slot)
+ case "$active" in
+ 0) target=1 ;;
+ 1) target=0 ;;
+ *) echo "Cannot determine the active E410 slot"; return 1 ;;
+ esac
+
+ kernel_vol=$(nand_find_volume ubi0 "linux$target")
+ rootfs_vol=$(nand_find_volume ubi0 "rootfs$target")
+ [ -n "$kernel_vol" ] && [ -n "$rootfs_vol" ] || {
+ echo "The inactive E410 UBI volume pair is missing"
+ return 1
+ }
+ [ "$E410_KERNEL_SIZE" -le \
+ "$(cat "/sys/class/ubi/$kernel_vol/data_bytes")" ] || return 1
+ [ "$E410_ROOTFS_SIZE" -le \
+ "$(cat "/sys/class/ubi/$rootfs_vol/data_bytes")" ] || return 1
+
+ return 0
+}
+
platform_check_image() {
case "$(board_name)" in
+ cambiumnetworks,e410)
+ cambiumnetworks_e410_check_image "$1"
+ return $?
+ ;;
asus,map-ac1300|\
asus,rt-ac42u|\
asus,rt-ac58u)
@@ -85,6 +149,115 @@ zyxel_do_upgrade() {
fi
}
+cambiumnetworks_e410_boot_command() {
+ local slot="$1"
+
+ printf '%s' \
+ "setenv image $slot; " \
+ 'setenv bootargs "mtdparts=spi0.1:128M(fs) ' \
+ "ubi.mtd=fs root=ubi0:rootfs$slot " \
+ 'rootfstype=ubifs rootwait"; ' \
+ 'nand device 1 && setenv mtdids nand1=nand1 && ' \
+ 'setenv mtdparts "mtdparts=nand1:0x8000000 at 0x0(fs)" && ' \
+ "ubi part fs && ubi read 0x84000000 linux$slot && " \
+ 'bootm 0x84000000#config at ap.dk01.1-c2'
+}
+
+cambiumnetworks_e410_do_upgrade() {
+ local image="$1"
+ local active target board_dir kernel_vol rootfs_vol
+ local kernel_tmp=/tmp/e410-sysupgrade-kernel.itb
+ local rootfs_tmp=/tmp/e410-sysupgrade-rootfs.ubifs
+ local new_root=/tmp/e410-sysupgrade-root
+ local env_tmp=/tmp/e410-sysupgrade-env
+ local kernel_hash rootfs_hash written_hash
+ local boot0 boot1 stable trial
+
+ cambiumnetworks_e410_image_info "$image" || return 1
+ board_dir="$E410_BOARD_DIR"
+ active=$(cambiumnetworks_e410_active_slot)
+ case "$active" in
+ 0) target=1 ;;
+ 1) target=0 ;;
+ *) return 1 ;;
+ esac
+ kernel_vol=$(nand_find_volume ubi0 "linux$target") || return 1
+ rootfs_vol=$(nand_find_volume ubi0 "rootfs$target") || return 1
+
+ boot0=$(cambiumnetworks_e410_boot_command 0) || return 1
+ boot1=$(cambiumnetworks_e410_boot_command 1) || return 1
+ stable="run owrt_boot$active; run owrt_boot$target"
+ trial="setenv bootcmd '$stable'; setenv image $active; "
+ trial="${trial}setenv e410_upgrade_state fallback-restored; saveenv; "
+ trial="${trial}run owrt_boot$target; run owrt_boot$active"
+
+ # Make the currently running slot persistent before modifying its peer. The
+ # final bootcmd update below is deliberately the last persistent operation.
+ {
+ printf 'owrt_boot0 %s\n' "$boot0"
+ printf 'owrt_boot1 %s\n' "$boot1"
+ printf 'bootcmd %s\n' "$stable"
+ printf 'image %s\n' "$active"
+ printf 'e410_upgrade_target %s\n' "$target"
+ printf 'e410_upgrade_fallback %s\n' "$active"
+ printf 'e410_upgrade_state writing\n'
+ } >"$env_tmp"
+ fw_setenv -s "$env_tmp" || {
+ rm -f "$env_tmp"
+ return 1
+ }
+ rm -f "$env_tmp"
+ sync
+ [ "$(fw_printenv -n bootcmd 2>/dev/null)" = "$stable" ] || return 1
+ [ "$(fw_printenv -n image 2>/dev/null)" = "$active" ] || return 1
+ [ "$(fw_printenv -n e410_upgrade_state 2>/dev/null)" = writing ] ||
+ return 1
+
+ tar xOf "$image" "$board_dir/kernel" >"$kernel_tmp" || return 1
+ tar xOf "$image" "$board_dir/root" >"$rootfs_tmp" || return 1
+ kernel_hash=$(sha256sum "$kernel_tmp" | awk '{print $1}')
+ rootfs_hash=$(sha256sum "$rootfs_tmp" | awk '{print $1}')
+
+ ubiupdatevol "/dev/$kernel_vol" "$kernel_tmp" || return 1
+ ubiupdatevol "/dev/$rootfs_vol" "$rootfs_tmp" || return 1
+ sync
+ written_hash=$(head -c "$E410_KERNEL_SIZE" "/dev/$kernel_vol" |
+ sha256sum | awk '{print $1}')
+ [ "$written_hash" = "$kernel_hash" ] || return 1
+ written_hash=$(head -c "$E410_ROOTFS_SIZE" "/dev/$rootfs_vol" |
+ sha256sum | awk '{print $1}')
+ [ "$written_hash" = "$rootfs_hash" ] || return 1
+ rm -f "$kernel_tmp" "$rootfs_tmp"
+
+ if [ -n "$UPGRADE_BACKUP" ]; then
+ mkdir -p "$new_root"
+ mount -t ubifs "/dev/$rootfs_vol" "$new_root" || return 1
+ mv "$UPGRADE_BACKUP" "$new_root/$BACKUP_FILE" || {
+ umount "$new_root"
+ return 1
+ }
+ sync
+ umount "$new_root" || return 1
+ rmdir "$new_root"
+ UPGRADE_BACKUP=
+ fi
+
+ {
+ printf 'e410_upgrade_state trial-armed\n'
+ printf 'bootcmd %s\n' "$trial"
+ } >"$env_tmp"
+ fw_setenv -s "$env_tmp" || {
+ rm -f "$env_tmp"
+ return 1
+ }
+ rm -f "$env_tmp"
+ sync
+ [ "$(fw_printenv -n bootcmd 2>/dev/null)" = "$trial" ] || return 1
+ [ "$(fw_printenv -n e410_upgrade_state 2>/dev/null)" = trial-armed ] ||
+ return 1
+ echo "E410 inactive slot $target written and protected trial boot armed"
+}
+
platform_do_upgrade_mikrotik_nand() {
local fw_mtd=$(find_mtd_part kernel)
fw_mtd="${fw_mtd/block/}"
@@ -105,6 +278,9 @@ platform_do_upgrade_mikrotik_nand() {
platform_do_upgrade() {
case "$(board_name)" in
+ cambiumnetworks,e410)
+ cambiumnetworks_e410_do_upgrade "$1"
+ ;;
8dev,jalapeno|\
aruba,ap-303|\
aruba,ap-303h|\
diff --git a/target/linux/ipq40xx/base-files/usr/sbin/e410-slot-commit b/target/linux/ipq40xx/base-files/usr/sbin/e410-slot-commit
new file mode 100755
index 0000000..6ce8317
--- /dev/null
+++ b/target/linux/ipq40xx/base-files/usr/sbin/e410-slot-commit
@@ -0,0 +1,48 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+
+. /lib/functions/system.sh
+
+[ "$(board_name 2>/dev/null)" = 'cambiumnetworks,e410' ] || exit 0
+[ "$(fw_printenv -n e410_upgrade_state 2>/dev/null)" = fallback-restored ] || exit 0
+
+target=$(fw_printenv -n e410_upgrade_target 2>/dev/null)
+fallback=$(fw_printenv -n e410_upgrade_fallback 2>/dev/null)
+case "$target:$fallback" in 0:1|1:0) ;; *) exit 1 ;; esac
+
+current=$(sed -n 's/.*root=ubi0:rootfs\([01]\).*/\1/p' /proc/cmdline)
+if [ "$current" = "$fallback" ]; then
+ printf 'e410_upgrade_state rolled-back\n' |
+ fw_setenv -s -
+ exit $?
+fi
+[ "$current" = "$target" ] || exit 1
+
+# Reaching late userspace is the rollback boundary. Give the Ethernet and
+# both Wi-Fi devices time to probe, but do not require an upstream network or
+# controller: an otherwise healthy AP may legitimately boot while isolated.
+attempt=0
+while [ "$attempt" -lt 180 ]; do
+ if ubus -S call system board >/dev/null 2>&1 &&
+ [ -e /sys/class/net/lan ] &&
+ [ -d /sys/class/ieee80211/phy0 ] &&
+ [ -d /sys/class/ieee80211/phy1 ]
+ then
+ bootcmd="run owrt_boot$target; run owrt_boot$fallback"
+ {
+ printf 'bootcmd %s\n' "$bootcmd"
+ printf 'image %s\n' "$target"
+ printf 'bootcount 0\n'
+ printf 'e410_upgrade_state committed\n'
+ } | fw_setenv -s - || exit 1
+ sync
+ [ "$(fw_printenv -n bootcmd)" = "$bootcmd" ] || exit 1
+ [ "$(fw_printenv -n image)" = "$target" ] || exit 1
+ exit 0
+ fi
+ attempt=$((attempt + 1))
+ sleep 1
+done
+
+echo 'E410 trial slot did not pass its local health checks' >&2
+exit 1
diff --git a/target/linux/ipq40xx/image/generic.mk b/target/linux/ipq40xx/image/generic.mk
index defd44b..5ef0648 100644
--- a/target/linux/ipq40xx/image/generic.mk
+++ b/target/linux/ipq40xx/image/generic.mk
@@ -372,11 +372,14 @@ define Device/cambiumnetworks_e410
DEVICE_FIT_COMPATIBLE := cambiumnetworks,e410
BLOCKSIZE := 128k
PAGESIZE := 2048
+ DEVICE_PACKAGES := uboot-envtools
KERNEL_INSTALL := 1
KERNEL_SIZE := 4216k
- IMAGES := $(if $(CONFIG_TARGET_ROOTFS_INITRAMFS),,kernel.itb rootfs.ubifs)
+ IMAGES := $(if $(CONFIG_TARGET_ROOTFS_INITRAMFS),,kernel.itb rootfs.ubifs sysupgrade.bin)
IMAGE/kernel.itb := append-kernel | check-size 4216k
IMAGE/rootfs.ubifs := e410-rootfs-ubifs | check-size 46128k
+ IMAGE/sysupgrade.bin := e410-rootfs-ubifs | check-size 46128k | \
+ sysupgrade-tar rootfs=$$$$@ | append-metadata
endef
TARGET_DEVICES += cambiumnetworks_e410
--
2.54.0 (Apple Git-157)
More information about the openwrt-devel
mailing list