Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/scripts/test_init_overlay.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -86,6 +88,112 @@ 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; /^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"
[ "$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() { :; }
# shellcheck disable=SC1091
. "$SB/carry.sh"
# blob <file> <tgz or empty>: 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); 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.
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

# 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"
255 changes: 255 additions & 0 deletions .github/scripts/test_sysupgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -3131,6 +3133,259 @@ 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 <file> <soc> <fit blocks> <squashfs bytes_used>: 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 <layout> <chip bytes>: /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 <layout> [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 "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
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 &&
! 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.
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"'
# --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 ==="

# An option named in a user-facing message must exist in the parser.
Expand Down
Loading
Loading