OpenWrt support for Linksys MX6200

Currently, the rootfs volume name is hard coded. However, this change is only necessary if you want to generate an image compatible with the OEM image for updating via UI.

you mean renaming it in the dts partition table?

btw, am back online. ubiformat doesn't work with either stock or openwrt, mtd does work with both.

No, rename in source code. But this method didn't work anyway because of the signature.

Do you have access to the shell on the OEM firmware or after running initramfs via tftp?

yes, on both stock and openwrt.

again, perhaps I'm sleepy today, but where in source?

please post ubinfo -a output on both.

Third string: https://github.com/openwrt/openwrt/blob/main/scripts/ubinize-image.sh#L93

openwrt - flashed to /dev/mtd19
root@OpenWrt:~# ubinfo
UBI version:                    1
Count of UBI devices:           1
UBI control device major/minor: 10:256
Present UBI devices:            ubi0
root@OpenWrt:~# ubinfo -a
UBI version:                    1
Count of UBI devices:           1
UBI control device major/minor: 10:256
Present UBI devices:            ubi0

ubi0
Volumes count:                           3
Logical eraseblock size:                 126976 bytes, 124.0 KiB
Total amount of logical eraseblocks:     400 (50790400 bytes, 48.4 MiB)
Amount of available logical eraseblocks: 0 (0 bytes)
Maximum count of volumes                 128
Count of bad physical eraseblocks:       0
Count of reserved physical eraseblocks:  40
Current maximum erase counter value:     2
Minimum input/output unit size:          2048 bytes
Character device major/minor:            246:0
Present volumes:                         0, 1, 2

Volume ID:   0 (on ubi0)
Type:        dynamic
Alignment:   1
Size:        44 LEBs (5586944 bytes, 5.3 MiB)
State:       OK
Name:        kernel
Character device major/minor: 246:1
-----------------------------------
Volume ID:   1 (on ubi0)
Type:        dynamic
Alignment:   1
Size:        71 LEBs (9015296 bytes, 8.5 MiB)
State:       OK
Name:        rootfs
Character device major/minor: 246:2
-----------------------------------
Volume ID:   2 (on ubi0)
Type:        dynamic
Alignment:   1
Size:        241 LEBs (30601216 bytes, 29.1 MiB)
State:       OK
Name:        rootfs_data
Character device major/minor: 246:3
stock - flashed to /dev/mtd18
~ # ubinfo -a
UBI version:                    1
Count of UBI devices:           2
UBI control device major/minor: 10:60
Present UBI devices:            ubi0, ubi1

ubi0
Volumes count:                           2
Logical eraseblock size:                 126976 bytes, 124.0 KiB
Total amount of logical eraseblocks:     400 (50790400 bytes, 48.4 MiB)
Amount of available logical eraseblocks: 0 (0 bytes)
Maximum count of volumes                 128
Count of bad physical eraseblocks:       0
Count of reserved physical eraseblocks:  40
Current maximum erase counter value:     1
Minimum input/output unit size:          2048 bytes
Character device major/minor:            239:0
Present volumes:                         0, 1

Volume ID:   0 (on ubi0)
Type:        static
Alignment:   1
Size:        50 LEBs (6348800 bytes, 6.1 MiB)
Data bytes:  4830188 bytes (4.6 MiB)
State:       OK
Name:        kernel
Character device major/minor: 239:1
-----------------------------------
Volume ID:   1 (on ubi0)
Type:        dynamic
Alignment:   1
Size:        306 LEBs (38854656 bytes, 37.1 MiB)
State:       OK
Name:        ubi_rootfs
Character device major/minor: 239:2

===================================

ubi1
Volumes count:                           1
Logical eraseblock size:                 126976 bytes, 124.0 KiB
Total amount of logical eraseblocks:     1040 (132055040 bytes, 125.9 MiB)
Amount of available logical eraseblocks: 0 (0 bytes)
Maximum count of volumes                 128
Count of bad physical eraseblocks:       0
Count of reserved physical eraseblocks:  40
Current maximum erase counter value:     7
Minimum input/output unit size:          2048 bytes
Character device major/minor:            238:0
Present volumes:                         0

Volume ID:   0 (on ubi1)
Type:        dynamic
Alignment:   1
Size:        996 LEBs (126468096 bytes, 120.6 MiB)
State:       OK
Name:        syscfg
Character device major/minor: 238:1

And hexdump -C -n 16 <ubi_image> output.

~/$ hexdump -C -n 16 bin/targets/qualcommax/ipq50xx/openwrt-qualcommax-ipq50xx-linksys_mx6200-squashfs-factory.bin 
00000000  55 42 49 23 01 00 00 00  00 00 00 00 00 00 00 00  |UBI#............|
00000010
~/$ hexdump -C -n 16 ~/Downloads/FW_MX6200_1.0.11.216041_prod.signed.img
00000000  55 42 49 23 01 00 00 00  00 00 00 00 00 00 00 00  |UBI#............|
00000010

You should use openwrt-qualcommax-ipq50xx-linksys_mx6200-squashfs-factory.ubi image produced by UbiFit call for ubiformat.

I can, but then it won't start with the UBI container header. Is that okay? I've left out the call to UbiFit for now:

define Device/linksys_mx6200
	$(call Device/FitImage)
	DEVICE_VENDOR := Linksys
	DEVICE_MODEL := MX6200
	KERNEL_IN_UBI := 1
	BLOCKSIZE := 128k
	PAGESIZE := 2048
	DEVICE_DTS_CONFIG := config@mp03.5-c1
	KERNEL_SIZE := 8192k
	IMAGE_SIZE := 51200k
	NAND_SIZE := 256m
	SOC := ipq5018
	IMAGES += factory.bin
	IMAGE/factory.bin := append-ubi | linksys-image type=$$$$(DEVICE_MODEL) | pad-to $$$$(BLOCKSIZE)
	DEVICE_PACKAGES := ath11k-firmware-ipq5018-qcn6122 \
		ipq-wifi-linksys_mx6200
endef
TARGET_DEVICES += linksys_mx6200

No, you need to forget about building an OEM format image.

bootipq checks the signature, so you still need to modify bootcmd to be able to run OpenWrt:

yeah, that was my next challenge. Check the nandboot variables I've set so far:

IPQ5018# printenv
altkern=4880000
auto_recovery=yes
baudrate=115200
boot_part=2
boot_part_ready=3
boot_ver=7.1.25
bootargs=console=ttyMSM0,115200n8 ubi.mtd=rootfs root=mtd:ubi_rootfs rootfstype=squashfs rootwait
bootcmd=bootipq
bootdelay=3
config_name=config@1
dload_dis=1
eth1addr=00:11:22:33:44:56
eth2addr=00:11:22:33:44:55
eth3addr=00:11:22:33:44:56
ethact=eth0
ethaddr=00:03:7f:ba:db:ad
fdt_high=0x4A400000
fdtcontroladdr=4a9d4004
flash_type=11
flashimg=tftp $loadaddr $image && nand erase $prikern $imgsize && nand write $loadaddr $prikern $filesize
flashimg2=tftp $loadaddr $image && nand erase $altkern $imgsize && nand write $loadaddr $altkern $filesize
fsbootargs=ubi.mtd=rootfs root=mtd:ubi_rootfs rootfstype=squashfs
image=MX6200.img
imgsize=3200000
ipaddr=192.168.1.1
kernsize=800000
loadaddr=44000000
machid=8040004
mtddevname=fs
mtddevnum=0
nandboot=setenv mtdids nand0=nand0; setenv mtdparts mtdparts=nand0:0x${imgsize}@0x${prikern}(rootfs),0x${imgsize}@0x${altkern}(rootfs_1); ubi part $partrootfs 2048; ubi read $loadaddr kernel $kernsize; bootm $loadaddr
nandbootargs=setenv bootargs init=/sbin/init ubi.mtd=${partrootfs} root=mtd:ubi_rootfs rootfstype=squashfs rootwait
nandbootcmd=if test $auto_recovery = no; then bootipq; elif test $boot_part = 1; then setenv partrootfs rootfs; else setenv partrootfs rootfs_1; fi; run nandbootargs; run nandboot
netmask=255.255.255.0
partition=nand0,0
prikern=1680000
serverip=192.168.1.254
soc_hw_version=20180101
soc_version_major=1
soc_version_minor=1
stderr=serial@78AF000
stdin=serial@78AF000
stdout=serial@78AF000
tftpboot=tftp $loadaddr openwrt-qualcommax-ipq50xx-linksys_mx6200-initramfs-uImage.itb && bootm $loadaddr

this works if you have stock on the first partition and openwrt on the second. (if you set bootcmd = nandbootcmd, but I've reverted for now..)

I need a script, preferably that fits in a variable, that checks whether the image found is signed or not, then choose to boot using bootipq, else alternative nandboot method.
I should be able to load bytes at a fixed offset to memory and check if the image is signed and then choose accordingly, but I haven't spent time on that yet.
The other question is how it handles the variables upon boot failures. From what I've seen it's the same as on other llinksys devices, BUT.. if I change variables from openwrt using fw_setenv, the changes do not persist after a reboot.
setenv works from uboot, but when changing boot_part, it reboots the system and only then it persists. Maybe that's locked down or secured as well?

and yes, I've set uboot_env for mx6200 already.

What if you have OpenWrt on both partitions and you try to boot the system manually?

From OEM:

CURRENT_BOOT_IMAGE=`syscfg get fwup_boot_part`
if [ $CURRENT_BOOT_IMAGE -eq 1 ] ; then
  fw_setenv boot_part_ready 3
  fw_setenv boot_part 2
  echo 1 > /proc/boot_info/0:HLOS/primaryboot
  echo 1 > /proc/boot_info/rootfs/primaryboot
  do_flash_bootconfig bootconfig "0:BOOTCONFIG"
  do_flash_bootconfig bootconfig1 "0:BOOTCONFIG1"
elif [ $CURRENT_BOOT_IMAGE -eq 2 ] ; then
  fw_setenv boot_part_ready 3
  fw_setenv boot_part 1
  echo 0 > /proc/boot_info/0:HLOS/primaryboot
  echo 0 > /proc/boot_info/rootfs/primaryboot
  do_flash_bootconfig bootconfig "0:BOOTCONFIG"
  do_flash_bootconfig bootconfig1 "0:BOOTCONFIG1"
else
  echo "detected odd boot partition $CURRENT_BOOT_IMAGE"
fi
# add sync to make sure disk cache is flushed after upgrade
sync

findings:

  1. Stock web UI accepts the factory.ubi image and flashes it. It switches to the alternative partition but won't boot since the image is not signed. Uboot checks via a TZ call whether secureboot is enabled or not (efused). So, unless we find a vulnerability, this path without a serial connection is blocked because uboot needs to be configured to use bootm instead of bootipq to bypass image verification.
  2. I've managed to persist the change to switch to the alternate partition. Calling fw_setenv boot_part is not sufficient. Uboot syncs this uboot variable with the 0:BOOTCONFIG and 0:BOOTCONFIG1 partitions:
> parittion 1 is active
auto-recovery enabled:1, boot_part:2, boot_part_ready:3
#### Sync BOOTCONFIG to boot_part(2 -> 1) ####
varname=boot_part, varvalue=1
Saving Environment to NAND...
Erasing NAND...
Erasing at 0x1460000 -- 100% complete.
Writing to NAND... OK

See first 3 lines where it detects boot_part isn't in sync.
This can be overcome by mimicking the OEM script to switch partitions. It writes the partition value to both BOOTCONFIG partitions which contains a list of partitions and:

  1. sets the partitions 0:HLOS and rootfs accordingly (0 for boot_part 1, and 1 for boot_part 2)
  2. 2 bytes change, haven't figured out what (yet)
  3. swaps 2 sets of 2 bytes later on in another partition table what I'm assuming marks active / inactive

Here are the files: https://limewire.com/d/byGuF#IrCnD9ADvT

Depending on the active partition, the same content is copied from one partition to another:

34825e767b9e9763e279944e925e90b9fdf318d3575ad092025c79ed9df87ac5 *mtd2_bp1.bin
7657c9227852aebd7ead428236cad32a6ee8bccdbb0f8d2b03b5b3a8df41bfae *mtd2_bp2.bin
34825e767b9e9763e279944e925e90b9fdf318d3575ad092025c79ed9df87ac5 *mtd3_bp1.bin
7657c9227852aebd7ead428236cad32a6ee8bccdbb0f8d2b03b5b3a8df41bfae *mtd3_bp2.bin
do_flash_mtd() {

    local bin=$1
    local mtdname=$2
    local pgsz

    local mtdpart=$(grep "\"${mtdname}\"" /proc/mtd | awk -F: '{print $1}')
    if [ ! -n "$mtdpart" ]; then
        echo "$mtdname is not available" && return
    fi

    flash_erase /dev/${mtdpart} 0 0
    nandwrite -p /dev/${mtdpart} /tmp/${bin}.bin
}

do_flash_partition() {
    local bin=$1
    local mtdname=$2
    local emmcblock="$(find_mmc_part "$mtdname")"

    if [ -e "$emmcblock" ]; then
        do_flash_emmc $bin $emmcblock
    else
        do_flash_mtd $bin $mtdname
    fi
}

do_flash_bootconfig() {
    local bin=$1
    local mtdname=$2

    # Fail safe upgrade
    if [ -f /proc/boot_info/getbinary_${bin} ]; then
        cat /proc/boot_info/getbinary_${bin} > /tmp/${bin}.bin
        do_flash_partition $bin $mtdname
    fi
}

and these are differences:

You can take a look at the script to change partition for NBG7815: https://openwrt.org/inbox/toh/zyxel/nbg7815_armor_g5#back_to_oem_firmware_1

And an additional module is loaded to handle the boot partition selection (/proc/boot_info):

# load bootconfig
/sbin/modprobe bootconfig

made further progress. I've now got uboot to automatically use the right boot command:

  • bootipq for signed images (OEM)
  • bootm for non-signed (Openwrt)

Essentially, in sequence, it:

  • runs bootipq if autorecovery is needed (3 boot failures) to switch to alternative partition
  • sets the rootfs (rootfs or rootfs_1) based on value of boot_part variable
  • checks if the header is a valid mbn header and whether it's signed
  • if so, run bootipq (for OEM signed)
  • if not, load kernel from nand, set bootargs, and run bootm (for anything else, including Openwrt)

here are the commands:

setenv setnandbootargs 'setenv bootargs init=/sbin/init ubi.mtd=${partrootfs} root=mtd:ubi_rootfs rootfstype=squashfs rootwait'
setenv nandinitcmd 'setenv mtdids nand0=nand0; setenv mtdparts mtdparts=nand0:0x${imgsize}@0x${prikern}(rootfs),0x${imgsize}@0x${altkern}(rootfs_1); ubi part $partrootfs 2048'
setenv setrootfscmd 'if test $boot_part = 1; then setenv partrootfs rootfs; else setenv partrootfs rootfs_1; fi'
setenv readhdr1cmd 'ubi read $loadaddr kernel 0x40; setexpr IMGOFF $loadaddr + 0x10; setexpr CODEOFF $loadaddr + 0x14; setexpr SIGOFF $loadaddr + 0x1c; setexpr CERTOFF $loadaddr + 0x24'
setenv readhdr2cmd 'setexpr.l HDR *$loadaddr; setexpr.l IMGSZ *$IMGOFF; setexpr.l CODESZ *$CODEOFF; setexpr.l SIGSZ *$SIGOFF; setexpr.l CERTSZ *$CERTOFF; setexpr TSIZE $CODESZ + $SIGSZ; setexpr TSIZE $TSIZE + $CERTSZ'
setenv testmbncmd 'if test $HDR -ne edfe0dd0 -a $IMGSZ -eq $TSIZE; then bootipq; else ubi read $loadaddr kernel $kernsize; run setnandbootargs; bootm $loadaddr; fi'
setenv bootcmd2 'if test $auto_recovery = no; then bootipq; else run setrootfscmd; run nandinitcmd; run readhdr1cmd; run readhdr2cmd; run testmbncmd; fi'

then set bootcmd to bootcmd2:

setenv bootcmd 'run bootcmd2'

I can also directly write to bootcmd, but for flexibility thought it's easier to have cmd2 still stored in case you'd like to switch back and forth between stock boot sequence vs custom.

I've tested power cycling 3 times to switch back and forth between partitions which works great.

It would be nicer to create a uboot script, but unsure where to store to and load and it from.

Next would be to look at sysupgrade and switching partitions from openwrt based on above findings and @lytr's comments. After that it would be ready for a PR.

Nice work! But couldn't this be simplified and bootm used for OEM firmware as well?

Nope, already tried:

IPQ5018# bootm $loadaddr
Wrong Image Format for bootm command
ERROR: can't get kernel image!

Could this be caused by trying to read the ubi rootfs volume instead of ubi_rootfs?
Due to the fact that different ubi volume names are used this is a bit problematic.

PR opened!