From b0d28d59a35eff349ff6fc814f608bf91dc31453 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 17 Oct 2026 10:02:34 +0300 Subject: [PATCH 1/2] gk7205v200: kernel and rootfs in one NOR partition, split at boot The fixed slots -- kernel 2 MiB at 0x50000, rootfs 5 MiB after it -- each cap their image on their own, so slack in one is lost to the other and every build is a round of trimming. On the gk7205v200 family the kernel now splits one "firmware" partition at boot (mtdsplit, OpenIPC/linux): the FIT at its start, the squashfs at the next 64 KiB, rootfs_data from the squashfs end. Only the sum has to fit the chip. Build: the kernel enables MTD_SPLIT_FIRMWARE, the FIT and squashfs parsers and MTD_ROOTFS_ROOT_DEV; legacy fixed-slot command lines are left unsplit. post-image.sh packs firmware.bin, a crc32+sha1 FIT (zImage and the board DTB, nor-fit.its, described "OpenIPC ") then the squashfs. Packages: the plain openipc.-nor-.tgz stays the fixed-slot uImage and rootfs, gated as before -- every camera in the field upgrades from it with its own sysupgrade, and the openipc.org installer reads it. firmware.bin goes into openipc.-nor--split.tgz, a name no older sysupgrade looks for, gated against the chip less boot, env and a 256 KiB overlay. The space win arrives when the plain package is retired. sysupgrade: a bootloader that boots the layout says so with openipc_nor_fit=1 (OpenIPC/u-boot, CONFIG_OPENIPC_NOR_FIT_SPLIT), and the default URL then names the -split package. firmware.bin goes down through mtdblock at 0x50000, over the "firmware" partition or, from fixed slots, across kernel, rootfs and rootfs_data. The overlay moves with the squashfs end, so its contents travel as a blob ("OPENIPC-OVL1", 12-digit length, gzipped tar) written where the new rootfs_data starts; -n writes a blob of length 0. dd runs with conv=fsync: from fixed slots the mounted root holds "rootfs" open, mtdblock keeps its last block cached until a flush, and a plain sync does not send one -- the blob was lost at the reboot without it (GK7205V300). Both writes are then read back through the MTD character device. Refused before anything is written: a split image on a bootloader without the token, another SoC's image, one that leaves no block for settings, and a separate kernel and rootfs on a camera already on the split layout. /init restores the blob before it mounts rootfs_data: erase the partition, mount it, untar, unmount. Length 0 only erases. Tests: test_sysupgrade.sh covers the split write, the overlay offset, -n, the move from fixed slots, the refusals, the read-back and the default URL; test_init_overlay.sh runs the carry-over itself. Verified on GK7205V200 (8 MB) and GK7205V300 (16 MB) NOR with OpenIPC U-Boot built with CONFIG_OPENIPC_NOR_FIT_SPLIT: FIT hashes checked at boot, /proc/mtd split as computed; upgrades to a bigger and a smaller image, from fixed slots, and split to split carry claim, keys and a marker file, using the sysupgrade inside the image; -n comes up on defaults; the new kernel as a uImage boots the fixed-slot layout unchanged; the plain package on a split camera and a split image on a token-less bootloader are refused with nothing written and no reboot. --- .github/scripts/test_init_overlay.sh | 57 +++++ .github/scripts/test_sysupgrade.sh | 196 +++++++++++++++++ Makefile | 26 +++ .../gk7205v200/gk7202v300.generic.config | 5 + .../gk7205v200/gk7205v200.generic.config | 5 + .../gk7205v200/gk7205v300.generic.config | 5 + .../gk7205v200/gk7605v100.generic.config | 5 + br-ext-chip-goke/board/gk7205v200/nor-fit.its | 53 +++++ .../board/gk7205v200/post-image.sh | 33 +++ .../configs/gk7205v200_lite_defconfig | 1 + .../configs/gk7205v200_ultimate_defconfig | 1 + .../configs/gk7205v300_lite_defconfig | 1 + .../configs/gk7205v300_ultimate_defconfig | 1 + general/overlay/init | 32 +++ general/overlay/usr/sbin/sysupgrade | 208 +++++++++++++++++- 15 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 br-ext-chip-goke/board/gk7205v200/nor-fit.its create mode 100755 br-ext-chip-goke/board/gk7205v200/post-image.sh diff --git a/.github/scripts/test_init_overlay.sh b/.github/scripts/test_init_overlay.sh index 44d8432d27..bd6e0e5b64 100755 --- a/.github/scripts/test_init_overlay.sh +++ b/.github/scripts/test_init_overlay.sh @@ -14,6 +14,8 @@ # uncompressed in its directory entries. Part 1 runs the real function against # synthetic partitions; part 2 pins that the format is still gated on it. # +# Part 3 runs the settings carry-over a split-NOR upgrade leaves behind. +# # Pure shell, no root, no QEMU. set -u @@ -86,6 +88,61 @@ else bad "no tmpfs fallback for an unmountable overlay in $SRC" fi +# --------------------------------------------------------------------------- +# Part 3: settings carried over by a split-NOR sysupgrade. The blob sits where +# rootfs_data now starts: "OPENIPC-OVL1", a 12-digit decimal length, a gzipped +# tar. The function runs for real; mount, umount and flash_eraseall are stubs +# that log, and /overlay and /mnt are moved into the sandbox. +sed -n '/^overlay_carry_in() {/,/^}/p' "$SRC" | + sed -e "s| /overlay| $SB/ovl|g; s|/overlay/|$SB/ovl/|g; s| /mnt| $SB/mnt|g" > "$SB/carry.sh" +[ -s "$SB/carry.sh" ] || { echo "FAIL overlay_carry_in() not found in $SRC"; exit 1; } +mount() { echo "mount $*" >> "$SB/log"; mkdir -p "${!#}"; } +umount() { echo "umount $*" >> "$SB/log"; } +flash_eraseall() { echo "flash_eraseall $*" >> "$SB/log"; rm -rf "$SB/mnt"; } +sync() { :; } +# shellcheck disable=SC1091 +. "$SB/carry.sh" +# blob : the blob at the start of a 192 KiB partition of +# old data, so a length read wrong reads that. +blob() { + head -c 196608 /dev/zero | tr '\000' '\132' > "$1" + { printf 'OPENIPC-OVL1%012d' "$(stat -c %s "${2:-/dev/null}" 2>/dev/null || echo 0)" + [ -n "${2:-}" ] && cat "$2"; } | dd of="$1" conv=notrunc 2>/dev/null +} +carry() { rm -rf "$SB/mnt" "$SB/ovl" "$SB/log"; : > "$SB/log"; OUT=$(overlay_carry_in "$1" "$SB/blk" 2>&1); } + +mkdir -p "$SB/settings/etc"; echo kept > "$SB/settings/etc/marker" +# Past one erase block, so the length decides how much of the partition is read. +head -c 70000 /dev/urandom > "$SB/settings/etc/big" +tar -C "$SB/settings" -czf "$SB/settings.tgz" . +blob "$SB/part" "$SB/settings.tgz" +carry "$SB/part" +if [ "$(cat "$SB/mnt/etc/marker" 2>/dev/null)" = kept ] && cmp -s "$SB/mnt/etc/big" "$SB/settings/etc/big" && + grep -q "flash_eraseall -j $SB/part" "$SB/log" && echo "$OUT" | grep -q "Settings restored"; then + ok "a carried-over blob is restored into a freshly erased rootfs_data" +else + bad "carry-over restore, out='$OUT' log='$(cat "$SB/log")'" +fi +e=$(grep -n flash_eraseall "$SB/log" | cut -d: -f1); m=$(grep -n "mount -t jffs2" "$SB/log" | cut -d: -f1) +[ -n "$e" ] && [ -n "$m" ] && [ "$e" -lt "$m" ] \ + && ok "...the erase comes before the jffs2 mount" \ + || bad "carry-over must erase before it mounts, log='$(cat "$SB/log")'" + +blob "$SB/part" "" +carry "$SB/part" +if grep -q "flash_eraseall -j $SB/part" "$SB/log" && ! grep -q "jffs2" "$SB/log" && + echo "$OUT" | grep -q "Clearing settings"; then + ok "a blob of length 0 (sysupgrade -n) erases and restores nothing" +else + bad "length-0 blob, out='$OUT' log='$(cat "$SB/log")'" +fi + +head -c 196608 /dev/zero | tr '\000' '\377' > "$SB/part" +carry "$SB/part" +[ ! -s "$SB/log" ] && [ -z "$OUT" ] \ + && ok "no blob: rootfs_data is left to the usual mount" \ + || bad "without a blob nothing may be touched, log='$(cat "$SB/log")'" + echo [ "$fail" -eq 0 ] && echo "All init overlay checks passed." || echo "$fail check(s) failed." exit "$fail" diff --git a/.github/scripts/test_sysupgrade.sh b/.github/scripts/test_sysupgrade.sh index b7070763b0..f41fccd13d 100755 --- a/.github/scripts/test_sysupgrade.sh +++ b/.github/scripts/test_sysupgrade.sh @@ -3131,6 +3131,202 @@ fi # --------------------------------------------------------------------------- echo +# --- the split NOR layout (Goke gk7205v200 family) --------------------------- +# One "firmware" partition from 0x50000 to the end of the chip; the kernel +# divides it at boot into the FIT, the squashfs after it and rootfs_data from +# the squashfs end. sysupgrade writes firmware.bin through mtdblock and stages +# the settings at the new overlay start, where /init restores them. The +# mtdblock "devices" are sparse files here, so a test reads back what landed +# where. + +# make_split_fw : a FIT naming +# the SoC, padded to whole 64 KiB blocks, then the squashfs, as post-image.sh +# packs firmware.bin. +make_split_fw() { + local tot=$(($3 * 65536 - 100)) + printf '\xd0\x0d\xfe\xed' > "$1" + printf %b "$(printf '\\x%02x\\x%02x\\x%02x\\x%02x' $(((tot >> 24) & 255)) \ + $(((tot >> 16) & 255)) $(((tot >> 8) & 255)) $((tot & 255)))" >> "$1" + dd if=/dev/zero bs=1 count=56 >> "$1" 2>/dev/null + printf 'description\0OpenIPC %s\0' "$2" >> "$1" + truncate -s $(($3 * 65536)) "$1" + make_squashfs "$SB/tmp/split.sqfs" "$4" + cat "$SB/tmp/split.sqfs" >> "$1"; rm -f "$SB/tmp/split.sqfs" + truncate -s $((($(stat -c %s "$1") + 65535) / 65536 * 65536)) "$1" +} + +# set_split_mtd : /proc/mtd, /sys/class/mtd and the +# mtdblock files for "split" (firmware and the children the kernel made) or +# "fixed" (kernel 2M, rootfs 5M, rootfs_data to the end, plus the overlapping +# firmware alias the fixed-slot command line also carries). The children of +# the split layout sit at the offsets an image of 30 + 40 blocks gives. +set_split_mtd() { + local chip=$2 i=0 spec name off size + rm -rf "$SB/sys/class/mtd" "$SB/dev"; mkdir -p "$SB/sys/class/mtd" "$SB/dev" + printf 'dev: size erasesize name\n' > "$SB/proc/mtd" + if [ "$1" = split ]; then + set -- u-boot:0:0x40000 env:0x40000:0x10000 firmware:0x50000:$((chip - 0x50000)) \ + kernel:0x50000:0x1e0000 rootfs:0x230000:0x280000 \ + rootfs_data:0x4b0000:$((chip - 0x4b0000)) + else + set -- u-boot:0:0x40000 env:0x40000:0x10000 kernel:0x50000:0x200000 \ + rootfs:0x250000:0x500000 firmware:0x50000:0x700000 \ + rootfs_data:0x750000:$((chip - 0x750000)) + fi + for spec in "$@"; do + name=${spec%%:*}; spec=${spec#*:}; off=$((${spec%%:*})); size=$((${spec#*:})) + mkdir -p "$SB/sys/class/mtd/mtd$i" "$SB/sys/class/mtd/mtd${i}ro" + echo "$name" > "$SB/sys/class/mtd/mtd$i/name" + echo "$off" > "$SB/sys/class/mtd/mtd$i/offset" + echo "$size" > "$SB/sys/class/mtd/mtd$i/size" + printf 'mtd%d: %08x 00010000 "%s"\n' "$i" "$size" "$name" >> "$SB/proc/mtd" + truncate -s "$size" "$SB/dev/mtdblock$i" + i=$((i + 1)) + done +} + +# split_env [chip]: a gk7205v200 running from that layout, its overlay +# mounted in the sandbox with a marker in it, on a bootloader that boots the +# split layout. +split_env() { + reset_env + set_platform gk7205v200_lite + STUB_SOC=gk7205v200; STUB_IMG_SOC=gk7205v200; STUB_VENDOR=goke + set_split_mtd "$1" "${2:-0x800000}" + set_cmdline 'mem=32M console=ttyAMA0,115200 panic=20 rootfstype=squashfs root=/dev/mtdblock4 init=/init mtdparts=sfc:256k(u-boot),64k(env),-(firmware) mmz_allocator=gk openipc_nor_fit=1' + rm -rf "$SB/overlay"; mkdir -p "$SB/overlay/etc"; echo kept > "$SB/overlay/etc/marker" + printf 'tmpfs %s/tmp tmpfs rw 0 0\n/dev/mtdblock5 %s/overlay jffs2 rw 0 0\n' "$SB" "$SB" \ + > "$SB/proc/mounts" + # The MTD character devices read the same files: here the chip is what + # mtdblock wrote. + RUN_ENV="MTD_SYS=$SB/sys/class/mtd MTDBLOCK=$SB/dev/mtdblock MTDCHR=$SB/dev/mtdblock" + make_split_fw "$SB/tmp/firmware.bin.gk7205v200" gk7205v200 "${SPLIT_FIT_BLOCKS:-30}" \ + "${SPLIT_SQ_USED:-$((40 * 65536 - 4000))}" + make_archive "$SB/tmp/firmware.bin.gk7205v200" +} +# The bytes at absolute flash offset $2 in mtdblock$1, which starts at $3. +flash_at() { xxd -p -l "$4" -s $(($2 - $3)) "$SB/dev/mtdblock$1"; } +# The settings blob at absolute offset $2 in mtdblock$1 (starting at $3): its +# length field, and the files of the tar it carries. +blob_len() { dd if="$SB/dev/mtdblock$1" bs=1 skip=$(($2 - $3 + 12)) count=12 2>/dev/null; } +blob_files() { + local n=$((10#$(blob_len "$@"))) + dd if="$SB/dev/mtdblock$1" bs=1 skip=$(($2 - $3 + 24)) count="$n" 2>/dev/null | tar tzf - 2>/dev/null +} +FW_HEAD=$(printf 'd00dfeed%08x' $((30 * 65536 - 100))) +OVL=$((0x50000 + 70 * 65536)) + +split_env split +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -eq 0 ] && [ "$(flash_at 2 0x50000 0x50000 8)" = "$FW_HEAD" ] && + [ "$(flash_at 2 $((0x50000 + 30 * 65536)) 0x50000 4)" = 68737173 ] && + [ "$(dd if="$SB/dev/mtdblock2" bs=1 skip=$((OVL - 0x50000)) count=12 2>/dev/null)" = OPENIPC-OVL1 ] && + blob_files 2 "$OVL" 0x50000 | grep -q 'etc/marker' && nothing_wrote && rebooted; then + ok "split layout: firmware.bin at 0x50000, the settings staged where rootfs_data will start" +else + bad "split layout upgrade, rc=$RC head=$(flash_at 2 0x50000 0x50000 8) out='$OUT'" +fi +if printf '%s' "$OUT" | grep -q "Settings staged at $(printf 0x%x "$OVL")"; then + ok "split layout: the overlay offset is the FIT and the squashfs, each to 64 KiB" +else + bad "split layout: expected the settings at $(printf 0x%x "$OVL"), out='$OUT'" +fi + +# -n: a blob of length 0, which /init takes as "erase, restore nothing". +split_env split +run -z -n --archive="$SB/tmp/fw.tgz" +if [ "$RC" -eq 0 ] && [ "$(blob_len 2 "$OVL" 0x50000)" = 000000000000 ]; then + ok "split layout -n: an empty settings blob, so the first boot starts clean" +else + bad "split layout -n, rc=$RC len='$(blob_len 2 "$OVL" 0x50000)' out='$OUT'" +fi + +# From fixed slots: the image runs on from the kernel slot into the rootfs one, +# and the settings land in the old rootfs, where the new rootfs_data starts. +split_env fixed +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -eq 0 ] && [ "$(flash_at 2 0x50000 0x50000 8)" = "$FW_HEAD" ] && + [ "$(flash_at 2 $((0x50000 + 30 * 65536)) 0x50000 4)" = 68737173 ] && + [ "$(dd if="$SB/dev/mtdblock3" bs=1 skip=$((OVL - 0x250000)) count=12 2>/dev/null)" = OPENIPC-OVL1 ] && + blob_files 3 "$OVL" 0x250000 | grep -q 'etc/marker' && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock4")" = 00000000 ] && nothing_wrote; then + ok "fixed slots -> split: written across kernel and rootfs, the firmware alias left alone" +else + bad "fixed -> split, rc=$RC out='$OUT'" +fi + +# The same on a bootloader that cannot boot the split layout: nothing written. +split_env fixed +set_cmdline 'mem=32M console=ttyAMA0,115200 panic=20 rootfstype=squashfs root=/dev/mtdblock3 init=/init mtdparts=sfc:256k(boot),64k(env),2048k(kernel),5120k(rootfs),-(rootfs_data) mmz_allocator=gk' +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "Update the bootloader first" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote && ! rebooted; then + ok "split image on an old bootloader: refused, nothing written, no reboot" +else + bad "split image on an old bootloader must be refused, rc=$RC out='$OUT'" +fi + +# An image that leaves no block for the settings is refused before the write. +SPLIT_SQ_USED=$((93 * 65536)) split_env split +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "room for settings" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote; then + ok "split layout: an image that fills the flash is refused, nothing written" +else + bad "split layout: an oversized image must be refused, rc=$RC out='$OUT'" +fi + +# Another SoC's image: refused before anything is written. +split_env split +make_split_fw "$SB/tmp/firmware.bin.gk7205v200" gk7205v300 30 $((40 * 65536 - 4000)) +make_archive "$SB/tmp/firmware.bin.gk7205v200" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote; then + ok "split layout: another SoC's firmware.bin is refused, nothing written" +else + bad "split layout: wrong SoC must be refused, rc=$RC out='$OUT'" +fi +# What mtdblock accepted is not what reached the chip (its cache was never +# written out): the read back through the MTD character device says so, and the +# run does not report the firmware written. +split_env split +mkdir -p "$SB/stale" +for i in 0 1 2 3 4 5; do truncate -s "$(stat -c %s "$SB/dev/mtdblock$i")" "$SB/stale/mtd$i"; done +RUN_ENV="$RUN_ENV MTDCHR=$SB/stale/mtd" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "does not read back as the image" && + ! printf '%s' "$OUT" | grep -q "Firmware updated"; then + ok "split layout: a write that never reached the chip is caught by the read-back" +else + bad "split layout: a stale chip must fail the write, rc=$RC out='$OUT'" +fi +rm -rf "$SB/stale" + +# A camera on the split layout has no slots for a separate kernel and rootfs. +split_env split +make_uimage "$SB/tmp/uImage.gk7205v200" gk7205v200 +make_rootfs "$SB/tmp/rootfs.squashfs.gk7205v200" +rm -f "$SB/tmp/firmware.bin.gk7205v200" +run -z --kernel="$SB/tmp/uImage.gk7205v200" --rootfs="$SB/tmp/rootfs.squashfs.gk7205v200" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "openipc.gk7205v200-nor-lite-split.tgz" && + nothing_wrote && [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && ! rebooted; then + ok "split layout: a separate kernel and rootfs are refused, the -split archive named" +else + bad "split layout: kernel+rootfs must be refused, rc=$RC out='$OUT'" +fi +unset STUB_SOC STUB_IMG_SOC STUB_VENDOR +rm -rf "$SB/overlay" "$SB/dev" + +# The default URL: the -split archive only for a bootloader that boots it. The +# plain one stays the fixed-slot package every older camera upgrades from. +SPLIT_CMDLINE='mem=32M console=ttyAMA0,115200 root=/dev/mtdblock4 mtdparts=sfc:256k(u-boot),64k(env),-(firmware) openipc_nor_fit=1' +default_url_is "$F/openipc.gk7205v200-nor-lite-split.tgz" \ + eval 'set_platform gk7205v200_lite; STUB_SOC=gk7205v200; set_cmdline "$SPLIT_CMDLINE"' +default_url_is "$F/openipc.gk7205v200-nor-lite.tgz" \ + eval 'set_platform gk7205v200_lite; STUB_SOC=gk7205v200' +default_url_is "$F/openipc.ssc338q-nor-lite.tgz" eval 'set_cmdline "$SPLIT_CMDLINE"' +unset STUB_SOC + echo "=== Part 2: invariants in $SRC ===" # An option named in a user-facing message must exist in the parser. diff --git a/Makefile b/Makefile index 85018b7e48..80dbf7fd2d 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,21 @@ endif @$(call PREPARE_REPACK,firmware.bin,$(shell expr $(subst ",,$(BR2_OPENIPC_FLASH_SIZE)) \* 1024),,,nor) else ifeq ($(BR2_OPENIPC_SOC_FAMILY),"hi3519dv500") @$(call PREPARE_REPACK,firmware.bin,$(shell expr $(subst ",,$(BR2_OPENIPC_FLASH_SIZE)) \* 1024),,,nor) +else ifneq ($(and $(filter "gk7205v200",$(BR2_OPENIPC_SOC_FAMILY)),$(wildcard $(TARGET)/images/firmware.bin)),) +# gk7205v200-family NOR ships twice. The plain package is the fixed-slot one, +# uImage and rootfs, unchanged: every camera in the field upgrades from it with +# its own sysupgrade, and the openipc.org installer reads it. The -split package +# is firmware.bin (board/gk7205v200/post-image.sh), FIT and squashfs back to back +# in one "firmware" partition the kernel splits at boot, for a bootloader that +# boots that layout; only its whole is bounded: the chip, less boot and env +# (320K) and the smallest overlay worth keeping (256K). +ifeq ($(BR2_OPENIPC_FLASH_SIZE),"8") + @$(call PREPARE_REPACK,uImage,2048,rootfs.squashfs,5120,nor) +else + @$(call PREPARE_REPACK,uImage,2048,rootfs.squashfs,8192,nor) +endif + @$(call CHECK_SIZE,firmware.bin,$(shell expr $(subst ",,$(BR2_OPENIPC_FLASH_SIZE)) \* 1024 - 576)) + @$(call REPACK_NOR_SPLIT) else ifneq ($(wildcard $(TARGET)/images/firmware.bin),) @$(call PREPARE_REPACK,firmware.bin,8192,,,nor) else @@ -341,6 +356,17 @@ define REPACK_FIRMWARE rm -f $(TARGET)/images/*.md5sum endef +# The split NOR package: firmware.bin alone, under its own name so a sysupgrade +# that predates the layout never sees it. Copies, as REPACK_NAND_FIT does. +define REPACK_NOR_SPLIT + cd $(TARGET)/images && cp -f firmware.bin firmware.bin.$(BR2_OPENIPC_SOC_MODEL) && \ + md5sum firmware.bin.$(BR2_OPENIPC_SOC_MODEL) > firmware.bin.$(BR2_OPENIPC_SOC_MODEL).md5sum + # Checksums first, as in REPACK_FIRMWARE. + cd $(TARGET)/images && tar -czf openipc.$(BR2_OPENIPC_SOC_MODEL)-nor-$(BR2_OPENIPC_VARIANT)-split.tgz \ + firmware.bin.$(BR2_OPENIPC_SOC_MODEL).md5sum firmware.bin.$(BR2_OPENIPC_SOC_MODEL) + rm -f $(TARGET)/images/*.md5sum +endef + # The FIT NAND package: three images, so not REPACK_FIRMWARE's two. Copies # rather than renames -- rootfs.ubifs stays where buildroot left it, and the # NOR package built from the same tree does not share any of these names. diff --git a/br-ext-chip-goke/board/gk7205v200/gk7202v300.generic.config b/br-ext-chip-goke/board/gk7205v200/gk7202v300.generic.config index 6bcbfac58d..5a5fcf1823 100644 --- a/br-ext-chip-goke/board/gk7205v200/gk7202v300.generic.config +++ b/br-ext-chip-goke/board/gk7205v200/gk7202v300.generic.config @@ -899,6 +899,11 @@ CONFIG_MTD=y CONFIG_MTD_CMDLINE_PARTS=y # CONFIG_MTD_AFS_PARTS is not set CONFIG_MTD_OF_PARTS=y +CONFIG_MTD_ROOTFS_ROOT_DEV=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" +CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y +CONFIG_MTD_SPLIT_FIT_FW=y # CONFIG_MTD_AR7_PARTS is not set # diff --git a/br-ext-chip-goke/board/gk7205v200/gk7205v200.generic.config b/br-ext-chip-goke/board/gk7205v200/gk7205v200.generic.config index 271efd1dac..5486dd51a2 100644 --- a/br-ext-chip-goke/board/gk7205v200/gk7205v200.generic.config +++ b/br-ext-chip-goke/board/gk7205v200/gk7205v200.generic.config @@ -898,6 +898,11 @@ CONFIG_MTD=y CONFIG_MTD_CMDLINE_PARTS=y # CONFIG_MTD_AFS_PARTS is not set CONFIG_MTD_OF_PARTS=y +CONFIG_MTD_ROOTFS_ROOT_DEV=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" +CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y +CONFIG_MTD_SPLIT_FIT_FW=y # CONFIG_MTD_AR7_PARTS is not set # diff --git a/br-ext-chip-goke/board/gk7205v200/gk7205v300.generic.config b/br-ext-chip-goke/board/gk7205v200/gk7205v300.generic.config index 37ea3417cb..dcc2f635ef 100644 --- a/br-ext-chip-goke/board/gk7205v200/gk7205v300.generic.config +++ b/br-ext-chip-goke/board/gk7205v200/gk7205v300.generic.config @@ -892,6 +892,11 @@ CONFIG_MTD=y CONFIG_MTD_CMDLINE_PARTS=y # CONFIG_MTD_AFS_PARTS is not set CONFIG_MTD_OF_PARTS=y +CONFIG_MTD_ROOTFS_ROOT_DEV=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" +CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y +CONFIG_MTD_SPLIT_FIT_FW=y # CONFIG_MTD_AR7_PARTS is not set # diff --git a/br-ext-chip-goke/board/gk7205v200/gk7605v100.generic.config b/br-ext-chip-goke/board/gk7205v200/gk7605v100.generic.config index 192c4dadc6..57925d201c 100644 --- a/br-ext-chip-goke/board/gk7205v200/gk7605v100.generic.config +++ b/br-ext-chip-goke/board/gk7205v200/gk7605v100.generic.config @@ -794,6 +794,11 @@ CONFIG_MTD=y CONFIG_MTD_CMDLINE_PARTS=y # CONFIG_MTD_AFS_PARTS is not set CONFIG_MTD_OF_PARTS=y +CONFIG_MTD_ROOTFS_ROOT_DEV=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" +CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y +CONFIG_MTD_SPLIT_FIT_FW=y # CONFIG_MTD_AR7_PARTS is not set # diff --git a/br-ext-chip-goke/board/gk7205v200/nor-fit.its b/br-ext-chip-goke/board/gk7205v200/nor-fit.its new file mode 100644 index 0000000000..cc428b9a81 --- /dev/null +++ b/br-ext-chip-goke/board/gk7205v200/nor-fit.its @@ -0,0 +1,53 @@ +/dts-v1/; + +/* + * Kernel of the gk7205v200-family NOR image: the zImage and its DTB, each + * hashed so U-Boot refuses a kernel the flash has corrupted instead of booting + * it. post-image.sh builds it from the kernel tree and packs firmware.bin: this + * FIT, then the squashfs at the next 64 KiB boundary. The kernel splits the + * one "firmware" partition U-Boot passes into kernel, rootfs and rootfs_data + * (CONFIG_MTD_SPLIT_FIT_FW, CONFIG_MTD_SPLIT_SQUASHFS_ROOT), so neither image + * has a slot of its own to fit. + * + * No fdt load address: U-Boot relocates the DTB under bootm_size, inside the + * kernel's lowmem. + */ +/ { + /* "OpenIPC ": sysupgrade's SoC check reads it (fit_soc). */ + description = "OpenIPC @SOC@"; + #address-cells = <1>; + + images { + kernel { + description = "Linux"; + data = /incbin/("zImage"); + type = "kernel"; + arch = "arm"; + os = "linux"; + compression = "none"; + load = <0x40008000>; + entry = <0x40008000>; + hash-1 { algo = "crc32"; }; + hash-2 { algo = "sha1"; }; + }; + + fdt { + description = "@DTB@"; + data = /incbin/("@DTB@"); + type = "flat_dt"; + arch = "arm"; + compression = "none"; + hash-1 { algo = "crc32"; }; + hash-2 { algo = "sha1"; }; + }; + }; + + configurations { + default = "conf-1"; + conf-1 { + description = "Linux + @DTB@"; + kernel = "kernel"; + fdt = "fdt"; + }; + }; +}; diff --git a/br-ext-chip-goke/board/gk7205v200/post-image.sh b/br-ext-chip-goke/board/gk7205v200/post-image.sh new file mode 100755 index 0000000000..a4e2e30d8d --- /dev/null +++ b/br-ext-chip-goke/board/gk7205v200/post-image.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# gk7205v200-family NOR: firmware.bin = a hashed FIT (zImage + DTB, built from +# nor-fit.its) followed by the squashfs at the next 64 KiB boundary, padded to +# 64 KiB. Written as one image at 0x50000; the kernel splits that "firmware" +# partition into kernel, rootfs and rootfs_data at boot (mtdsplit), so only the +# whole has to fit the chip. The zImage and DTB come from the kernel tree: +# BINARIES_DIR only gets the uImage, with its DTB appended. +set -e + +BINARIES_DIR=$1 +BOARD_DIR="$(dirname "$0")" +ERASEBLOCK_SIZE=$((64 * 1024)) + +SOC=$(sed -n 's/^BR2_OPENIPC_SOC_MODEL="\(.*\)"/\1/p' "${BR2_CONFIG}") +KBOOT=$(ls -d "${BUILD_DIR}"/linux-*/arch/arm/boot 2>/dev/null | grep -v headers | head -1) +FIT_DIR="${BINARIES_DIR}/nor-fit" + +rm -rf "${FIT_DIR}" && mkdir -p "${FIT_DIR}" +sed -e "s/@SOC@/${SOC}/" -e "s/@DTB@/${SOC}-demb.dtb/g" \ + "${BOARD_DIR}/nor-fit.its" > "${FIT_DIR}/nor-fit.its" +cp "${KBOOT}/zImage" "${FIT_DIR}/" +cp "${KBOOT}/dts/${SOC}-demb.dtb" "${FIT_DIR}/" +"${HOST_DIR}/bin/mkimage" -f "${FIT_DIR}/nor-fit.its" "${BINARIES_DIR}/fitImage" +rm -rf "${FIT_DIR}" + +FIT_SIZE=$(stat -c%s "${BINARIES_DIR}/fitImage") +OFFSET_BLOCKS=$(( (FIT_SIZE + ERASEBLOCK_SIZE - 1) / ERASEBLOCK_SIZE )) +cp "${BINARIES_DIR}/fitImage" "${BINARIES_DIR}/firmware.bin" +dd if="${BINARIES_DIR}/rootfs.squashfs" of="${BINARIES_DIR}/firmware.bin" \ + bs=${ERASEBLOCK_SIZE} seek=${OFFSET_BLOCKS} conv=notrunc status=none +truncate -s %${ERASEBLOCK_SIZE} "${BINARIES_DIR}/firmware.bin" +echo "firmware.bin: FIT $((FIT_SIZE / 1024))K + squashfs $(( $(stat -c%s "${BINARIES_DIR}/rootfs.squashfs") / 1024 ))K = $(( $(stat -c%s "${BINARIES_DIR}/firmware.bin") / 1024 ))K" diff --git a/br-ext-chip-goke/configs/gk7205v200_lite_defconfig b/br-ext-chip-goke/configs/gk7205v200_lite_defconfig index e6fc34a993..bcc3a3989f 100644 --- a/br-ext-chip-goke/configs/gk7205v200_lite_defconfig +++ b/br-ext-chip-goke/configs/gk7205v200_lite_defconfig @@ -37,6 +37,7 @@ BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y BR2_TARGET_ROOTFS_CPIO=y BR2_TARGET_ROOTFS_SQUASHFS=y BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y +BR2_ROOTFS_POST_IMAGE_SCRIPT="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/post-image.sh" # OpenIPC BR2_OPENIPC_SOC_VENDOR="goke" diff --git a/br-ext-chip-goke/configs/gk7205v200_ultimate_defconfig b/br-ext-chip-goke/configs/gk7205v200_ultimate_defconfig index d1713cd705..4f7e4d7b73 100644 --- a/br-ext-chip-goke/configs/gk7205v200_ultimate_defconfig +++ b/br-ext-chip-goke/configs/gk7205v200_ultimate_defconfig @@ -39,6 +39,7 @@ BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y BR2_TARGET_ROOTFS_CPIO=y BR2_TARGET_ROOTFS_SQUASHFS=y BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y +BR2_ROOTFS_POST_IMAGE_SCRIPT="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/post-image.sh" # OpenIPC BR2_OPENIPC_SOC_VENDOR="goke" diff --git a/br-ext-chip-goke/configs/gk7205v300_lite_defconfig b/br-ext-chip-goke/configs/gk7205v300_lite_defconfig index f36516e553..9e036b71af 100644 --- a/br-ext-chip-goke/configs/gk7205v300_lite_defconfig +++ b/br-ext-chip-goke/configs/gk7205v300_lite_defconfig @@ -37,6 +37,7 @@ BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y BR2_TARGET_ROOTFS_CPIO=y BR2_TARGET_ROOTFS_SQUASHFS=y BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y +BR2_ROOTFS_POST_IMAGE_SCRIPT="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/post-image.sh" # OpenIPC BR2_OPENIPC_SOC_VENDOR="goke" diff --git a/br-ext-chip-goke/configs/gk7205v300_ultimate_defconfig b/br-ext-chip-goke/configs/gk7205v300_ultimate_defconfig index 67e3663d5f..164fdb5c7e 100644 --- a/br-ext-chip-goke/configs/gk7205v300_ultimate_defconfig +++ b/br-ext-chip-goke/configs/gk7205v300_ultimate_defconfig @@ -39,6 +39,7 @@ BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y BR2_TARGET_ROOTFS_CPIO=y BR2_TARGET_ROOTFS_SQUASHFS=y BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y +BR2_ROOTFS_POST_IMAGE_SCRIPT="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/post-image.sh" # OpenIPC BR2_OPENIPC_SOC_VENDOR="goke" diff --git a/general/overlay/init b/general/overlay/init index a42d325782..50a7925964 100755 --- a/general/overlay/init +++ b/general/overlay/init @@ -88,6 +88,37 @@ EOF # feature node") -- and the branch below then erased it. That took every # setting and the root password with it on an XM510 moved from a kernel with # xattrs to one without (#2606 turned them on there). Reads only. +# Settings an upgrade moved. On the split NOR layout (one "firmware" partition +# the kernel divides at boot) an upgrade that changes the images moves where +# rootfs_data starts, so sysupgrade stages the settings at the new start as +# "OPENIPC-OVL1", a 12-digit length and a gzipped tar. Restore them into a +# freshly erased overlay before it is mounted. Until the erase, a power cut +# leaves the blob in place and the next boot does this again. +overlay_carry_in() { + local chr=$1 blk=$2 len blocks + [ "$(dd if="$chr" bs=12 count=1 2>/dev/null)" = "OPENIPC-OVL1" ] || return 0 + # Decimal with leading zeros, which $(( )) would read as octal. + len=$(dd if="$chr" bs=24 count=1 2>/dev/null | tail -c 12 | sed 's/^0*//') + len=${len:-0} + blocks=$(((24 + len + 65535) / 65536)) + if [ "$len" -eq 0 ]; then + # sysupgrade -n: a clean overlay, nothing to restore. + echo "Clearing settings after an upgrade..." + flash_eraseall -j "$chr" + return 0 + fi + echo "Restoring settings carried over by an upgrade..." + mount -t tmpfs tmpfs /overlay || return 0 + dd if="$chr" bs=65536 count=$blocks 2>/dev/null | tail -c +25 | head -c "$len" > /overlay/settings.tgz + flash_eraseall -j "$chr" + if mount -t jffs2 "$blk" /mnt; then + gunzip -c /overlay/settings.tgz | tar -x -C /mnt && echo "Settings restored." + sync + umount /mnt + fi + umount /overlay +} + OVERLAY_MARK=.openipc-overlay overlay_is_ours() { grep -qs -e "$OVERLAY_MARK" -e 'eula-accepted' -e 'majestic.yaml' \ @@ -125,6 +156,7 @@ if $is_flash_root; then # Before anything touches rootfs_data: mounting jffs2 on a mirror is # already a write, since the scan queues every block it cannot parse # for erasure. + grep -q 'nand' /proc/cmdline || overlay_carry_in /dev/$mtdchrdev /dev/$mtdblkdev if ! grep -q 'nand' /proc/cmdline && flash_aliased; then echo "Flash is smaller than the kernel thinks: leaving it alone, settings will not persist." if ! mount -t tmpfs tmpfs /overlay; then diff --git a/general/overlay/usr/sbin/sysupgrade b/general/overlay/usr/sbin/sysupgrade index e803a3405a..088f8b2c50 100755 --- a/general/overlay/usr/sbin/sysupgrade +++ b/general/overlay/usr/sbin/sysupgrade @@ -57,6 +57,11 @@ WDOG_RELEASE=/tmp/.sysupgrade-wdog-release # boot. Overridable so the test harness can point both at its sandbox. UBI_SYS=${UBI_SYS:-/sys/class/ubi} INITTAB=${INITTAB:-/etc/inittab} +# Where MTD publishes its partitions, and the block devices the split NOR +# layout is written through. Overridable for the same reason. +MTD_SYS=${MTD_SYS:-/sys/class/mtd} +MTDBLOCK=${MTDBLOCK:-/dev/mtdblock} +MTDCHR=${MTDCHR:-/dev/mtd} # Where a handed-off flash phase writes its log, and the port that serves it. # The hand-off kills dropbear and majestic with everything else, so this is the # only way to watch a NAND flash without a serial cable -- see follow_start. @@ -340,6 +345,17 @@ fit_split_blocks() { check_combined_fits() { local x=$1 [ -f "$x" ] || return 0 + if nor_split_image "$x"; then + local ovl=$(nor_split_overlay_at "$x") end=$(nor_split_end) + [ $((ovl + 65536)) -le "$end" ] || + die "$x does not fit the flash with room for settings: it ends at $(printf 0x%x "$ovl"), the flash at $(printf 0x%x "$end"). Nothing was written." + return 0 + fi + # A split-layout image on a camera of that family whose bootloader does not + # boot the layout: the FIT would land in a kernel slot U-Boot cannot boot. + if nor_split_family && ! nor_split_boot && nor_split_overlay_at "$x" >/dev/null; then + die "This firmware is for the split NOR layout, which this camera's bootloader does not boot. Update the bootloader first (https://openipc.org). Nothing was written." + fi local kdev=$(get_device "kernel") local rdev=$(get_device "rootfs") if [ "$kdev" = "/dev/" ] || [ "$rdev" = "/dev/" ]; then @@ -379,6 +395,13 @@ preflight_image_sizes() { free_staged_archive return 0 fi + # A camera on the split layout has no kernel or rootfs slot to write: its + # "kernel" and "rootfs" are what the running kernel carved out of the + # current image, and a uImage or squashfs written into them would sit where + # no boot looks for it. + if { [ "1" = "$update_kernel" ] || [ "1" = "$update_rootfs" ]; } && nor_split_running; then + die "This camera runs the split NOR layout and upgrades from firmware.bin (openipc.$model-nor-$(get_system_build)-split.tgz), not a separate kernel and rootfs. Nothing was written." + fi # Present at all, first. An archive built for another camera unpacks # fine and holds nothing named for this $model; the image checks found # that only after the pivot, where every failure reboots -- a camera @@ -756,6 +779,179 @@ do_update_rootfs() { echo_c 32 "RootFS updated to ${rootfs_version:-unknown}" } +# --- Split NOR layout (gk7205v200 family) ----------------------------------- +# +# firmware.bin -- a hashed FIT, then the squashfs at the next 64 KiB boundary -- +# is written whole at 0x50000. U-Boot passes Linux one "firmware" partition +# from there to the end of the chip, and the kernel divides it at boot +# (mtdsplit): "kernel" is the FIT, "rootfs" the squashfs, "rootfs_data" the rest +# from the squashfs end rounded up to 64 KiB. Nothing has a slot of its own, so +# an upgrade that changes either image moves where the overlay starts. +# +# The settings cross that boundary as a blob written at the new overlay start: +# "OPENIPC-OVL1", the length in 12 decimal digits, then a gzipped tar of the +# overlay. /init finds it before it mounts rootfs_data, restores it into a +# freshly erased overlay, and only then does the camera have its settings back. +# A power cut before that leaves the blob where it is, and the next boot does it +# again. + +# The bootloader boots this layout: it says so on the kernel command line. +nor_split_boot() { + grep -q 'openipc_nor_fit=1' /proc/cmdline +} + +# "mtdN offset size" for the partitions that together cover 0x50000 to the end +# of the chip without overlapping: the "firmware" partition alone when it +# reaches the end (the split layout), else kernel, rootfs and rootfs_data back to +# back (fixed slots, on the way to the split layout). From sysfs, which the +# ramfs pivot carries along; nothing printed means neither shape is there. +nor_split_devs() { + local d n o z end=0 chip=0 + for d in "$MTD_SYS"/mtd*; do + case "$d" in *ro) continue ;; esac + [ -r "$d/offset" ] || continue + o=$(cat "$d/offset") z=$(cat "$d/size") + [ $((o + z)) -gt "$chip" ] && chip=$((o + z)) + done + [ "$chip" -gt 0 ] || return 1 + for n in firmware kernel rootfs rootfs_data; do + for d in "$MTD_SYS"/mtd*; do + case "$d" in *ro) continue ;; esac + [ "$(cat "$d/name" 2>/dev/null)" = "$n" ] || continue + eval "${n}_dev=${d##*/} ${n}_off=$(cat "$d/offset") ${n}_size=$(cat "$d/size")" + break + done + done + if [ -n "$firmware_dev" ] && [ "$firmware_off" -eq $((0x50000)) ] && + [ $((firmware_off + firmware_size)) -eq "$chip" ]; then + echo "$firmware_dev $firmware_off $firmware_size" + return 0 + fi + [ -n "$kernel_dev" ] && [ -n "$rootfs_dev" ] && [ -n "$rootfs_data_dev" ] || return 1 + [ "$kernel_off" -eq $((0x50000)) ] && + [ "$rootfs_off" -eq $((kernel_off + kernel_size)) ] && + [ "$rootfs_data_off" -eq $((rootfs_off + rootfs_size)) ] && + [ $((rootfs_data_off + rootfs_data_size)) -eq "$chip" ] || return 1 + echo "$kernel_dev $kernel_off $kernel_size" + echo "$rootfs_dev $rootfs_off $rootfs_size" + echo "$rootfs_data_dev $rootfs_data_off $rootfs_data_size" +} + +# Where the kernel will start rootfs_data for image $1, as an absolute flash +# offset: the squashfs at the FIT rounded up to 64 KiB, plus its bytes_used +# (superblock offset 40, little-endian) rounded up to 64 KiB. +nor_split_overlay_at() { + local blocks le used + blocks=$(fit_split_blocks "$1") || return 1 + [ "$(xxd -p -l 4 -s $((blocks * 65536)) "$1" 2>&3)" = "68737173" ] || return 1 + le=$(xxd -p -l 4 -s $((blocks * 65536 + 40)) "$1" 2>&3 | + sed 's/\(..\)\(..\)\(..\)\(..\)/\4\3\2\1/') + used=$((0x${le:-0})) + [ "$used" -gt 0 ] || return 1 + echo $((0x50000 + blocks * 65536 + (used + 65535) / 65536 * 65536)) +} + +# The end of the area nor_split_devs covers. +nor_split_end() { + nor_split_devs | awk 'END { if (NR) print $2 + $3 }' +} + +# Write $1 at absolute offset $2 through the partitions nor_split_devs lists, +# 64 KiB blocks through mtdblock (which erases each block it writes). +# +# mtdblock writes into a cache of one erase block and puts it on the chip when a +# write lands in another block, on a flush, or on the device's last close. On +# the way from fixed slots the old root squashfs still holds "rootfs" open, so +# dd's close is not the last, and a plain sync does not flush it: the last block +# written -- the settings blob -- stayed in RAM and was gone at the reboot +# (GK7205V300). dd's fsync sends the flush. The result is then read back past +# mtdblock, through the MTD character device, so a write that did not reach the +# chip fails here instead of being reported done. +nor_write_at() { + local f=$1 at=$2 len dev off size from to + len=$(stat -c %s "$f") || return 1 + nor_split_devs | while read -r dev off size; do + from=$((at > off ? at : off)) + to=$((at + len < off + size ? at + len : off + size)) + [ "$from" -lt "$to" ] || continue + dd if="$f" of="$MTDBLOCK${dev#mtd}" bs=65536 \ + skip=$(((from - at) / 65536)) seek=$(((from - off) / 65536)) \ + count=$(((to - from + 65535) / 65536)) conv=notrunc,fsync 2>&3 || exit 1 + done || return 1 + sync + [ "$(nor_read_at "$at" "$len" | md5sum | cut -d' ' -f1)" = "$(md5sum < "$f" | cut -d' ' -f1)" ] +} + +# $2 bytes from absolute offset $1, read through the MTD character devices: +# what is on the chip, past any cache mtdblock keeps. +nor_read_at() { + local at=$1 left=$2 dev off size n + nor_split_devs | while read -r dev off size; do + [ "$left" -gt 0 ] || break + [ "$at" -ge "$off" ] && [ "$at" -lt $((off + size)) ] || continue + n=$((off + size - at)); [ "$n" -gt "$left" ] && n=$left + dd if="$MTDCHR${dev#mtd}" bs=65536 skip=$(((at - off) / 65536)) \ + count=$((((at - off) % 65536 + n + 65535) / 65536)) 2>&3 | + tail -c +$(((at - off) % 65536 + 1)) | head -c "$n" + at=$((at + n)); left=$((left - n)) + done +} + +# SoCs whose NOR firmware is built for the split layout (board/gk7205v200). +nor_split_family() { + case "${soc:-$model}" in + gk7205v200|gk7205v210|gk7205v300|gk7202v300|gk7605v100) return 0 ;; + esac + return 1 +} + +# Running from the split layout: one "firmware" partition from 0x50000 to the +# end of the chip, the kernel's own partitions inside it. +nor_split_running() { + nor_split_boot && [ "$(nor_split_devs | wc -l)" -eq 1 ] +} + +# The image is one for the split layout and the bootloader boots it. +nor_split_image() { + nor_split_boot && [ -n "$(nor_split_devs)" ] && nor_split_overlay_at "$1" >/dev/null +} + +do_update_firmware_split() { + local x=$1 ovl end blob=/tmp/overlay.blob tgz=/tmp/overlay.tgz mp + echo "Split NOR layout: firmware.bin at 0x50000, the kernel divides it at boot" + [ "$(fit_soc "$x")" = "$model" ] || + die "$x is built for $(fit_soc "$x"), this camera is a $model. Nothing was written." + ovl=$(nor_split_overlay_at "$x") || die "$x is not a FIT followed by a squashfs. Nothing was written." + end=$(nor_split_end) + [ -n "$end" ] || die "The flash layout is neither the split one nor fixed slots this upgrade can turn into it. Nothing was written." + rm -f "$blob" "$tgz" + quiesce_overlay + mp=$(awk -v d="$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" '$1 == d { print $2; exit }' /proc/mounts) + if [ "1" != "$clear_overlay" ] && [ -n "$mp" ]; then + tar -C "$mp" -cf - . 2>&3 | gzip > "$tgz" || + die "Could not copy the settings into RAM. Nothing was written; -n upgrades without them." + else + # Length 0: the first boot erases the overlay and restores nothing. + : > "$tgz" + fi + printf 'OPENIPC-OVL1%012d' "$(stat -c %s "$tgz")" > "$blob" + cat "$tgz" >> "$blob" && rm -f "$tgz" + # To a whole 64 KiB block (busybox truncate has no %SIZE). + local bsz=$(stat -c %s "$blob") + head -c $(((65536 - bsz % 65536) % 65536)) /dev/zero >> "$blob" + [ $((ovl + $(stat -c %s "$blob"))) -le "$end" ] || + die "The new firmware and the settings do not fit the flash ($(( (ovl + $(stat -c %s "$blob") - 0x50000) / 1024 )) KB of $(( (end - 0x50000) / 1024 )) KB). Nothing was written; -n upgrades without the settings." + mark_live_flash_dirty + echo "Writing $x at 0x50000" + nor_write_at "$x" $((0x50000)) || die "Writing $x failed: the flash does not read back as the image." + nor_write_at "$blob" "$ovl" || + die "Writing the settings at $(printf 0x%x "$ovl") failed; the camera starts from defaults." + rm -f "$blob" + overlay_recreated=1 + echo "Settings staged at $(printf 0x%x "$ovl") for the first boot" + echo_c 32 "Firmware updated" +} + do_update_firmware() { # A combined image bundles the FIT kernel (DTB magic d00dfeed) and the # rootfs squashfs in one blob — the rootfs packed right after the FIT at a @@ -773,6 +969,11 @@ do_update_firmware() { echo "Update firmware from $x" [ ! -f "$x" ] && die "File $x not found" + if nor_split_image "$x"; then + do_update_firmware_split "$x" + return 0 + fi + if [ "$(get_device "kernel")" != "/dev/" ] && [ "$(get_device "rootfs")" != "/dev/" ]; then # Split at the FIT boundary, then hand each slice to the split flow. # Assigned on its own line: `local v=$(cmd)` reports local's status, not @@ -2759,7 +2960,12 @@ default_upgrade_url() { esac osr=$(get_system_build) case "$osr" in lite|ultimate|neo) repo=firmware ;; esac - echo "https://github.com/OpenIPC/$repo/releases/download/latest/openipc.$model-$(pkg_flash)-$osr.tgz" + # A bootloader that boots the split NOR layout takes firmware.bin, from its + # own archive: the plain one keeps the fixed-slot images every older + # camera and installer expects. + local split= + [ "$repo" = firmware ] && [ "$(pkg_flash)" = nor ] && nor_split_family && nor_split_boot && split=-split + echo "https://github.com/OpenIPC/$repo/releases/download/latest/openipc.$model-$(pkg_flash)-$osr$split.tgz" } is_device_platform() { From 351e6d1a6767d9545759e6287d2dbc259846f133 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:02:02 +0300 Subject: [PATCH 2/2] gk7205v200: split NOR, refuse earlier and restore more carefully From review of the split-layout upgrade: sysupgrade - The FIT's SoC, bytes past the squashfs, and settings too big for what the image leaves are now refused before the RAM pivot, where a refusal still costs no reboot. The writer keeps its own checks. - A camera on the split layout takes only a split firmware.bin: any other combined image used to fall through to a whole-partition write over "firmware". - --channel/--build resolve the plain package from the manifest; a camera whose bootloader boots the layout now fetches the -split one beside it, as the default URL already did. The manifest itself keeps listing only the plain package, which older cameras install from. /init - The carry-over runs only after flash_aliased says the chip is what the kernel thinks, and is skipped on an aliased one. - Nothing is erased until the archive is read whole and lists clean. A short read or no RAM keeps the blob for the next boot and boots on a RAM overlay; a damaged archive is reported and left to the usual mount. Every failure goes to the console and /dev/kmsg. CI: the -split package made `find openipc*nor*` return two paths, so the NOR upload step skipped; the plain package is picked by name. Verified on GK7205V200 and GK7205V300 with the images built from this tree, using the sysupgrade inside them: fixed slots to split and split to split carry settings ("Settings restored."), -n comes up on defaults ("Clearing settings after an upgrade..."). --- .github/scripts/test_init_overlay.sh | 57 ++++++++++++++++++++++-- .github/scripts/test_sysupgrade.sh | 65 ++++++++++++++++++++++++++-- .github/workflows/build-one.yml | 7 ++- .github/workflows/build.yml | 7 ++- general/overlay/init | 52 +++++++++++++++++++--- general/overlay/usr/sbin/sysupgrade | 57 +++++++++++++++++++++--- 6 files changed, 224 insertions(+), 21 deletions(-) diff --git a/.github/scripts/test_init_overlay.sh b/.github/scripts/test_init_overlay.sh index bd6e0e5b64..fd71055217 100755 --- a/.github/scripts/test_init_overlay.sh +++ b/.github/scripts/test_init_overlay.sh @@ -93,10 +93,14 @@ fi # rootfs_data now starts: "OPENIPC-OVL1", a 12-digit decimal length, a gzipped # tar. The function runs for real; mount, umount and flash_eraseall are stubs # that log, and /overlay and /mnt are moved into the sandbox. -sed -n '/^overlay_carry_in() {/,/^}/p' "$SRC" | +sed -n '/^overlay_carry_in() {/,/^}/p; /^carry_report() {/,/^}/p' "$SRC" | sed -e "s| /overlay| $SB/ovl|g; s|/overlay/|$SB/ovl/|g; s| /mnt| $SB/mnt|g" > "$SB/carry.sh" [ -s "$SB/carry.sh" ] || { echo "FAIL overlay_carry_in() not found in $SRC"; exit 1; } -mount() { echo "mount $*" >> "$SB/log"; mkdir -p "${!#}"; } +mount() { + echo "mount $*" >> "$SB/log" + [ "$2" = tmpfs ] && [ -n "${TMPFS_FAILS:-}" ] && return 1 + mkdir -p "${!#}" +} umount() { echo "umount $*" >> "$SB/log"; } flash_eraseall() { echo "flash_eraseall $*" >> "$SB/log"; rm -rf "$SB/mnt"; } sync() { :; } @@ -109,7 +113,10 @@ blob() { { printf 'OPENIPC-OVL1%012d' "$(stat -c %s "${2:-/dev/null}" 2>/dev/null || echo 0)" [ -n "${2:-}" ] && cat "$2"; } | dd of="$1" conv=notrunc 2>/dev/null } -carry() { rm -rf "$SB/mnt" "$SB/ovl" "$SB/log"; : > "$SB/log"; OUT=$(overlay_carry_in "$1" "$SB/blk" 2>&1); } +carry() { + rm -rf "$SB/mnt" "$SB/ovl" "$SB/log"; : > "$SB/log" + OUT=$(overlay_carry_in "$1" "$SB/blk" 2>&1); RC=$? +} mkdir -p "$SB/settings/etc"; echo kept > "$SB/settings/etc/marker" # Past one erase block, so the length decides how much of the partition is read. @@ -137,12 +144,56 @@ else bad "length-0 blob, out='$OUT' log='$(cat "$SB/log")'" fi +# A damaged archive: reported, and nothing erased -- the usual mount decides. +blob "$SB/part" "$SB/settings.tgz" +printf 'XXXX' | dd of="$SB/part" bs=1 seek=40 conv=notrunc 2>/dev/null +carry "$SB/part" +if [ "$RC" -eq 0 ] && ! grep -q flash_eraseall "$SB/log" && echo "$OUT" | grep -q "damaged"; then + ok "a damaged carry-over is reported and nothing is erased" +else + bad "damaged carry-over, rc=$RC out='$OUT' log='$(cat "$SB/log")'" +fi + +# A length past what the partition holds: kept for the next boot (rc 1). +blob "$SB/part" "$SB/settings.tgz" +printf 'OPENIPC-OVL1%012d' 999999 | dd of="$SB/part" conv=notrunc 2>/dev/null +carry "$SB/part" +if [ "$RC" -eq 1 ] && ! grep -q flash_eraseall "$SB/log"; then + ok "a short read keeps the blob for the next boot, nothing erased" +else + bad "short read, rc=$RC out='$OUT' log='$(cat "$SB/log")'" +fi + +# No RAM for the archive: kept for the next boot too. +blob "$SB/part" "$SB/settings.tgz" +TMPFS_FAILS=1 carry "$SB/part" +if [ "$RC" -eq 1 ] && ! grep -q flash_eraseall "$SB/log"; then + ok "no tmpfs: the blob is kept for the next boot, nothing erased" +else + bad "tmpfs failure, rc=$RC out='$OUT' log='$(cat "$SB/log")'" +fi + head -c 196608 /dev/zero | tr '\000' '\377' > "$SB/part" carry "$SB/part" [ ! -s "$SB/log" ] && [ -z "$OUT" ] \ && ok "no blob: rootfs_data is left to the usual mount" \ || bad "without a blob nothing may be touched, log='$(cat "$SB/log")'" +# Drift: the carry-over runs only where the alias guard allows, and a blob it +# kept sends init to a RAM overlay rather than the jffs2 path that formats. +call=$(sed -n '/aliased=false/,/elif ! mount -t jffs2/p' "$SRC") +if echo "$call" | grep -q 'flash_aliased' && echo "$call" | grep -q '! \$aliased' && + echo "$call" | awk '/flash_aliased/ && !a {a=NR} /overlay_carry_in/ && !c {c=NR} END {exit !(a && c && a < c)}'; then + ok "the carry-over runs after the alias check, and only on flash that is not aliased" +else + bad "overlay_carry_in must come after flash_aliased and be skipped on aliased flash in $SRC" +fi +if echo "$call" | grep -q 'carried" = 1' && echo "$call" | grep -q 'mount -t tmpfs tmpfs /overlay'; then + ok "a kept blob boots on a RAM overlay" +else + bad "a carry-over kept for the next boot must not reach the jffs2 mount in $SRC" +fi + echo [ "$fail" -eq 0 ] && echo "All init overlay checks passed." || echo "$fail check(s) failed." exit "$fail" diff --git a/.github/scripts/test_sysupgrade.sh b/.github/scripts/test_sysupgrade.sh index f41fccd13d..6f2776bee3 100755 --- a/.github/scripts/test_sysupgrade.sh +++ b/.github/scripts/test_sysupgrade.sh @@ -240,6 +240,8 @@ if [ "$head" = "1" ]; then exit 0 fi [ "$out" = "-" ] && [ -n "${STUB_DL_FILE:-}" ] && cat "$STUB_DL_FILE" +# fetch_manifest: the channel/build index, when a test supplies one. +case "$out" in *manifest.flat) [ -n "${STUB_MANIFEST:-}" ] && cp "$STUB_MANIFEST" "$out" ;; esac exit "${STUB_CURL_RC:-0}"' # check_sdcard re-reads `mount` after every umount, so a static pair of stubs # would spin forever: the unmount has to actually change what mount reports. @@ -3269,7 +3271,7 @@ fi # An image that leaves no block for the settings is refused before the write. SPLIT_SQ_USED=$((93 * 65536)) split_env split run -z --archive="$SB/tmp/fw.tgz" -if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "room for settings" && +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "settings do not fit" && [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote; then ok "split layout: an image that fills the flash is refused, nothing written" else @@ -3281,11 +3283,49 @@ split_env split make_split_fw "$SB/tmp/firmware.bin.gk7205v200" gk7205v300 30 $((40 * 65536 - 4000)) make_archive "$SB/tmp/firmware.bin.gk7205v200" run -z --archive="$SB/tmp/fw.tgz" -if [ "$RC" -ne 0 ] && [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote; then - ok "split layout: another SoC's firmware.bin is refused, nothing written" +if [ "$RC" -ne 0 ] && [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote && + ! rebooted; then + ok "split layout: another SoC's firmware.bin is refused before the pivot, nothing written, no reboot" else bad "split layout: wrong SoC must be refused, rc=$RC out='$OUT'" fi +# Bytes past the squashfs: not what post-image.sh packs, and they would run +# into the settings. Refused before anything moves. +split_env split +head -c 131072 /dev/urandom >> "$SB/tmp/firmware.bin.gk7205v200" +make_archive "$SB/tmp/firmware.bin.gk7205v200" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "runs past the end of its squashfs" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote && ! rebooted; then + ok "split layout: a firmware.bin with bytes past its squashfs is refused, no reboot" +else + bad "split layout: trailing bytes must be refused, rc=$RC out='$OUT'" +fi + +# Settings that do not fit what the image leaves: refused before the pivot. +SPLIT_SQ_USED=$((92 * 65536)) split_env split +head -c 200000 /dev/urandom > "$SB/overlay/etc/noise" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "settings do not fit" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote && ! rebooted; then + ok "split layout: settings too big for the space left are refused before the pivot" +else + bad "split layout: oversized settings must be refused unwritten, rc=$RC out='$OUT'" +fi + +# Any other combined image on a camera already on the split layout: its +# "firmware" partition is the whole flash after env. +split_env split +make_combined "$SB/tmp/firmware.bin.gk7205v200" +make_archive "$SB/tmp/firmware.bin.gk7205v200" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "not a split-layout firmware.bin" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && nothing_wrote && ! rebooted; then + ok "split layout: a combined image of another shape is refused, nothing written" +else + bad "split layout: a non-split combined image must be refused, rc=$RC out='$OUT'" +fi + # What mtdblock accepted is not what reached the chip (its cache was never # written out): the read back through the MTD character device says so, and the # run does not report the firmware written. @@ -3325,6 +3365,25 @@ default_url_is "$F/openipc.gk7205v200-nor-lite-split.tgz" \ default_url_is "$F/openipc.gk7205v200-nor-lite.tgz" \ eval 'set_platform gk7205v200_lite; STUB_SOC=gk7205v200' default_url_is "$F/openipc.ssc338q-nor-lite.tgz" eval 'set_cmdline "$SPLIT_CMDLINE"' +# --channel resolves the plain package from the manifest; a camera that boots +# the split layout fetches the -split one beside it. +printf '@channel stable nightly-1\nnightly-1 gk7205v200_lite nor %s\n' \ + "$F/openipc.gk7205v200-nor-lite.tgz" > "$SB/manifest.flat" +for c in split plain; do + reset_env; set_meminfo 8192; STUB_DL_BYTES=8691055 + set_platform gk7205v200_lite; STUB_SOC=gk7205v200 + [ "$c" = split ] && set_cmdline "$SPLIT_CMDLINE" + RUN_ENV="STUB_MANIFEST=$SB/manifest.flat" + run -z --channel=stable + want="$F/openipc.gk7205v200-nor-lite.tgz" + [ "$c" = split ] && want="$F/openipc.gk7205v200-nor-lite-split.tgz" + if printf '%s' "$OUT" | grep -qF "Download from $want" && nothing_wrote; then + ok "--channel, $c bootloader -> $want" + else + bad "--channel, $c bootloader: expected 'Download from $want', rc=$RC out='$OUT'" + fi +done +rm -f "$SB/manifest.flat" unset STUB_SOC echo "=== Part 2: invariants in $SRC ===" diff --git a/.github/workflows/build-one.yml b/.github/workflows/build-one.yml index 6f054de8f4..7feb7ab10a 100644 --- a/.github/workflows/build-one.yml +++ b/.github/workflows/build-one.yml @@ -190,8 +190,11 @@ jobs: TIME=$(date -d @${SECONDS} +%M:%S) echo TIME=${TIME} >> ${GITHUB_ENV} - NORFW=$(find output/images -name openipc*nor*) - if [ -e ${NORFW} ]; then + # The plain NOR package. gk7205v200-family boards build a -split + # one beside it (Makefile REPACK_NOR_SPLIT), and two paths here made + # the test below fail and the upload step skip. + NORFW=$(find output/images -name 'openipc*nor*' ! -name '*-split.tgz' | head -1) + if [ -n "${NORFW}" ] && [ -e "${NORFW}" ]; then echo NORFW=${NORFW} >> ${GITHUB_ENV} fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b425522b2..a25c57a191 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -476,8 +476,11 @@ jobs: TIME=$(date -d @${SECONDS} +%M:%S) echo TIME=${TIME} >> ${GITHUB_ENV} - NORFW=$(find output/images -name openipc*nor*) - if [ -e ${NORFW} ]; then + # The plain NOR package. gk7205v200-family boards build a -split + # one beside it (Makefile REPACK_NOR_SPLIT), and two paths here made + # the test below fail and the upload step skip. + NORFW=$(find output/images -name 'openipc*nor*' ! -name '*-split.tgz' | head -1) + if [ -n "${NORFW}" ] && [ -e "${NORFW}" ]; then echo NORFW=${NORFW} >> ${GITHUB_ENV} fi diff --git a/general/overlay/init b/general/overlay/init index 50a7925964..2170d5cc34 100755 --- a/general/overlay/init +++ b/general/overlay/init @@ -108,15 +108,45 @@ overlay_carry_in() { return 0 fi echo "Restoring settings carried over by an upgrade..." - mount -t tmpfs tmpfs /overlay || return 0 + # Nothing is erased until the archive is whole and in RAM. Until then a + # failure keeps the blob for the next boot (return 1: the caller boots on + # a RAM overlay, so the jffs2 path never formats it away). + if ! mount -t tmpfs tmpfs /overlay; then + carry_report "no RAM to restore the carried-over settings into; trying again next boot" + return 1 + fi dd if="$chr" bs=65536 count=$blocks 2>/dev/null | tail -c +25 | head -c "$len" > /overlay/settings.tgz + if [ "$(wc -c < /overlay/settings.tgz)" -ne "$len" ]; then + umount /overlay + carry_report "could not read the carried-over settings; trying again next boot" + return 1 + fi + # A damaged archive will not get better: say so, and leave rootfs_data to + # the usual mount, which formats what it cannot read. + if ! gunzip -c /overlay/settings.tgz | tar -t > /dev/null 2>&1; then + umount /overlay + carry_report "the carried-over settings are damaged; the camera starts from defaults" + return 0 + fi flash_eraseall -j "$chr" - if mount -t jffs2 "$blk" /mnt; then - gunzip -c /overlay/settings.tgz | tar -x -C /mnt && echo "Settings restored." + if ! mount -t jffs2 "$blk" /mnt; then + carry_report "rootfs_data would not mount after the erase; the carried-over settings are lost" + elif gunzip -c /overlay/settings.tgz | tar -x -C /mnt; then + echo "Settings restored." + sync + umount /mnt + else sync umount /mnt + carry_report "the carried-over settings could not be unpacked in full" fi umount /overlay + return 0 +} + +carry_report() { + echo "$1" + echo "<3>init: $1" > /dev/kmsg 2>/dev/null } OVERLAY_MARK=.openipc-overlay @@ -156,13 +186,25 @@ if $is_flash_root; then # Before anything touches rootfs_data: mounting jffs2 on a mirror is # already a write, since the scan queues every block it cannot parse # for erasure. - grep -q 'nand' /proc/cmdline || overlay_carry_in /dev/$mtdchrdev /dev/$mtdblkdev - if ! grep -q 'nand' /proc/cmdline && flash_aliased; then + aliased=false + grep -q 'nand' /proc/cmdline || ! flash_aliased || aliased=true + carried=0 + if ! grep -q 'nand' /proc/cmdline && ! $aliased; then + overlay_carry_in /dev/$mtdchrdev /dev/$mtdblkdev || carried=1 + fi + if $aliased; then echo "Flash is smaller than the kernel thinks: leaving it alone, settings will not persist." if ! mount -t tmpfs tmpfs /overlay; then echo "Cannot mount overlay." exit 1 fi + elif [ "$carried" = 1 ]; then + # Settings staged by an upgrade are still waiting in rootfs_data; + # mounting it as jffs2 would format them away. + if ! mount -t tmpfs tmpfs /overlay; then + echo "Cannot mount overlay." + exit 1 + fi elif ! mount -t jffs2 /dev/$mtdblkdev /overlay || dmesg | grep -q "jffs2.*: Magic bitmask.*not found"; then # The format below is for a partition that is not ours to keep: a # vendor's leftovers on a camera just converted, or one that never diff --git a/general/overlay/usr/sbin/sysupgrade b/general/overlay/usr/sbin/sysupgrade index 088f8b2c50..fb059aff0a 100755 --- a/general/overlay/usr/sbin/sysupgrade +++ b/general/overlay/usr/sbin/sysupgrade @@ -346,11 +346,23 @@ check_combined_fits() { local x=$1 [ -f "$x" ] || return 0 if nor_split_image "$x"; then - local ovl=$(nor_split_overlay_at "$x") end=$(nor_split_end) - [ $((ovl + 65536)) -le "$end" ] || - die "$x does not fit the flash with room for settings: it ends at $(printf 0x%x "$ovl"), the flash at $(printf 0x%x "$end"). Nothing was written." + # Everything do_update_firmware_split refuses, refused here, before + # the pivot: after it every refusal is a reboot. + local ovl=$(nor_split_overlay_at "$x") end=$(nor_split_end) blob + [ "$(fit_soc "$x")" = "$model" ] || + die "$x is built for $(fit_soc "$x"), this camera is a $model. Nothing was written." + [ $((0x50000 + $(stat -c %s "$x"))) -le "$ovl" ] || + die "$x runs past the end of its squashfs ($(printf 0x%x "$ovl")): not a firmware.bin as the build packs it. Nothing was written." + blob=$(nor_split_blob_size) + [ $((ovl + blob)) -le "$end" ] || + die "$x and the settings do not fit the flash: the image ends at $(printf 0x%x "$ovl"), the settings need $((blob / 1024)) KB, the flash ends at $(printf 0x%x "$end"). Nothing was written; -n upgrades without the settings." return 0 fi + # A camera on the split layout takes nothing else: its "firmware" + # partition is the whole flash after env, and the generic path below + # would write any blob over it. + nor_split_running && + die "$x is not a split-layout firmware.bin for this camera (a FIT, then a squashfs). Nothing was written." # A split-layout image on a camera of that family whose bootloader does not # boot the layout: the FIT would land in a kernel slot U-Boot cannot boot. if nor_split_family && ! nor_split_boot && nor_split_overlay_at "$x" >/dev/null; then @@ -911,6 +923,33 @@ nor_split_running() { nor_split_boot && [ "$(nor_split_devs | wc -l)" -eq 1 ] } +# This camera installs from the -split package: NOR, a SoC built for the +# layout, and a bootloader that boots it. +nor_split_wanted() { + [ "$(pkg_flash)" = nor ] && nor_split_family && nor_split_boot +} + +# The -split package's URL for a plain firmware package's, else the URL as it +# is: openipc.-nor-.tgz becomes ...--split.tgz. +nor_split_url() { + if nor_split_wanted; then + echo "$1" | sed 's|\(/openipc\.[^/]*-nor-[a-z0-9]*\)\.tgz$|\1-split.tgz|' + else + echo "$1" + fi +} + +# The settings blob this run will write, in bytes: the header, the gzipped tar +# of the overlay, whole 64 KiB blocks; one block for -n. +nor_split_blob_size() { + local mp n=0 + mp=$(awk -v d="$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" '$1 == d { print $2; exit }' /proc/mounts) + if [ "1" != "$clear_overlay" ] && [ -n "$mp" ]; then + n=$(tar -C "$mp" -cf - . 2>&3 | gzip | wc -c) + fi + echo $(((24 + n + 65535) / 65536 * 65536)) +} + # The image is one for the split layout and the bootloader boots it. nor_split_image() { nor_split_boot && [ -n "$(nor_split_devs)" ] && nor_split_overlay_at "$1" >/dev/null @@ -922,6 +961,8 @@ do_update_firmware_split() { [ "$(fit_soc "$x")" = "$model" ] || die "$x is built for $(fit_soc "$x"), this camera is a $model. Nothing was written." ovl=$(nor_split_overlay_at "$x") || die "$x is not a FIT followed by a squashfs. Nothing was written." + [ $((0x50000 + $(stat -c %s "$x"))) -le "$ovl" ] || + die "$x runs past the end of its squashfs. Nothing was written." end=$(nor_split_end) [ -n "$end" ] || die "The flash layout is neither the split one nor fixed slots this upgrade can turn into it. Nothing was written." rm -f "$blob" "$tgz" @@ -973,6 +1014,7 @@ do_update_firmware() { do_update_firmware_split "$x" return 0 fi + nor_split_running && die "$x is not a split-layout firmware.bin for this camera. Nothing was written." if [ "$(get_device "kernel")" != "/dev/" ] && [ "$(get_device "rootfs")" != "/dev/" ]; then # Split at the FIT boundary, then hand each slice to the split flow. @@ -1695,6 +1737,9 @@ download_firmware() { if [ -n "$platform" ]; then platform="${model}_${platform#*_}"; else platform="${model}_${osr}"; fi url=$(resolve_url "$build_id" "$platform" "$(pkg_flash)") [ -z "$url" ] && die "No artifact for $platform in $build_id" + # The manifest lists the plain package, which older cameras + # install from; the -split one sits beside it under its own name. + url=$(nor_split_url "$url") echo "Resolved $build_id for $platform" device_pinned=1 fi @@ -2963,9 +3008,9 @@ default_upgrade_url() { # A bootloader that boots the split NOR layout takes firmware.bin, from its # own archive: the plain one keeps the fixed-slot images every older # camera and installer expects. - local split= - [ "$repo" = firmware ] && [ "$(pkg_flash)" = nor ] && nor_split_family && nor_split_boot && split=-split - echo "https://github.com/OpenIPC/$repo/releases/download/latest/openipc.$model-$(pkg_flash)-$osr$split.tgz" + local u="https://github.com/OpenIPC/$repo/releases/download/latest/openipc.$model-$(pkg_flash)-$osr.tgz" + [ "$repo" = firmware ] && u=$(nor_split_url "$u") + echo "$u" } is_device_platform() {