[openwrt/openwrt] qualcommax: ipq60xx: enable dual-boot for 360v6

LEDE Commits lede-commits at lists.infradead.org
Sun Dec 28 03:45:43 PST 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/fa3b9f514942aab672dc4473d218370970c221b3

commit fa3b9f514942aab672dc4473d218370970c221b3
Author: Zhenyu Qi <qzydustin at hotmail.com>
AuthorDate: Sat Dec 13 14:04:51 2025 -0700

    qualcommax: ipq60xx: enable dual-boot for 360v6
    
    Add dual-partition upgrade support for Qihoo 360v6 using the generic
    bootconfig.sh library. This enables safe system upgrades with automatic
    failover capability.
    
    The device uses Qualcomm's bootconfig structure to control A/B partition
    switching. The bootloader dynamically maps physical NAND partitions to
    logical MTD devices based on the bootconfig, ensuring firmware always
    writes to the inactive partition.
    
    Implementation details:
    - Use bootconfig.sh library (copied from ipq50xx) for bootconfig operations
    - Operate on 'rootfs' partition by name instead of hardcoded offset
    - Add magic header validation for safety
    - Remove OEM UBI volumes (wifi_fw and ubi_rootfs) before sysupgrade
    - Toggle bootconfig before removing OEM volumes
    
    Hardware details:
    - SoC: Qualcomm IPQ6000
    - Flash: NAND with dual rootfs partitions (mtd16/mtd17)
    - Bootconfig: controls slot selection via partition name lookup
    
    Installation:
    Standard sysupgrade process. After upgrade, the system will boot
    from the new partition while preserving the old system as backup.
    
    The OEM volume cleanup is necessary because these volumes are created
    by the stock firmware and are not automatically cleaned by the standard
    nand_upgrade_prepare_ubi() function, which only removes volumes named
    'kernel', 'rootfs', and 'rootfs_data'. Without this cleanup, the remaining
    OEM volumes consume available space, causing the creation of rootfs_data
    to fail during sysupgrade.
    
    Tested on Qihoo 360v6 running stock firmware and OpenWrt.
    
    Signed-off-by: Zhenyu Qi <qzydustin at hotmail.com>
    Link: https://github.com/openwrt/openwrt/pull/21154
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 .../ipq60xx/base-files/lib/functions/bootconfig.sh | 177 +++++++++++++++++++++
 .../ipq60xx/base-files/lib/upgrade/platform.sh     |  47 +++++-
 2 files changed, 223 insertions(+), 1 deletion(-)

diff --git a/target/linux/qualcommax/ipq60xx/base-files/lib/functions/bootconfig.sh b/target/linux/qualcommax/ipq60xx/base-files/lib/functions/bootconfig.sh
new file mode 100644
index 0000000000..3632c6a25e
--- /dev/null
+++ b/target/linux/qualcommax/ipq60xx/base-files/lib/functions/bootconfig.sh
@@ -0,0 +1,177 @@
+. /lib/functions.sh
+
+PART_SIZE=20
+NAME_SIZE=16
+MAX_NUM_PARTS=16
+
+MAGIC_START_HEX="a0 a1 a2 a3"
+MAGIC_START_TRY_HEX="a1 a1 a2 a3"
+MAGIC_END_HEX="b0 b1 b2 b3"
+
+validate_bootconfig_magic() {
+	local file=$1
+	magic_start=$(hexdump -v -n 4 -e '4/1 "%02x "' "$file")
+	magic_end=$(hexdump -v -s 332 -n 4 -e '4/1 "%02x "' "$file")
+	
+	if [ "$magic_start" != "$MAGIC_START_HEX" ] && \
+	   [ "$magic_start" != "$MAGIC_START_TRY_HEX" ]; then
+		echo "Not a valid bootconfig file, start magic does not match" >&2
+		return 1
+	fi
+
+	if [ "$magic_end" != "$MAGIC_END_HEX" ]; then
+		echo "Not a valid bootconfig file, end magic does not match" >&2
+		return 1
+	fi
+	return 0
+}
+
+get_bootconfig_numparts() {
+	local file=$1
+	numpartshex=$(hexdump -v -s 8 -n 4 -e '4/1 "%02x "' "$file")
+	numparts=$(( 0x$(echo $numpartshex | awk '{print $4$3$2$1}') ))
+	echo ${numparts}
+}
+
+get_bootconfig_partidx() {
+	local file=$1
+	local partname=$2
+	local numparts=$(get_bootconfig_numparts "$file")
+
+	if [ -z "$numparts" ]; then
+		echo "Could not get number of partitions" >&2
+		return
+	fi
+
+	if [ $numparts -gt $MAX_NUM_PARTS ]; then
+		numparts=$MAX_NUM_PARTS
+	fi
+
+	for i in $(seq 0 $((numparts -1))); do
+		nameoffset=$((12 + i * $PART_SIZE))
+		nameraw=$(dd if="$file" bs=1 skip="$nameoffset" count=12 2>/dev/null)
+		name=${nameraw//S'\x00'/}
+		if [ "$partname" = "$name" ]; then
+			echo $i
+		fi
+	done
+}
+
+get_bootconfig_primaryboot() {
+	local file=$1
+	local partname=$2
+
+	local partidx=$(get_bootconfig_partidx "$file" "$partname")
+	if ! echo "$partidx" | grep -Eq '^[0-9]+$'; then
+		echo "Could not get partition index for $partname in $file" >&2
+		return
+	fi
+
+	if [ "$partidx" -ge 0 ] && [ "$partidx" -lt $MAX_NUM_PARTS ]; then
+		offset=$((12 + $partidx * $PART_SIZE + $NAME_SIZE))
+		primaryboothex=$(hexdump -v -s "$offset" -n 4 -e '4/1 "%02x "' $file)
+		primaryboot=$(( 0x$(echo $primaryboothex | awk '{print $4$3$2$1}') ))
+		echo $primaryboot
+	fi
+}
+
+_set_bootconfig_primaryboot() {
+	local file=$1
+	local partname=$2
+	local primaryboot=$3
+	local primaryboothex
+	local partidx
+	local primarybootoffset
+
+	partidx=$(get_bootconfig_partidx "$file" "$partname")
+	if ! echo "$partidx" | grep -Eq '^[0-9]+$'; then
+		echo "Could not get partition index for $2" >&2
+		return 1
+	fi
+	primarybootoffset=$((12 + $partidx * $PART_SIZE + $NAME_SIZE))
+
+	case "$primaryboot" in
+		0)
+			printf "\x00\x00\x00\x00" | dd of="$file" seek="$primarybootoffset" bs=1 count=4 conv=notrunc 2>/dev/null
+			;;
+		1)
+			printf "\x01\x00\x00\x00" | dd of="$file" seek="$primarybootoffset" bs=1 count=4 conv=notrunc 2>/dev/null
+			;;
+		*)
+			echo "invalid argument: primaryboot must be 0 or 1" >&2
+			return 1
+			;;
+	esac
+}
+
+set_bootconfig_primaryboot() {
+	local file=$1
+	local partname=$2
+	local primaryboot=$3
+
+	[ -z "$file" ] || [ -z "$partname" ] || [ -z "$primaryboot" ] && {
+		echo "usage: $0 <file> <partition name> <0|1>"
+		return 1
+	}
+
+	[ ! -e "$file" ] && {
+		echo "file $file not found" >&2
+		return 1
+	}
+
+	[ ! -w $file ] && {
+		echo "file $file not writable" >&2
+		return 1
+	}
+
+	validate_bootconfig_magic "$file"
+	[ $? -ne 0 ] && return 1
+
+	_set_bootconfig_primaryboot $file $partname $primaryboot
+	[ $? -ne 0 ] && return 1
+
+	return 0
+}
+
+toggle_bootconfig_primaryboot() {
+	local file=$1
+	local partname=$2
+	local primaryboot
+	
+	[ -z "$file" ] || [ -z "$partname" ] && {
+		echo "usage: $0 <file> <partition name>"
+		return 1
+	}
+
+	[ ! -e "$file" ] && {
+		echo "file $file not found" >&2
+		return 1
+	}
+
+	[ ! -w $file ] && {
+		echo "file $file not writable" >&2
+		return 1
+	}
+
+	validate_bootconfig_magic "$file"
+	[ $? -ne 0 ] && return 1
+	
+	primaryboot=$(get_bootconfig_primaryboot "$1" "$2")
+
+	case "$primaryboot" in
+		0)
+			_set_bootconfig_primaryboot "$1" "$2" 1
+			;;
+		1)
+			_set_bootconfig_primaryboot "$1" "$2" 0
+			;;
+		*)
+			echo "invalid value: primaryboot must be 0 or 1" >&2
+			return 1
+			;;
+	esac
+
+	[ $? -ne 0 ] && return 1
+
+	return 0
+}
diff --git a/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh b/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh
index d31363521f..edd0127143 100644
--- a/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh
+++ b/target/linux/qualcommax/ipq60xx/base-files/lib/upgrade/platform.sh
@@ -1,3 +1,5 @@
+. /lib/functions/bootconfig.sh
+
 PART_NAME=firmware
 REQUIRE_IMAGE_METADATA=1
 
@@ -27,6 +29,43 @@ remove_oem_ubi_volume() {
 	fi
 }
 
+qihoo_bootconfig_toggle_rootfs() {
+	local partname=$1
+	local tempfile
+	local mtdidx
+
+	mtdidx=$(find_mtd_index "$partname")
+	[ ! "$mtdidx" ] && {
+		echo "cannot find mtd index for $partname"
+		return 1
+	}
+
+	tempfile=/tmp/mtd"$mtdidx".bin
+	dd if=/dev/mtd"$mtdidx" of="$tempfile" bs=1 count=336 2>/dev/null
+	[ $? -ne 0 ] || [ ! -f "$tempfile" ] && {
+		echo "failed to create a temp copy of /dev/mtd$mtdidx"
+		return 1
+	}
+
+	toggle_bootconfig_primaryboot "$tempfile" "rootfs"
+	[ $? -ne 0 ] && {
+		echo "failed to toggle primaryboot for rootfs partition"
+		return 1
+	}
+
+	mtd write "$tempfile" /dev/mtd"$mtdidx" 2>/dev/null
+	[ $? -ne 0 ] && {
+		echo "failed to write temp copy back to /dev/mtd$mtdidx"
+		return 1
+	}
+
+	# Update bootconfig1 if exists
+	local mtdidx1=$(find_mtd_index "${partname}1")
+	[ -n "$mtdidx1" ] && mtd write "$tempfile" /dev/mtd"$mtdidx1" 2>/dev/null
+
+	return 0
+}
+
 tplink_get_boot_part() {
 	local cur_boot_part
 	local args
@@ -117,8 +156,14 @@ platform_do_upgrade() {
 		;;
 	glinet,gl-ax1800|\
 	glinet,gl-axt1800|\
-	netgear,wax214|\
+	netgear,wax214)
+		nand_do_upgrade "$1"
+		;;
 	qihoo,360v6)
+		CI_UBIPART="rootfs_1"
+		qihoo_bootconfig_toggle_rootfs "0:bootconfig"
+		remove_oem_ubi_volume wifi_fw
+		remove_oem_ubi_volume ubi_rootfs
 		nand_do_upgrade "$1"
 		;;
 	netgear,wax610|\




More information about the lede-commits mailing list