[openwrt/openwrt] ipq40xx: fix broken image generation for EX6150v2

LEDE Commits lede-commits at lists.infradead.org
Fri Jun 7 17:09:35 PDT 2024


blocktrron pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/de59fc45402ff03e320264c8204f6928090534ad

commit de59fc45402ff03e320264c8204f6928090534ad
Author: David Bauer <mail at david-bauer.net>
AuthorDate: Fri Jun 7 19:23:56 2024 +0200

    ipq40xx: fix broken image generation for EX6150v2
    
    All NETGEAR EX6150v2 validate the rootfs for which OpenWrt places a
    fakeheader at the position, where the bootloader expects it.
    
    Some EX6150v2 bootloaders do however make a broken assumption about
    where the rootfs starts. This is due to them calculating the rootfs
    start not based upon the kernel-length but the string-offset of the
    FIT-image.
    
    We have to be compatible with both this broken as well as the valid
    calculation. So we do relocate the FDT string section to a
    block-boundary and enlarge the FIT image to end at this boundary +
    BLOCKSIZE / 2. This way, both the broken as well as correct calculations
    do expect the rootfs-header at the same position.
    
    It is worth noting, that this is a rare edge-case in which only happens
    if the image-length as well as the start of the string-section are not
    placed in the same erase-block. This is an edge-case which happens very
    rarely (thus it was not spotted prior).
    
    Affected:
     - U-Boot 2012.07 (Jun 16 2016 - 11:59:37)
    
    Signed-off-by: David Bauer <mail at david-bauer.net>
    (cherry picked from commit 8f9546f7b0a14f3afa813e39ed45c968ece24464)
---
 target/linux/ipq40xx/image/generic.mk             |  9 ++-
 target/linux/ipq40xx/image/netgear-fit-padding.py | 89 +++++++++++++++++++++++
 2 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/target/linux/ipq40xx/image/generic.mk b/target/linux/ipq40xx/image/generic.mk
index 0fe7e02ed7..5f8082c99f 100644
--- a/target/linux/ipq40xx/image/generic.mk
+++ b/target/linux/ipq40xx/image/generic.mk
@@ -3,6 +3,11 @@ DEVICE_VARS += NETGEAR_BOARD_ID NETGEAR_HW_ID
 DEVICE_VARS += RAS_BOARD RAS_ROOTFS_SIZE RAS_VERSION
 DEVICE_VARS += WRGG_DEVNAME WRGG_SIGNATURE
 
+define Build/netgear-fit-padding
+	./netgear-fit-padding.py $@ $@.new
+	mv $@.new $@
+endef
+
 define Device/FitImage
 	KERNEL_SUFFIX := -uImage.itb
 	KERNEL = kernel-bin | gzip | fit gzip $$(KDIR)/image-$$(DEVICE_DTS).dtb
@@ -33,8 +38,8 @@ define Device/DniImage
 	NETGEAR_BOARD_ID :=
 	NETGEAR_HW_ID :=
 	IMAGES += factory.img
-	IMAGE/factory.img := append-kernel | pad-offset 64k 64 | append-uImage-fakehdr filesystem | append-rootfs | pad-rootfs | netgear-dni
-	IMAGE/sysupgrade.bin := append-kernel | pad-offset 64k 64 | append-uImage-fakehdr filesystem | \
+	IMAGE/factory.img := append-kernel | netgear-fit-padding | append-uImage-fakehdr filesystem | append-rootfs | pad-rootfs | netgear-dni
+	IMAGE/sysupgrade.bin := append-kernel | netgear-fit-padding | append-uImage-fakehdr filesystem | \
 		append-rootfs | pad-rootfs | check-size | append-metadata
 endef
 
diff --git a/target/linux/ipq40xx/image/netgear-fit-padding.py b/target/linux/ipq40xx/image/netgear-fit-padding.py
new file mode 100755
index 0000000000..87c0854b5a
--- /dev/null
+++ b/target/linux/ipq40xx/image/netgear-fit-padding.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+# -*- coding: utf-8 -*-
+
+# NETGEAR EX6150v2 padding tool
+# (c) 2024 David Bauer <mail at david-bauer.net>
+
+import math
+import sys
+
+FLASH_BLOCK_SIZE = 64 * 1024
+
+
+def read_field(data, offset):
+    return data[offset + 3] | data[offset + 2] << 8 | data[offset + 1] << 16 | data[offset] << 24
+
+
+if __name__ == '__main__':
+    if len(sys.argv) != 3:
+        print('Usage: {} <input-image> <output-image>'.format(sys.argv[0]))
+        sys.exit(1)
+
+    with open(sys.argv[1], 'rb') as f:
+        data = f.read()
+
+    file_len = len(data)
+
+    # File-len in fdt header at offset 0x4
+    file_len_hdr = read_field(data, 0x4)
+    # String offset in fdt header at offset 0xc
+    str_off = read_field(data, 0xc)
+
+    print("file_len={} hdr_file_len={} str_off={}".format(file_len, file_len_hdr, str_off))
+
+    # Off to NETGEAR calculations - Taken from u-boot source (cmd_dni.c:2145)
+    #
+    # rootfs_addr = (ntohl(hdr->ih_size)/CONFIG_SYS_FLASH_SECTOR_SIZE+1) * CONFIG_SYS_FLASH_SECTOR_SIZE +
+    #               2*sizeof(image_header_t)-sizeof(image_header_t);
+    # rootfs_addr = rootfs_addr - (0x80 - mem_addr);
+
+    # NETGEAR did fuck up badly. The image uses a FIT header, while the calculation is done on a legacy header
+    # assumption. 'ih_size' matches 'off_dt_strings' of a fdt_header.
+    # From my observations, this seems to be fixed on newer bootloader versions.
+    # However, we need to be compatible with both.
+
+    # This presents a challenge: FDT_STR might end short of a block boundary, colliding with the rootfs_addr
+    #
+    # Our dirty solution:
+    #  - Move the string_table to match a block_boundary.
+    #  - Update the total file_len to end on 50% of a block boundary.
+    #
+    # This ensures all netgear calculations will be correct, regardless whether they are done based on the
+    # 'off_dt_strings' or 'totalsize' fields of a fdt header.
+
+    new_dt_strings = int((math.floor(file_len / FLASH_BLOCK_SIZE) + 2) * FLASH_BLOCK_SIZE)
+    new_image_len = int(new_dt_strings + (FLASH_BLOCK_SIZE / 2))
+    new_file_len = int(new_dt_strings + FLASH_BLOCK_SIZE - 64)
+    print(f"new_file_len={new_file_len} new_hdr_file_len={new_image_len} new_str_offset={new_dt_strings}")
+
+    # Convert data to bytearray
+    data = bytearray(data)
+
+    # Enlarge byte-array to new size
+    data.extend(bytearray(new_file_len - file_len))
+
+    # Assert that the new and old string-tables are at least 256 bytes apart.
+    # We pad by two blocks, but let's be extra sure.
+    assert new_dt_strings - str_off >= 256
+
+    # Move the string table to the new offset
+    for i in range(0, 256):
+        data[new_dt_strings + i] = data[str_off + i]
+        data[str_off + i] = 0
+
+    # Update the string offset in the header
+    data[0xc] = (new_dt_strings >> 24) & 0xFF
+    data[0xd] = (new_dt_strings >> 16) & 0xFF
+    data[0xe] = (new_dt_strings >> 8) & 0xFF
+    data[0xf] = new_dt_strings & 0xFF
+
+    # Update the file length in the header
+    data[0x4] = (new_image_len >> 24) & 0xFF
+    data[0x5] = (new_image_len >> 16) & 0xFF
+    data[0x6] = (new_image_len >> 8) & 0xFF
+    data[0x7] = new_image_len & 0xFF
+
+    # Write the new file
+    with open(sys.argv[1] + '.new', 'wb') as f:
+        f.write(data)




More information about the lede-commits mailing list