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@hotmail.com>
Link: https://github.com/openwrt/openwrt/pull/21154
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Zhenyu Qi 2025-12-13 14:04:51 -07:00 committed by Robert Marko
parent 08a1cace9f
commit fa3b9f5149
2 changed files with 223 additions and 1 deletions

View File

@ -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
}

View File

@ -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|\