diff --git a/.github/scripts/test_sysupgrade.sh b/.github/scripts/test_sysupgrade.sh index 97b15b0a2..e9ad6e954 100755 --- a/.github/scripts/test_sysupgrade.sh +++ b/.github/scripts/test_sysupgrade.sh @@ -142,7 +142,7 @@ set_cmdline "$CMDLINE_FLASH" # --- stubs ----------------------------------------------------------------- stub() { printf '#!/bin/bash\n%s\n' "$2" > "$SB/bin/$1"; chmod +x "$SB/bin/$1"; } -stub ipcinfo 'case "$1" in -v) echo "${STUB_VENDOR:-sigmastar}";; -F) echo nor;; esac' +stub ipcinfo 'case "$1" in -v) echo "${STUB_VENDOR:-sigmastar}";; -F) echo "${STUB_FLASH:-nor}";; esac' # `upgrade` is the URL a builder profile writes on first boot; unset models # the env that lost it, or never had it (#2484). stub fw_printenv ' @@ -279,6 +279,13 @@ case "$applet" in case " $* " in *" $STUB_FLASHCP_FAIL_DEV "*) exit 1 ;; esac fi ;; reboot) echo "reboot" >> "$FLASH_LOG"; exit 0 ;; + # UBI volume writes, the PID 1 hand-off and the stage-2 unmount: logged + # like flashcp, so a test can assert what reached which volume, in order. + ubiupdatevol) echo "ubiupdatevol $*" >> "$FLASH_LOG" + [ "1" = "$STUB_FLASHCP_FAIL" ] && exit 1 ;; + kill) echo "$applet $*" >> "$FLASH_LOG" ;; + umount) echo "$applet $*" >> "$FLASH_LOG" + exit "${STUB_BB_UMOUNT_RC:-0}" ;; esac exit 0' @@ -353,6 +360,47 @@ make_fit() { make_rootfs() { dd if=/dev/zero bs=1k count=8 of="$1" 2>/dev/null; } +# A FIT whose root description names a SoC, as the NAND FIT's does +# ("OpenIPC ", board//nand-fit.its): fit_soc's witness. +make_fit_soc() { + printf '\xd0\x0d\xfe\xed' > "$1" + dd if=/dev/zero bs=1 count=60 >> "$1" 2>/dev/null + printf 'description\0OpenIPC %s\0' "$2" >> "$1" + dd if=/dev/zero bs=1 count=64 >> "$1" 2>/dev/null +} + +# set_ubi [rootfs_reserved_ebs]: a UBI device with kernel/rootfs/rootfs_data +# volumes, as /sys/class/ubi shows them: 126976-byte LEBs (2 KiB pages, +# 128 KiB blocks). The rootfs volume is 2 LEBs by default -- room for the +# fixtures below and not much more, so a size test has an edge to cross. +set_ubi() { + local u="$SB/sys/class/ubi" i name ebs + rm -rf "$u" + i=0 + for name in kernel rootfs rootfs_data; do + mkdir -p "$u/ubi0_$i" + echo "$name" > "$u/ubi0_$i/name" + echo 126976 > "$u/ubi0_$i/usable_eb_size" + ebs=2; [ "$name" = rootfs ] && ebs=${1:-2} + echo "$ebs" > "$u/ubi0_$i/reserved_ebs" + i=$((i + 1)) + done +} + +# A UBIFS image: superblock node magic at 0 (0x06101831, little-endian), node +# type 6 (superblock) at 20, and the LEB size it was made for at 36 -- the three +# fields sysupgrade reads. $2 is that LEB size (default: the volume's). +make_ubifs() { + local leb=${2:-126976} + printf '\x31\x18\x10\x06' > "$1" + dd if=/dev/zero bs=1 count=16 >> "$1" 2>/dev/null # 4..19 + printf '\x06' >> "$1" # 20: node type + dd if=/dev/zero bs=1 count=15 >> "$1" 2>/dev/null # 21..35 + printf %b "$(printf '\\x%02x\\x%02x\\x%02x\\x%02x' \ + $((leb & 255)) $(((leb >> 8) & 255)) $(((leb >> 16) & 255)) $(((leb >> 24) & 255)))" >> "$1" + dd if=/dev/zero bs=1k count=8 >> "$1" 2>/dev/null +} + # A squashfs whose superblock claims $2 bytes, padded to $3 bytes on disk ($3 # defaults to $2). $3 < $2 is what an unpack that runs out of room in /tmp # leaves behind, and what check_rootfs_complete has to refuse; $3 > $2 is what @@ -421,6 +469,8 @@ run() { STUB_ISIZE="${STUB_ISIZE:-}" \ STUB_RANGE_IGNORED="${STUB_RANGE_IGNORED:-}" \ UNPACK_RESERVE_KB="${UNPACK_RESERVE_KB:-512}" \ + UBI_SYS="$SB/sys/class/ubi" INITTAB="$SB/etc/inittab" handoff_wait=0 \ + ${RUN_ENV:-} \ sh "$SB/sysupgrade" "$@" 2>&1) RC=$? } @@ -444,7 +494,10 @@ reset_env() { set_platform ssc338q_lite unset STUB_FLASHCP_FAIL STUB_FLASHCP_FAIL_DEV STUB_PIVOT_RC STUB_REMOUNT_RC STUB_CURL_RC unset STUB_DL_BYTES STUB_ISIZE STUB_RANGE_IGNORED UNPACK_RESERVE_KB - unset STUB_PREPARE STUB_ABORT STUB_UMOUNT_BUSY + unset STUB_PREPARE STUB_ABORT STUB_UMOUNT_BUSY RUN_ENV + rm -rf "$SB/sys" + printf '::sysinit:/etc/init.d/rcS\n::shutdown:/bin/umount -a -f\n::restart:/sbin/init\n' > "$SB/etc/inittab" + rm -f "$SB"/tmp/*.gk7205v500 set_meminfo : > "$SDMOUNTS" set_mounts @@ -1985,16 +2038,290 @@ else fi rm -f "$SB/bin/stat" +# --------------------------------------------------------------------------- +echo +echo "=== Part 1b: UBI NAND layouts ===" + +# Two layouts keep kernel and rootfs in UBI volumes, told apart by root=: +# ubifs kernel = FIT, rootfs = UBIFS, root=ubi0:rootfs +# ubiblock kernel = uImage, rootfs = squashfs through ubiblock +# ubiupdatevol takes its volume exclusively, and the rootfs volume is held +# open on both (UBIFS, or ubiblock's reader), so a rootfs write on either +# goes through the PID 1 hand-off; the kernel volume is written in place. +CMDLINE_UBIFS='mem=32M console=ttyAMA0,115200 panic=20 init=/init root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048 mtdparts=nand:768k(boot),256k(env),-(ubi)' +CMDLINE_UBIBLOCK='mem=32M console=ttyAMA0,115200 panic=20 init=/init root=/dev/ubiblock0_1 ubi.mtd=2,2048 ubi.block=0,1 mtdparts=nand:768k(boot),256k(env),-(ubi)' +UFIT="$SB/tmp/fitImage.gk7205v500" +UFS="$SB/tmp/rootfs.ubifs.gk7205v500" +UK="$SB/tmp/uImage.gk7205v500" +US="$SB/tmp/rootfs.squashfs.gk7205v500" + +# ubi_setup [rootfs_reserved_ebs]: a gk7205v500 NAND camera +# whose MTD table has no kernel/rootfs partitions -- they are UBI volumes. +ubi_setup() { + set_ubi "${2:-2}" + set_mtd <<'EOF2' +dev: size erasesize name +mtd0: 000c0000 00020000 "boot" +mtd1: 00040000 00020000 "env" +mtd2: 07f00000 00020000 "ubi" +EOF2 + if [ "$1" = ubifs ]; then set_cmdline "$CMDLINE_UBIFS"; else set_cmdline "$CMDLINE_UBIBLOCK"; fi + set_platform gk7205v500_ultimate ultimate + export STUB_VENDOR=goke STUB_SOC=gk7205v500 STUB_IMG_SOC=gk7205v500 + make_fit "$UFIT" + make_ubifs "$UFS" + make_uimage "$UK" gk7205v500 + make_squashfs "$US" 8192 +} +ubi_wrote() { grep -q "ubiupdatevol .*$1" "$SB/tmp/flash.log"; } +nothing_ubi() { ! grep -qE "ubiupdatevol|flashcp|flash_eraseall" "$SB/tmp/flash.log"; } +handed_off() { grep -q "^kill -QUIT 1" "$SB/tmp/flash.log"; } + +# --- ubiblock: NOR artifacts; the rootfs goes through the hand-off too +# (measured: ubiupdatevol on a volume ubiblock has open is EBUSY) +reset_env; ubi_setup ubiblock +STUB_PIVOT_RC=0 +run -z --kernel="$UK" --rootfs="$US" +if handed_off && nothing_ubi && grep -q "ubi_layout=.ubiblock" "$SB/ram/sysupgrade.env"; then + ok "ubiblock: a rootfs write hands PID 1 off before anything is written" +else + bad "ubiblock: expected the hand-off, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi +reset_env; ubi_setup ubiblock +run -z --kernel="$UK" +if [ "$RC" -eq 0 ] && ubi_wrote "/dev/ubi0_0 $UK" && ! handed_off && ! grep -q flashcp "$SB/tmp/flash.log"; then + ok "ubiblock: a uImage is written into the kernel volume in place" +else + bad "ubiblock: kernel-only should write ubi0_0 in place, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi +reset_env; ubi_setup ubiblock +RUN_ENV="_ramfs_phase=1 _handoff=1 ubi_layout=ubiblock kernel_device=/dev/ubi0_0 ubi_rootfs_dev=/dev/ubi0_1 ubi_data_dev=/dev/ubi0_2 update_kernel=1 update_rootfs=1 kernel_file=$UK rootfs_file=$US model=gk7205v500 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" +run +u=$(logged_at "umount -l /mnt"); k=$(logged_at "ubiupdatevol /dev/ubi0_0 $UK"); r=$(logged_at "ubiupdatevol /dev/ubi0_1 $US") +if [ -n "$u" ] && [ -n "$k" ] && [ -n "$r" ] && [ "$u" -lt "$k" ] && [ "$k" -lt "$r" ] && rebooted; then + ok "stage 2 (ubiblock): old root released, uImage then squashfs written" +else + bad "stage 2 ubiblock order ${u:-none}/${k:-none}/${r:-none} log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- ubifs: a kernel-only write touches no mounted volume +reset_env; ubi_setup ubifs +run -z --kernel="$UFIT" +if [ "$RC" -eq 0 ] && ubi_wrote "/dev/ubi0_0 $UFIT" && ! handed_off; then + ok "ubifs: a FIT kernel is written into the kernel volume in place" +else + bad "ubifs: kernel-only should write ubi0_0 without a hand-off, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- ubifs: a local UBIFS rootfs has no SoC witness +reset_env; ubi_setup ubifs +run -z --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "Cannot verify the SoC of a UBIFS rootfs" && nothing_ubi; then + ok "ubifs: a local UBIFS rootfs is refused without --force_soc, nothing written" +else + bad "ubifs: expected the SoC refusal before any write, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- ubifs: no ::restart: entry -> refused before the kernel goes down +reset_env; ubi_setup ubifs +printf '::sysinit:/etc/init.d/rcS\n' > "$SB/etc/inittab" +run -z -f --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "::restart:/sbin/init" && nothing_ubi && ! rebooted; then + ok "ubifs: an inittab that cannot hand PID 1 off is refused, nothing written, no reboot" +else + bad "ubifs: expected the inittab refusal, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- ubifs: no pivot -> no in-place fallback, refused unwritten +reset_env; ubi_setup ubifs +run -z -f --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "cannot be rewritten without it" && nothing_ubi && ! rebooted; then + ok "ubifs: a failed pivot refuses rather than writing a volume that is in use" +else + bad "ubifs: expected the no-pivot refusal, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- format must match what root= mounts +reset_env; ubi_setup ubifs +run -z -f --rootfs="$US" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "boots a UBIFS rootfs" && nothing_ubi; then + ok "ubifs: a squashfs rootfs is refused" +else + bad "ubifs: a squashfs must be refused, rc=$RC out='$OUT'" +fi +reset_env; ubi_setup ubiblock +run -z -f --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "is a UBIFS image" && nothing_ubi; then + ok "ubiblock: a UBIFS rootfs is refused" +else + bad "ubiblock: a UBIFS image must be refused, rc=$RC out='$OUT'" +fi + +# --- a UBIFS made for other LEBs would never mount +reset_env; ubi_setup ubifs +make_ubifs "$UFS" 253952 +run -z -f --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "253952-byte LEBs" && nothing_ubi; then + ok "ubifs: an image for another LEB size is refused before the kernel is written" +else + bad "ubifs: LEB mismatch must be refused, rc=$RC out='$OUT'" +fi + +# --- bigger than the volume +reset_env; ubi_setup ubifs 1 +dd if=/dev/zero bs=1k count=200 >> "$UFS" 2>/dev/null +run -z -f --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "does not fit" && nothing_ubi; then + ok "ubifs: a rootfs larger than its volume is refused, nothing written" +else + bad "ubifs: oversize rootfs must be refused, rc=$RC out='$OUT'" +fi + +# --- the hand-off itself +reset_env; ubi_setup ubifs +STUB_PIVOT_RC=0 +run -z -f --kernel="$UFIT" --rootfs="$UFS" +envf="$SB/ram/sysupgrade.env" +if handed_off && nothing_ubi && [ -x "$SB/ram/sbin/init" ] && [ ! -e "$SB/ram/bin/umount" ] && + [ -L "$SB/ram/sbin/umount" ] && + grep -q "_handoff=1" "$envf" 2>/dev/null && grep -q "_ramfs_phase=.1" "$envf" && + grep -q "ubi_rootfs_dev=./dev/ubi0_1" "$envf" && grep -q "sysupgrade.env" "$SB/ram/sbin/init"; then + ok "ubifs: the RAM root is staged for PID 1 and SIGQUIT sent before any write" +else + bad "ubifs: hand-off staging, log='$(cat "$SB/tmp/flash.log")' init='$(cat "$SB/ram/sbin/init" 2>&1)' out='$OUT'" +fi + +# --- stage 2, as PID 1: release the old root, then write, then reboot +reset_env; ubi_setup ubifs +RUN_ENV="_ramfs_phase=1 _handoff=1 ubi_layout=ubifs kernel_device=/dev/ubi0_0 ubi_rootfs_dev=/dev/ubi0_1 ubi_data_dev=/dev/ubi0_2 update_kernel=1 update_rootfs=1 kernel_file=$UFIT rootfs_file=$UFS model=gk7205v500 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" +run +u=$(logged_at "umount -l /mnt"); k=$(logged_at "ubiupdatevol /dev/ubi0_0"); r=$(logged_at "ubiupdatevol /dev/ubi0_1"); b=$(logged_at "^reboot") +if [ -n "$u" ] && [ -n "$k" ] && [ -n "$r" ] && [ -n "$b" ] && [ "$u" -lt "$k" ] && [ "$k" -lt "$r" ] && [ "$r" -lt "$b" ]; then + ok "stage 2: old root released, kernel then rootfs written, then reboot" +else + bad "stage 2 order umount/kernel/rootfs/reboot = ${u:-none}/${k:-none}/${r:-none}/${b:-none} log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +reset_env; ubi_setup ubifs +RUN_ENV="_ramfs_phase=1 _handoff=1 ubi_layout=ubifs kernel_device=/dev/ubi0_0 ubi_rootfs_dev=/dev/ubi0_1 ubi_data_dev=/dev/ubi0_2 clear_overlay=1 model=gk7205v500 root_on_flash=1 ram_root_shipped=1" +run +if grep -q "ubiupdatevol -t /dev/ubi0_2" "$SB/tmp/flash.log" && rebooted; then + ok "stage 2: the UBIFS overlay volume is truncated" +else + bad "stage 2 wipe: expected ubiupdatevol -t /dev/ubi0_2, log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- -n on a mounted UBIFS overlay needs the hand-off too +reset_env; ubi_setup ubifs; set_mounts ubi +run -z -n +if [ "$RC" -ne 0 ] && nothing_ubi && ! rebooted; then + ok "ubifs: wiping a mounted UBIFS overlay without a pivot is refused, nothing erased" +else + bad "ubifs: -n without a pivot must refuse, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- the FIT beside a UBIFS rootfs is its SoC witness +reset_env; ubi_setup ubifs +make_fit_soc "$UFIT" gk7205v500 +run -z --kernel="$UFIT" --rootfs="$UFS" +if printf '%s' "$OUT" | grep -q "SoC from the FIT kernel beside it: gk7205v500" && + printf '%s' "$OUT" | grep -q "SoC OK" && nothing_ubi; then + ok "ubifs: a FIT naming this SoC vouches for the UBIFS rootfs beside it" +else + bad "ubifs: FIT witness, rc=$RC out='$OUT'" +fi +reset_env; ubi_setup ubifs +make_fit_soc "$UFIT" hi3516ev300 +run -z --kernel="$UFIT" --rootfs="$UFS" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "Wrong SoC" && nothing_ubi && ! rebooted; then + ok "ubifs: a FIT for another SoC stops the run before anything is written" +else + bad "ubifs: foreign FIT must be refused, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- a local archive is not pinned by its names +reset_env; ubi_setup ubifs +stage="$SB/stage"; rm -rf "$stage"; mkdir -p "$stage" +cp "$UFIT" "$UFS" "$stage/" +(cd "$stage" && for f in fitImage.gk7205v500 rootfs.ubifs.gk7205v500; do + md5sum "$f" > "$f.md5sum"; done && tar cf - . | gzip > "$SB/tmp/fw.tgz") +rm -f "$UFIT" "$UFS" +run -z --archive="$SB/tmp/fw.tgz" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "no FIT kernel beside it names one" && nothing_ubi; then + ok "ubifs: an archive whose FIT names no SoC is not taken on its file names" +else + bad "ubifs: archive without a witness must be refused, rc=$RC out='$OUT'" +fi + +# --- stage 2 writes nothing when the old root will not let go +reset_env; ubi_setup ubifs +RUN_ENV="STUB_BB_UMOUNT_RC=1 _ramfs_phase=1 _handoff=1 ubi_layout=ubifs kernel_device=/dev/ubi0_0 ubi_rootfs_dev=/dev/ubi0_1 ubi_data_dev=/dev/ubi0_2 update_kernel=1 update_rootfs=1 kernel_file=$UFIT rootfs_file=$UFS model=gk7205v500 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" +run +if printf '%s' "$OUT" | grep -q "Could not let go of the old root" && nothing_ubi && rebooted; then + ok "stage 2: a failed release of the old root reboots with nothing written" +else + bad "stage 2: umount failure must stop before the first write, log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- a ubiblock camera from before the hand-off keeps its gluebi path +reset_env; ubi_setup ubiblock +printf '::sysinit:/etc/init.d/rcS\n' > "$SB/etc/inittab" +set_mtd <<'EOF2' +dev: size erasesize name +mtd0: 000c0000 00020000 "boot" +mtd1: 00040000 00020000 "env" +mtd2: 07f00000 00020000 "ubi" +mtd3: 003e0000 0001f000 "kernel" +mtd4: 01f00000 0001f000 "rootfs" +mtd5: 00200000 0001f000 "rootfs_data" +EOF2 +run -z --kernel="$UK" --rootfs="$US" +if [ "$RC" -eq 0 ] && flashed /dev/mtd3 && flashed /dev/mtd4 && ! handed_off && + ! grep -q ubiupdatevol "$SB/tmp/flash.log"; then + ok "ubiblock without ::restart: writes through gluebi as it always did" +else + bad "ubiblock old-inittab fallback, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi +reset_env; ubi_setup ubiblock +printf '::sysinit:/etc/init.d/rcS\n' > "$SB/etc/inittab" +run -z --kernel="$UK" --rootfs="$US" +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q "::restart:/sbin/init" && nothing_ubi && ! rebooted; then + ok "ubiblock without ::restart: and without gluebi is refused, nothing written" +else + bad "ubiblock old inittab, no gluebi, rc=$RC log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# --- the download: -nand- for ubifs, -nor- for ubiblock +F=https://github.com/OpenIPC/firmware/releases/download/latest +default_url_is "$F/openipc.gk7205v500-nand-ultimate.tgz" ubi_setup ubifs +default_url_is "$F/openipc.gk7205v500-nor-ultimate.tgz" ubi_setup ubiblock + +# --- the unpack leaves rootfs.ubi (fresh-install image) in the archive +reset_env; ubi_setup ubifs +stage="$SB/stage"; rm -rf "$stage"; mkdir -p "$stage" +cp "$UFIT" "$UFS" "$stage/" +dd if=/dev/zero bs=1k count=64 of="$stage/rootfs.ubi.gk7205v500" 2>/dev/null +(cd "$stage" && for f in fitImage.gk7205v500 rootfs.ubifs.gk7205v500 rootfs.ubi.gk7205v500; do + md5sum "$f" > "$f.md5sum"; done && tar cf - . | gzip > "$SB/tmp/fw.tgz") +rm -f "$UFIT" "$UFS" +run -z --archive="$SB/tmp/fw.tgz" +if [ -f "$UFIT" ] && [ -f "$UFS" ] && [ ! -e "$SB/tmp/rootfs.ubi.gk7205v500" ] && + ! printf '%s' "$OUT" | grep -q "Wrong checksum"; then + ok "ubifs: the volume images are unpacked, rootfs.ubi and its checksum are not" +else + bad "ubifs unpack: fit=$([ -f "$UFIT" ] && echo y) ubifs=$([ -f "$UFS" ] && echo y) ubi=$([ -e "$SB/tmp/rootfs.ubi.gk7205v500" ] && echo y) out='$OUT'" +fi + # --------------------------------------------------------------------------- echo echo "=== Part 2: invariants in $SRC ===" # An option named in a user-facing message must exist in the parser. # --connect-timeout, --speed-limit/--speed-time and --max-filesize are curl's, -# not ours. +# and --exclude is tar's, not ours. for opt in $(grep -oE '\-\-[a-z_]+' "$SRC" | sort -u); do case "$opt" in - --force_*|--wipe_overlay|--no_reboot|--no_update|--no_ramfs|--help|--web|--url|--archive|--kernel|--rootfs|--channel|--build|--list*|--insecure|--connect*|--speed*|--proto*|--max*) continue ;; + --force_*|--wipe_overlay|--no_reboot|--no_update|--no_ramfs|--help|--web|--url|--archive|--kernel|--rootfs|--channel|--build|--list*|--insecure|--connect*|--speed*|--proto*|--max*|--exclude) continue ;; esac bad "message references '$opt', which the option parser does not accept" done @@ -2210,8 +2537,14 @@ fi # Every flashcp the script runs has to have its status read. A bare call # discards it and a pipeline hides it; either way a write that never happened is # announced as one that did (#2426). -if grep -n 'set_progress flash' "$SRC" | grep -qv '||'; then - bad "an unguarded set_progress write: $(grep -n 'set_progress flash' "$SRC" | grep -v '||')" +# write_image is the one place a write is not guarded on its own line: its +# set_progress IS its return status, and its callers carry the `||` instead. +wi_lines=$(awk '/^write_image\(\)/,/^}/ { print NR }' "$SRC") +if grep -n 'set_progress \(flash\|ubiupdatevol\)' "$SRC" | grep -v '||' | + grep -qvE "^($(echo $wi_lines | tr ' ' '|')):"; then + bad "an unguarded set_progress write: $(grep -n 'set_progress \(flash\|ubiupdatevol\)' "$SRC" | grep -v '||')" +elif grep -n 'write_image "' "$SRC" | grep -qv '||'; then + bad "an unguarded write_image call: $(grep -n 'write_image "' "$SRC" | grep -v '||')" else ok "every flashcp/flash_eraseall write is followed by a status check" fi @@ -2391,7 +2724,7 @@ fi body=$(sed -n '/^do_update_rootfs()/,/^}/p' "$SRC") e=$(printf '%s\n' "$body" | grep -n 'exit_update' | head -1 | cut -d: -f1) m=$(printf '%s\n' "$body" | grep -n 'mark_live_flash_dirty' | head -1 | cut -d: -f1) -f=$(printf '%s\n' "$body" | grep -n 'flashcp' | head -1 | cut -d: -f1) +f=$(printf '%s\n' "$body" | grep -nE 'write_image|flashcp' | head -1 | cut -d: -f1) if [ -n "$e" ] && [ -n "$m" ] && [ -n "$f" ] && [ "$e" -lt "$m" ] && [ "$m" -lt "$f" ]; then ok "do_update_rootfs marks after the same-version return and before the write" else diff --git a/Makefile b/Makefile index 464b69253..981075b12 100644 --- a/Makefile +++ b/Makefile @@ -193,6 +193,15 @@ endif ifeq ($(BR2_TARGET_ROOTFS_UBI),y) ifneq ($(filter $(BR2_OPENIPC_SOC_VENDOR),"rockchip" "sigmastar"),) @$(call PREPARE_REPACK,,,rootfs.ubi,16384,nand) +else ifneq ($(wildcard $(PWD)/br-ext-chip-$(subst ",,$(BR2_OPENIPC_SOC_VENDOR))/board/$(subst ",,$(BR2_OPENIPC_SOC_FAMILY))/nand-fit.its),) +# FIT NAND (board//nand-fit.its): the kernel lives in the `kernel` UBI +# volume, so the package carries what sysupgrade writes into each volume -- +# fitImage and rootfs.ubifs -- and rootfs.ubi for a fresh install. Measured +# against the volume sizes in the board's ubinize-nand.cfg. + @$(call CHECK_SIZE,fitImage,4096) + @$(call CHECK_SIZE,rootfs.ubifs,32768) + @$(call CHECK_SIZE,rootfs.ubi,16384) + @$(call REPACK_NAND_FIT) else @$(call PREPARE_REPACK,uImage,4096,rootfs.ubi,16384,nand) endif @@ -318,3 +327,18 @@ define REPACK_FIRMWARE cd $(TARGET)/images && tar -czf $(ARCHIVE) $(KERNEL_MD5) $(ROOTFS_MD5) $(KERNEL) $(ROOTFS) 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. +NAND_FIT_IMAGES = fitImage rootfs.ubifs rootfs.ubi +define REPACK_NAND_FIT + cd $(TARGET)/images && for f in $(NAND_FIT_IMAGES); do \ + cp -f $$f $$f.$(BR2_OPENIPC_SOC_MODEL) && \ + md5sum $$f.$(BR2_OPENIPC_SOC_MODEL) > $$f.$(BR2_OPENIPC_SOC_MODEL).md5sum || exit 1; done + # Checksums first, as in REPACK_FIRMWARE. + cd $(TARGET)/images && tar -czf openipc.$(BR2_OPENIPC_SOC_MODEL)-nand-$(BR2_OPENIPC_VARIANT).tgz \ + $(foreach f,$(NAND_FIT_IMAGES),$(f).$(BR2_OPENIPC_SOC_MODEL).md5sum) \ + $(foreach f,$(NAND_FIT_IMAGES),$(f).$(BR2_OPENIPC_SOC_MODEL)) + rm -f $(TARGET)/images/*.md5sum +endef diff --git a/br-ext-chip-goke/board/gk7205v500/gk7205v500.generic.config b/br-ext-chip-goke/board/gk7205v500/gk7205v500.generic.config index cfea83475..aef2bc3c5 100644 --- a/br-ext-chip-goke/board/gk7205v500/gk7205v500.generic.config +++ b/br-ext-chip-goke/board/gk7205v500/gk7205v500.generic.config @@ -485,13 +485,16 @@ CONFIG_HAVE_MEMBLOCK=y CONFIG_NO_BOOTMEM=y # CONFIG_HAVE_BOOTMEM_INFO_NODE is not set CONFIG_SPLIT_PTLOCK_CPUS=4 -# CONFIG_COMPACTION is not set +CONFIG_COMPACTION=y +CONFIG_MIGRATION=y # CONFIG_PHYS_ADDR_T_64BIT is not set # CONFIG_KSM is not set CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 CONFIG_NEED_PER_CPU_KM=y # CONFIG_CLEANCACHE is not set -# CONFIG_CMA is not set +CONFIG_CMA=y +# CONFIG_CMA_DEBUG is not set +CONFIG_CMA_AREAS=77 # CONFIG_ZPOOL is not set # CONFIG_ZBUD is not set # CONFIG_ZSMALLOC is not set @@ -2849,3 +2852,19 @@ CONFIG_ARCH_HAS_SG_CHAIN=y CONFIG_SBITMAP=y # CONFIG_VIRTUALIZATION is not set # CONFIG_GCC_PLUGINS is not set + +# +# CMA for the media stack (xm_osal mmz_allocator=cma): the kernel reserves the +# zone named by mmz= on the command line (drivers/xmedia/cma) and, with +# CMA_MEM_SHARED, lends it to movable pages while the MMZ does not need it, so +# Linux can be given the whole DDR (mem=${totalmem}) as on gk7205v200. +# +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=0 +CONFIG_CMA_SIZE_SEL_MBYTES=y +# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set +# CONFIG_CMA_SIZE_SEL_MIN is not set +# CONFIG_CMA_SIZE_SEL_MAX is not set +CONFIG_CMA_ALIGNMENT=8 +CONFIG_CMA_MEM_SHARED=y +# CONFIG_CMA_ADVANCE_SHARE is not set diff --git a/br-ext-chip-goke/board/gk7205v500/nand-fit.its b/br-ext-chip-goke/board/gk7205v500/nand-fit.its new file mode 100644 index 000000000..d1410dca3 --- /dev/null +++ b/br-ext-chip-goke/board/gk7205v500/nand-fit.its @@ -0,0 +1,51 @@ +/dts-v1/; + +/* + * Kernel volume of the gk7205v500-family NAND image: the zImage and its DTB, + * each hashed so U-Boot refuses a kernel that NAND has corrupted instead of + * booting it. Built by general/scripts/rootfs_script.sh (the "NAND FIT" step) + * from the kernel tree, before ubinize packs it into the `kernel` volume. + * + * No fdt load address: u-boot-xmedia relocates the DTB under bootm_size, + * inside the kernel's lowmem. + */ +/ { + /* "OpenIPC ": sysupgrade's SoC check reads it (fit_soc). @SOC@ is + filled in from OPENIPC_SOC_MODEL when the FIT is built. */ + 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 = "xm720xxx-demb"; + data = /incbin/("xm720xxx-demb.dtb"); + type = "flat_dt"; + arch = "arm"; + compression = "none"; + hash-1 { algo = "crc32"; }; + hash-2 { algo = "sha1"; }; + }; + }; + + configurations { + default = "conf-1"; + conf-1 { + description = "Linux + xm720xxx-demb"; + kernel = "kernel"; + fdt = "fdt"; + }; + }; +}; diff --git a/br-ext-chip-goke/board/gk7205v500/ubinize-nand.cfg b/br-ext-chip-goke/board/gk7205v500/ubinize-nand.cfg new file mode 100644 index 000000000..c98bb4289 --- /dev/null +++ b/br-ext-chip-goke/board/gk7205v500/ubinize-nand.cfg @@ -0,0 +1,30 @@ +# gk7205v500-family NAND: kernel (FIT) + rootfs (UBIFS) + rootfs_data. +# Boots with u-boot-xmedia's FIT NAND env: `ubi read ${baseaddr} kernel; bootm` +# and root=ubi0:rootfs. sysupgrade rewrites `kernel` and `rootfs` in place +# with ubiupdatevol, so their sizes are the ceiling for future images. +[kernel] +mode=ubi +vol_id=0 +vol_type=dynamic +vol_name=kernel +vol_alignment=1 +vol_size=4MiB +image=BINARIES_DIR/fitImage + +[rootfs] +mode=ubi +vol_id=1 +vol_type=dynamic +vol_name=rootfs +vol_alignment=1 +vol_size=32MiB +image=BR2_ROOTFS_UBIFS_PATH + +[rootfs_data] +mode=ubi +vol_id=2 +vol_type=dynamic +vol_name=rootfs_data +vol_alignment=1 +vol_size=2MiB +vol_flags=autoresize diff --git a/br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig b/br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig index b4145086c..c72f73716 100644 --- a/br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig +++ b/br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig @@ -43,7 +43,7 @@ BR2_TARGET_ROOTFS_SQUASHFS4_XZ=y BR2_TARGET_ROOTFS_UBI=y BR2_TARGET_ROOTFS_UBI_SUBSIZE=2048 BR2_TARGET_ROOTFS_UBI_USE_CUSTOM_CONFIG=y -BR2_TARGET_ROOTFS_UBI_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL)/scripts/ubifs/ubinize.cfg" +BR2_TARGET_ROOTFS_UBI_CUSTOM_CONFIG_FILE="$(EXTERNAL_VENDOR)/board/$(OPENIPC_SOC_FAMILY)/ubinize-nand.cfg" BR2_TARGET_ROOTFS_UBIFS_LEBSIZE=0x1f000 # OpenIPC diff --git a/general/overlay/etc/inittab b/general/overlay/etc/inittab index 14bfc44b5..0ec080f42 100644 --- a/general/overlay/etc/inittab +++ b/general/overlay/etc/inittab @@ -34,3 +34,9 @@ console::respawn:/sbin/getty -L console 0 vt100 # Stuff to do before rebooting ::shutdown:/etc/init.d/rcK ::shutdown:/bin/umount -a -f + +# SIGQUIT: run the shutdown actions above, kill every process, exec /sbin/init. +# sysupgrade relies on it to rewrite a mounted UBIFS volume: it pivots into a +# RAM root whose /sbin/init is its flash phase, so PID 1 itself leaves the +# flash and the volume can be released (see need_handoff in sysupgrade). +::restart:/sbin/init diff --git a/general/overlay/init b/general/overlay/init index deeac781f..6e74b68a5 100755 --- a/general/overlay/init +++ b/general/overlay/init @@ -83,8 +83,18 @@ grep -qE 'root=[^ ]*(nfs|mmcblk|ram)' /proc/cmdline && is_flash_root=false grep -qE 'root=[^ ]*nfs' /proc/cmdline && is_nfs_root=true if $is_flash_root; then - if grep -q ubifs /proc/cmdline; then - mount -t ubifs ubi0:rootfs_data /overlay + # A UBI root -- a UBIFS rootfs, or a squashfs through ubiblock -- keeps its + # overlay in the rootfs_data UBI volume, as UBIFS. A ubiblock command line + # names no ubifs, and the jffs2 branch below would find gluebi's mtd for + # that volume, fail to mount jffs2 on it and erase it: the overlay and the + # camera's claim with it. + if grep -q ubifs /proc/cmdline || grep -qE 'root=/dev/ubiblock' /proc/cmdline; then + # Settings in RAM rather than a camera that does not boot: a volume that + # will not mount as UBIFS must not take PID 1 down with it. + if ! mount -t ubifs ubi0:rootfs_data /overlay && ! mount -t tmpfs tmpfs /overlay; then + echo "Cannot mount overlay." + exit 1 + fi else mtdblkdev=$(awk -F ':' '/rootfs_data/ {print $1}' /proc/mtd | sed 's/mtd/mtdblock/') mtdchrdev=$(grep 'rootfs_data' /proc/mtd | cut -d: -f1) diff --git a/general/overlay/usr/sbin/sysupgrade b/general/overlay/usr/sbin/sysupgrade index 900d03a52..8fa31ef05 100755 --- a/general/overlay/usr/sbin/sysupgrade +++ b/general/overlay/usr/sbin/sysupgrade @@ -49,6 +49,10 @@ WDOG_PROC=${WDOG_PROC:-/proc} # pivot /tmp is the tmpfs enter_ramfs moved across, the same one set_progress # smuggles its status through. WDOG_RELEASE=/tmp/.sysupgrade-wdog-release +# Where UBI publishes its devices and volumes, and the inittab PID 1 parsed at +# boot. Overridable so the test harness can point both at its sandbox. +UBI_SYS=${UBI_SYS:-/sys/class/ubi} +INITTAB=${INITTAB:-/etc/inittab} # Seconds the rootfs verify-mount may take before it is treated as unmountable. mount_wait=${mount_wait:-45} # Seconds the operator gets to Ctrl-C out of a run that will force a reboot. @@ -146,14 +150,23 @@ compare_versions() { # pre-checks have to name the same files before those two run, and four copies # of the defaults would drift. rootfs_image() { - [ -n "$rootfs_file" ] && echo "$rootfs_file" || echo "/tmp/rootfs.squashfs.$model" + [ -n "$rootfs_file" ] && { echo "$rootfs_file"; return 0; } + [ "$ubi_layout" = "ubifs" ] && echo "/tmp/rootfs.ubifs.$model" || echo "/tmp/rootfs.squashfs.$model" } kernel_image() { [ -n "$kernel_file" ] && { echo "$kernel_file"; return 0; } + [ "$ubi_layout" = "ubifs" ] && { echo "/tmp/fitImage.$model"; return 0; } [ "$vendor" = "rockchip" ] && echo "/tmp/zboot.img.$model" || echo "/tmp/uImage.$model" } +# Where the rootfs is written: the `rootfs` UBI volume on a UBI layout, the +# partition of that name otherwise. +rootfs_device() { + [ -n "$ubi_layout" ] && { echo "$ubi_rootfs_dev"; return 0; } + get_device "rootfs" +} + # Refuse an image that cannot fit the partition it is bound for, before the # write rather than during it. # @@ -257,8 +270,17 @@ preflight_image_sizes() { [ "1" = "$update_rootfs" ] && [ ! -f "$(rootfs_image)" ] && die "No rootfs image for this camera ($model): $(rootfs_image) not found. Is the firmware built for $model?" [ "1" = "$update_kernel" ] && check_image_fits "$(kernel_image)" "$kernel_device" kernel - [ "1" = "$update_rootfs" ] && check_image_fits "$(rootfs_image)" "$(get_device "rootfs")" rootfs + [ "1" = "$update_rootfs" ] && check_image_fits "$(rootfs_image)" "$(rootfs_device)" rootfs [ "1" = "$update_rootfs" ] && check_rootfs_complete "$(rootfs_image)" + # The UBIFS checks read the target volume out of sysfs, which the flash + # phase may not have; the camera is also still whole here. + if [ "1" = "$update_rootfs" ] && [ -n "$ubi_layout" ]; then + check_rootfs_format "$(rootfs_image)" + # The whole UBIFS verdict, SoC included: a UBIFS rootfs is written from + # the handed-off PID 1, where any refusal is a reboot. + is_ubifs "$(rootfs_image)" && verify_ubifs_rootfs "$(rootfs_image)" + fi + need_handoff && check_handoff return 0 } @@ -277,6 +299,8 @@ do_update_kernel() { # and always reflash. if [ "$(xxd -p -l 4 "$x" 2>/dev/null)" = "d00dfeed" ]; then echo "FIT kernel detected, skipping uImage SoC/version probe" + local fsoc=$(fit_soc "$x") + [ -n "$fsoc" ] && check_soc "$fsoc" else local ksoc=$(od -j 32 -N 32 -S 1 -A n "$x" | cut -d- -f3) if [ "$vendor" != "ingenic" ] && [ "$vendor" != "rockchip" ]; then @@ -292,7 +316,8 @@ do_update_kernel() { # as live as a rootfs write, so -x cannot be honoured after it either. # mark_live_flash_dirty is itself a no-op on a camera not running from # flash, and marks the run touched in both cases. - if [ "/dev/" = "$(get_device "kernel")" ]; then + # A UBI kernel volume is never mounted, gluebi or not. + if [ -z "$ubi_layout" ] && [ "/dev/" = "$(get_device "kernel")" ]; then mark_live_flash_dirty else mark_flash_touched @@ -300,7 +325,7 @@ do_update_kernel() { # The success line below reads the version back off the DEVICE, so an # unchecked failure here does not merely claim success, it claims it while # printing the timestamp of the kernel that is still there. - set_progress flashcp -v "$x" "$kernel_device" || + write_image "$x" "$kernel_device" || die "Writing $x to $kernel_device failed." echo_c 32 "Kernel updated to $(get_kernel_version "$kernel_device")" } @@ -338,8 +363,7 @@ soc_guarded_by_kernel() { if [ "kernel_follows" = "$1" ]; then k="/tmp/uImage.$model" elif [ "1" = "$update_kernel" ]; then - k="$kernel_file" - [ -z "$k" ] && k="/tmp/uImage.$model" + k=$(kernel_image) else return 1 fi @@ -403,6 +427,90 @@ check_rootfs_complete() { Nothing was written. Download it again -- a truncated image loses its .md5sum with it." } +# The SoC a FIT kernel was built for, or nothing. +# +# A legacy uImage carries it in its name field (do_update_kernel reads it at +# offset 32); a FIT has none of its own, so the NAND FIT is described as +# "OpenIPC " (board//nand-fit.its, stamped at build time). The +# description is in the FIT's first few hundred bytes. A FIT without one -- the +# cv6xx kernel -- answers nothing, and no conclusion is drawn from that. +fit_soc() { + [ -f "$1" ] || return 0 + [ "$(xxd -p -l 4 "$1" 2>&3)" = "d00dfeed" ] || return 0 + dd if="$1" bs=4096 count=1 2>&3 | tr -c 'a-zA-Z0-9_ ' '\n' | + awk '$1 == "OpenIPC" && NF == 2 { print $2; exit }' +} + +# A UBIFS image: the superblock node's magic (0x06101831, stored little-endian) +# at offset 0. +is_ubifs() { + [ "$(xxd -p -l 4 "$1" 2>&3)" = "31181006" ] +} + +# The rootfs format has to be the one this camera's kernel command line mounts: +# a squashfs written into the volume of a root=ubi0:rootfs camera, or a UBIFS +# into a ubiblock one, flashes cleanly and never mounts again. The kernel's +# rootfstype is fixed by the bootloader environment, which no upgrade rewrites. +check_rootfs_format() { + [ -n "$ubi_layout" ] || return 0 + if [ "$ubi_layout" = "ubifs" ] && ! is_ubifs "$1"; then + die "This camera boots a UBIFS rootfs, and $1 is not one. Nothing was written." + fi + if [ "$ubi_layout" = "ubiblock" ] && is_ubifs "$1"; then + die "This camera boots a squashfs rootfs through ubiblock, and $1 is a UBIFS image. Nothing was written." + fi + return 0 +} + +# What can be known about a UBIFS rootfs without mounting it -- which needs UBI +# under it, so a loop device will not do. +# +# Its superblock pins the LEB size the image was made for, and UBIFS refuses to +# mount on a volume whose LEBs are any other size: the classic way to get this +# wrong is a 2 KiB-page image on a 4 KiB-page chip, and it fails only after the +# reboot. So that is checked against the volume it is bound for. +# +# What it cannot give is the SoC stamp or the version: both live in files the +# image stores compressed. So the SoC rests on the same evidence an unmountable +# squashfs does (see verify_rootfs), and an image is never skipped as the same +# version -- the kernel FIT carries no version either. +check_ubifs_image() { + local x=$1 dev want leb ntype + ntype=$(od -An -tu1 -j20 -N1 "$x" 2>&3 | awk '{print $1; exit}') + [ "$ntype" = "6" ] || die "$x is not a UBIFS image: its first node is not a superblock. Nothing was written." + leb=$(od -An -tu4 -j36 -N4 "$x" 2>&3 | awk '{print $1; exit}') + dev=$(rootfs_device) + want=$(cat "$UBI_SYS/${dev#/dev/}/usable_eb_size" 2>&3) + [ -n "$leb" ] && [ -n "$want" ] || return 0 + [ "$leb" = "$want" ] && return 0 + die "$x was made for $leb-byte LEBs; $dev has $want. It would not mount. Nothing was written." +} + +verify_ubifs_rootfs() { + local x=$1 + rootfs_version= + check_ubifs_image "$x" + echo "UBIFS rootfs; its SoC and version cannot be read without mounting it." + # The FIT that ships beside it says which SoC the pair was built for -- + # the one witness a UBIFS rootfs has. Taken whether or not this run + # writes the kernel: the package carries it either way. + local fsoc=$(fit_soc "$(kernel_image)") + if [ "1" = "$skip_soc" ]; then + echo_c 33 "Skipping the rootfs SoC pre-check (SoC validation disabled)." + elif is_device_platform "$system_platform" && [ "1" != "$device_pinned" ]; then + die "Cannot verify that a UBIFS rootfs is built for this camera's device profile ('$system_platform'). Nothing was written. Pass --force_soc if you are sure it is." + elif [ -n "$fsoc" ]; then + echo "SoC from the FIT kernel beside it: $fsoc" + check_soc "$fsoc" + elif [ "1" = "$remote_update" ] && [ -z "$archive" ]; then + # A download, named for this camera's model. A local archive is + # named by whoever made it, so it does not count. + echo_c 33 "The image is pinned to '$model' by its artifact name." + else + die "Cannot verify the SoC of a UBIFS rootfs: no FIT kernel beside it names one. Nothing was written. Pass --force_soc if you are sure it is built for '$model'." + fi +} + # Read the candidate rootfs's SoC stamp and version by loop-mounting it. # # This is a PRE-FLIGHT CHECK ONLY: the image is written with flashcp, which writes @@ -414,6 +522,11 @@ verify_rootfs() { echo_c 33 "\nVerify" echo "Verifying rootfs from $x" [ ! -f "$x" ] && die "File $x not found" + check_rootfs_format "$x" + if is_ubifs "$x"; then + verify_ubifs_rootfs "$x" "$2" + return 0 + fi check_rootfs_complete "$x" local y=/tmp/rootfs @@ -483,13 +596,13 @@ do_update_rootfs() { echo "Update rootfs from $x" [ ! -f "$x" ] && die "File $x not found" [ "1" = "$exit_update" ] && return 0 - local dev=$(get_device "rootfs") + local dev=$(rootfs_device) check_image_fits "$x" "$dev" rootfs mark_live_flash_dirty # ${rootfs_version} is what verify_rootfs read out of the CANDIDATE FILE, so # an unchecked failure here prints the version the operator asked for -- # indistinguishable from the upgrade that did not happen. - set_progress flashcp -v "$x" "$dev" || + write_image "$x" "$dev" || die "Writing $x to $dev failed." echo_c 32 "RootFS updated to ${rootfs_version:-unknown}" } @@ -612,6 +725,16 @@ do_wipe_overlay() { # old overlay -- including the shadow entry that decides whether the camera # is claimed -- exactly where it was. local dev=$(get_device "rootfs_data") + # A UBI rootfs_data that no MTD (gluebi) name reaches: truncate the volume. + # An empty volume is what ubinize ships, and UBIFS formats it on the next + # mount. Reaching here with it mounted means PID 1 was handed off and the + # old root released first (need_handoff), so the volume is free. + if [ -n "$ubi_layout" ] && [ "$dev" = "/dev/" ]; then + [ -n "$ubi_data_dev" ] || die "No rootfs_data volume to wipe." + set_progress ubiupdatevol -t "$ubi_data_dev" || + die "Truncating the overlay volume $ubi_data_dev failed." + return 0 + fi set_progress flash_eraseall $jffs2 "$dev" || die "Erasing the overlay partition $dev failed." } @@ -771,6 +894,7 @@ check_unpack_room() { # The last four bytes of a gzip stream are the uncompressed size. need=$(od -An -tu4 -j $((sz - 4)) -N4 "$a" 2>&3 | awk '{print $1; exit}') [ -n "$need" ] && [ "$need" -gt 0 ] 2>&3 || return 0 + need=$(($(unpacked_part_kb $((need / 1024)) unpacked) * 1024)) # The same archive has to fit twice over: once in the tmpfs, and once in the # RAM that tmpfs is made of. They are different numbers -- see # check_unpack_ram -- and either can be the one that binds. @@ -853,6 +977,40 @@ majestic first, and majestic is where the missing memory is." die "$out" } +# Members of the archive this run does not unpack, as tar options. +# +# A UBIFS NAND package carries rootfs.ubi -- the whole UBI image, for a fresh +# install from U-Boot or defib -- beside the two volume images sysupgrade +# writes. It is the largest member and it is never written here, so it is not +# unpacked either: /tmp is RAM. The pattern takes its .md5sum with it, so the +# checksum pass does not go looking for it. +tar_excludes() { + [ "$ubi_layout" = "ubifs" ] && echo "--exclude=rootfs.ubi.*" + return 0 +} + +# Of an unpack measured as $1 KB, the part tar_excludes leaves -- $2 says what +# was measured: "unpacked" (the gzip trailer) or "packed" (Content-Length). +# +# A UBIFS NAND package holds each volume image twice -- alone, and again inside +# rootfs.ubi -- so the two this run keeps are about half of the unpacked total +# (48% on the gk7205v500 image). Against the PACKED size they are more: a UBIFS +# image pads every LEB with 0xFF, which gzip takes almost nothing to store, so +# Content-Length understates what lands in /tmp (65% measured, 22035917 bytes +# packed against 14640708 kept). Both rounded up, so the guess errs towards +# asking for more. Every other package unpacks whole, and its Content-Length +# already stands for the unpacked size (see unpack_size_kb). +unpacked_part_kb() { + [ -n "$1" ] || return 0 + if [ "$ubi_layout" != "ubifs" ]; then + echo "$1" + elif [ "$2" = "packed" ]; then + echo $(($1 * 75 / 100)) + else + echo $(($1 * 55 / 100)) + fi +} + # How big will the unpack be, in KB? Nothing when it cannot be told. # # Asked two ways, because neither works everywhere. @@ -872,7 +1030,12 @@ majestic first, and majestic is where the missing memory is." # a hand-rolled --url archive of something compressible expands far past it -- # which is why the trailer is asked for first and this is only the fallback. unpack_size_kb() { - gzip_isize_kb "$1" || remote_length_kb "$1" + local n + if n=$(gzip_isize_kb "$1"); then + unpacked_part_kb "$n" unpacked + elif n=$(remote_length_kb "$1"); then + unpacked_part_kb "$n" packed + fi } # The gzip trailer, via Range. Trusted only on a 206 with exactly four bytes @@ -917,13 +1080,17 @@ remote_length_kb() { download_firmware() { - [ "$flash_type" = "nand" ] && echo_c 31 "\nNote: the updater uses the NOR package for updating NAND" + # A UBIFS NAND layout installs from the -nand- package; every other camera, + # NAND ones included (a ubiblock rootfs holds the NOR artifacts verbatim), + # from the NOR package. + [ "$flash_type" = "nand" ] && [ "$(pkg_flash)" = "nor" ] && + echo_c 31 "\nNote: the updater uses the NOR package for updating NAND" echo_c 33 "\nFirmware" osr=$(get_system_build) if [ -n "$archive" ]; then [ ! -f "$archive" ] && die "File $archive not found" check_unpack_room "$archive" - gzip -d "$archive" -c | tar xf - -C /tmp && echo_c 32 "Local archive unpacked" || die "Cannot extract $archive" + gzip -d "$archive" -c | tar xf - -C /tmp $(tar_excludes) && echo_c 32 "Local archive unpacked" || die "Cannot extract $archive" else if [ -n "$channel" ] || [ -n "$build_id" ]; then fetch_manifest @@ -935,14 +1102,14 @@ download_firmware() { # by builder); fall back to ${model}_${osr} for pre-1.0.51 images. platform=$(grep '^BUILD_PLATFORM=' /etc/os-release 2>/dev/null | cut -d= -f2) if [ -n "$platform" ]; then platform="${model}_${platform#*_}"; else platform="${model}_${osr}"; fi - url=$(resolve_url "$build_id" "$platform" "nor") + url=$(resolve_url "$build_id" "$platform" "$(pkg_flash)") [ -z "$url" ] && die "No artifact for $platform in $build_id" echo "Resolved $build_id for $platform" device_pinned=1 fi [ -z "$url" ] && url=$(fw_printenv -n upgrade || default_upgrade_url) # Named for this device, so verify_rootfs can trust it when it cannot mount it. - [ "${url##*/}" = "$system_platform-nor.tgz" ] && device_pinned=1 + [ "${url##*/}" = "$system_platform-$(pkg_flash).tgz" ] && device_pinned=1 echo "Download from $url" probe_url "$url" # The streamed route stages no second copy, so it never had a room @@ -963,7 +1130,7 @@ download_firmware() { # link completes. -m stays as a far outer backstop against a trickle that # never trips the stall check. curl --connect-timeout 30 $dl_meter --speed-limit 1024 --speed-time 60 -m 1800 \ - -L $(tls_opts "$url") "$url" -o - | gzip -d | tar xf - -C /tmp && echo_c 32 "Received and unpacked" || die "Cannot retrieve $url" + -L $(tls_opts "$url") "$url" -o - | gzip -d | tar xf - -C /tmp $(tar_excludes) && echo_c 32 "Received and unpacked" || die "Cannot retrieve $url" fi if [ "1" != "$skip_md5" ]; then (cd /tmp && md5sum -s -c *.md5sum) || die "Wrong checksum!" @@ -1362,6 +1529,117 @@ ramfs_unwind() { return 1 } +# Does this run have to get PID 1 off the flash before it writes? +# +# ubiupdatevol (UBI_IOCVOLUP) takes the volume exclusively -- get_exclusive() +# in drivers/mtd/ubi/cdev.c -- and fails with EBUSY while anything else has it +# open, readers included. Every volume init mounted is open: a UBIFS rootfs or +# overlay (UBIFS opens its volume even for a read-only mount), and a squashfs +# rootfs through ubiblock alike. The ramfs pivot does not unmount them: init's +# overlay root is the old root, and PID 1 runs from it. OpenWrt meets the same +# wall the same way (procd execs upgraded as PID 1 before stage2 unmounts +# anything); here busybox init does the exec, through inittab's ::restart:. +# Measured on a gk7205v510 with a ubiblock root: "ubiupdatevol: UBI_IOCVOLUP: +# Resource busy" from inside the ramfs pivot. +# +# The kernel volume is never mounted, so a kernel-only run needs none of this. +need_handoff() { + [ -n "$ubi_layout" ] || return 1 + [ "1" = "$update_rootfs" ] && return 0 + [ "1" = "$clear_overlay" ] && + grep -qE '^ubi[0-9]*:rootfs_data ' /proc/mounts 2>&3 && return 0 + return 1 +} + +# Can PID 1 be handed off at all? Asked before anything is written, where a +# refusal still leaves the camera exactly as it was. +# +# The ::restart: entry is read by init at boot, so the file on flash is the +# best evidence there is of what PID 1 will do with SIGQUIT. Without one, +# busybox init ignores the signal, and the run could only reboot unwritten. +check_handoff() { + [ "1" = "$skip_ramfs" ] && + die "--no_ramfs: this run must rewrite a mounted UBI volume, which cannot be done from the running system. Nothing was written." + command -v pivot_root >/dev/null 2>&1 || + die "No pivot_root: this run must rewrite a mounted UBI volume, which needs it. Nothing was written." + inittab_hands_off || + die "This run must rewrite a mounted UBI volume, and only PID 1 can let go of it: $INITTAB has no '::restart:/sbin/init' entry to hand it off with. Nothing was written." + return 0 +} + +# Whether PID 1 can be handed off at all: its inittab, read at boot, has the +# ::restart: entry that execs /sbin/init. Images before the UBI layouts had none. +inittab_hands_off() { + grep -qE '^[^#]*::restart:/sbin/init([[:space:]]|$)' "$INITTAB" 2>&3 +} + +# Lay out what busybox init needs to exec into this ramfs as PID 1. +# +# init runs its ::shutdown: actions before it execs the ::restart: target, and +# after the pivot it resolves them here. rcK is not here, which is right. But +# `/bin/umount -a -f` would be -- the applet symlinks above put an umount in +# /bin -- and it would take /tmp, holding the images, down with everything +# else. The action names that absolute path, so the link moves to /sbin: init +# no longer finds it, and the flash phase (verify_rootfs's loop mount, for +# one) still does, through PATH. +# +# /sbin/init is the restart target init names, resolved in this root too. The +# state the second phase needs cannot ride in the environment: init execs it +# with init's environment, not ours. So it goes in a file, written after the +# exports it records -- see hand_off_pid1. +stage_handoff() { + mkdir -p "$RAM_ROOT/sbin" 2>&3 || return 1 + rm -f "$RAM_ROOT/bin/umount" 2>&3 + ln -sf ../bin/busybox "$RAM_ROOT/sbin/umount" 2>&3 + printf '#!/bin/sh\n. /sysupgrade.env\nexec /bin/busybox ash /sysupgrade\n' \ + > "$RAM_ROOT/sbin/init" 2>&3 || return 1 + chmod +x "$RAM_ROOT/sbin/init" 2>&3 + [ -x "$RAM_ROOT/sbin/init" ] +} + +# Hand PID 1 to the ramfs, and never come back. +# +# pivot_root has already moved PID 1's root and cwd here along with ours. What +# still ties it to the flash is its own text -- busybox mapped from the old +# root -- and the processes it supervises. SIGQUIT makes busybox init run its +# shutdown actions, kill every other process (this one included), and exec +# /sbin/init: ours. The second phase then starts as PID 1 with the old root +# referenced by nothing, which is what lets its UBI volumes go. +# +# If PID 1 does not take it -- an inittab that changed under a running init -- +# this survives the wait and reboots: nothing has been written. +hand_off_pid1() { + # Relative: the cwd is the new root (enter_ramfs cd'd into it), so this is + # the /sysupgrade.env that /sbin/init sources. + export -p > ./sysupgrade.env 2>&3 + echo "export _handoff=1" >> ./sysupgrade.env + echo_c 37 "\nHanding PID 1 over to the RAM root; this console goes quiet until it does." + sync + busybox kill -QUIT 1 + sleep "${handoff_wait:-30}" + echo_c 31 "PID 1 did not hand over; rebooting (nothing was flashed)" + ./bin/busybox reboot -d 1 -f + exit 1 +} + +# Second phase, as PID 1: let go of the old root so its UBI volumes close. +# +# Lazy, because the old root holds the moved-out mount points' parents and +# whatever else init had mounted under it; with every process that could hold +# a reference gone, the lazy detach is final at once: UBIFS's put_super closes +# its volumes, and the squashfs on a ubiblock device is the block device's last +# opener, so ubiblock closes its reader too. +release_old_root() { + # Nothing is written before this answers. If the old root is still + # attached, every volume under it is still open, and the kernel would go + # down and the rootfs then fail -- a half upgrade. Refusing here reboots + # onto the image that is still there, untouched. + busybox umount -l /mnt 2>&3 || + die "Could not let go of the old root; nothing was written." + sync + echo 3 > /proc/sys/vm/drop_caches 2>&3 +} + # Move the whole flashing phase into a RAM filesystem and re-exec there, so that # nothing it needs afterwards lives on the partition it is about to overwrite. # @@ -1430,6 +1708,9 @@ enter_ramfs() { done cp "$0" "$RAM_ROOT/sysupgrade" 2>/dev/null || return 1 chmod +x "$RAM_ROOT/sysupgrade" 2>/dev/null + if [ "handoff" = "$1" ]; then + stage_handoff || return 1 + fi # Carry over the mounts the flash still needs: /dev for the MTD nodes, /proc # for get_device and sysrq, /tmp for the unpacked images. Moving rather than @@ -1469,6 +1750,11 @@ enter_ramfs() { # /etc/os-release to read it from (#2484). # device_pinned: the image was fetched under this device's own name. export system_platform device_pinned + # The UBI layout and its volumes, resolved on the whole system: the flash + # phase may have no /sys to resolve them from. + export ubi_layout ubi_rootfs_dev ubi_data_dev UBI_SYS INITTAB + # archive: verify_ubifs_rootfs does not count a local archive as pinned. + export archive cd "$RAM_ROOT" || { ramfs_unwind; return 1; } # After this the old root is at ./mnt and still mounted — deliberately: the @@ -1482,6 +1768,8 @@ enter_ramfs() { # declared -- see the note above the other exports. export _ramfs_phase=1 + [ "handoff" = "$1" ] && hand_off_pid1 + # `chroot .` is what actually re-points this process's root; pivot_root only # moved the mounts. Then exec so the shell interpreting the rest is the copy # in RAM, not the one on the flash we are about to erase. @@ -1677,11 +1965,45 @@ get_device() { # (enter_ramfs moves it without checking), so sysfs can simply be absent exactly # where this is needed. partition_size() { - case "$1" in /dev/mtd[0-9]*) ;; *) return 0 ;; esac + case "$1" in + # A UBI volume holds reserved_ebs LEBs of usable_eb_size bytes each; + # ubiupdatevol refuses anything larger (UBI_IOCVOLUP's -EINVAL). + /dev/ubi[0-9]*_[0-9]*) + local d="$UBI_SYS/${1#/dev/}" ebs usable + ebs=$(cat "$d/reserved_ebs" 2>&3) && usable=$(cat "$d/usable_eb_size" 2>&3) || return 0 + [ -n "$ebs" ] && [ -n "$usable" ] && echo $((ebs * usable)) + return 0 ;; + /dev/mtd[0-9]*) ;; + *) return 0 ;; + esac local hex=$(grep "^${1#/dev/}:" /proc/mtd 2>/dev/null | cut -d' ' -f2) [ -n "$hex" ] && echo $((0x$hex)) } +# The character device of the UBI volume named $1 (/dev/ubi0_1), or nothing. +# +# By name, never by number: ubinize numbers the volumes, but a camera whose +# layout came from somewhere else -- U-Boot's `ubi create`, defib's installer +# -- numbers them in the order they were made. +ubi_volume() { + local d + for d in "$UBI_SYS"/ubi[0-9]*_[0-9]*; do + [ -f "$d/name" ] || continue + [ "$(cat "$d/name" 2>&3)" = "$1" ] && { echo "/dev/${d##*/}"; return 0; } + done + return 1 +} + +# Write image $1 to flash target $2: a UBI volume through ubiupdatevol, which +# replaces the volume's contents and trims the 0xFF tail of every LEB (#2519); +# anything else is an MTD partition and goes through flashcp as it always has. +write_image() { + case "$2" in + /dev/ubi[0-9]*_[0-9]*) set_progress ubiupdatevol "$2" "$1" ;; + *) set_progress flashcp -v "$1" "$2" ;; + esac +} + # Record that a write is about to land on flash that backs the RUNNING # filesystem, so reboot_system knows --no_reboot can no longer be honoured. # @@ -1732,8 +2054,27 @@ get_system_info() { [ -z "$model" ] && model="$soc" [ -z "$model" ] && die "Cannot determine SoC: no BUILD_PLATFORM and no U-Boot 'soc'" resolve_model_alias - kernel_device=$(get_device "kernel") - [ "$kernel_device" = "/dev/" ] && kernel_device=$(get_device "firmware") + detect_ubi_layout + # A ubiblock camera running an image from before the hand-off has no + # ::restart: entry, and only the upgrade being run would ship one. Its + # rootfs was written through gluebi's mtd until now, which works with the + # volume mounted (gluebi writes beside ubiblock's reader), so keep doing + # that rather than refusing every upgrade. The next image brings the + # entry, and the UBI path, with it. + if [ "$ubi_layout" = "ubiblock" ] && ! inittab_hands_off && + [ "/dev/" != "$(get_device "rootfs")" ]; then + ubi_layout= + fi + if [ -n "$ubi_layout" ]; then + # Resolved here, on the whole system: the flash phase may run in a ramfs + # where /sys came across best-effort, or not at all. + kernel_device=$(ubi_volume kernel) + ubi_rootfs_dev=$(ubi_volume rootfs) + ubi_data_dev=$(ubi_volume rootfs_data) + else + kernel_device=$(get_device "kernel") + [ "$kernel_device" = "/dev/" ] && kernel_device=$(get_device "firmware") + fi kernel_version=$(get_kernel_version "$kernel_device") system_version=$(get_system_version "") system_platform=$(get_system_platform "") @@ -1755,6 +2096,37 @@ get_system_info() { && root_on_flash=0 } +# Which NAND layout this camera boots, when its kernel and rootfs are UBI +# volumes rather than MTD partitions. Empty for everything else -- NOR, a raw +# NAND kernel partition, and a squashfs reached through gluebi's mtdblock, which +# the MTD path below already writes. +# +# ubifs kernel volume = FIT, rootfs volume = UBIFS (root=ubi0:rootfs). +# Fetches the -nand- package: fitImage. + rootfs.ubifs.. +# ubiblock kernel volume = uImage, rootfs volume = squashfs mounted through +# ubiblock (root=/dev/ubiblockX_Y). Its volumes hold exactly the +# NOR artifacts, so it fetches the NOR package. +# +# Either way the rootfs volume is in use for as long as the camera runs: UBIFS +# or ubiblock holds it open under init's overlay root, and ubiupdatevol needs +# the volume to itself (see need_handoff). So a rootfs write on either layout +# has PID 1 leave the flash first. See enter_ramfs. +detect_ubi_layout() { + ubi_layout= + [ -n "$(ubi_volume kernel)" ] && [ -n "$(ubi_volume rootfs)" ] || return 0 + if grep -qE '(^|[[:space:]])root=ubi[0-9]*:rootfs([[:space:]]|$)' /proc/cmdline 2>&3; then + ubi_layout=ubifs + elif grep -qE '(^|[[:space:]])root=/dev/ubiblock' /proc/cmdline 2>&3; then + ubi_layout=ubiblock + fi + return 0 +} + +# The package flavour this camera installs from, as the manifest spells it. +pkg_flash() { + [ "$ubi_layout" = "ubifs" ] && echo nand || echo nor +} + get_system_version() { grep "GITHUB_VERSION" "$1/etc/os-release" | head -1 | cut -d= -f2 | sed 's/"//g' } @@ -1780,12 +2152,12 @@ default_upgrade_url() { plat=$(get_system_platform) case "$plat" in *_*_*) - echo "https://github.com/OpenIPC/builder/releases/download/latest/$plat-nor.tgz" + echo "https://github.com/OpenIPC/builder/releases/download/latest/$plat-$(pkg_flash).tgz" return ;; 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-nor-$osr.tgz" + echo "https://github.com/OpenIPC/$repo/releases/download/latest/openipc.$model-$(pkg_flash)-$osr.tgz" } is_device_platform() { @@ -1898,7 +2270,8 @@ rewrites_live_flash() { [ "1" = "$update_rootfs" ] && return 0 [ "1" = "$image_combined" ] && return 0 [ "1" = "$clear_overlay" ] && return 0 - [ "1" = "$update_kernel" ] && [ "/dev/" = "$(get_device "kernel")" ] + # A UBI kernel volume is a volume of its own, never mounted. + [ -z "$ubi_layout" ] && [ "1" = "$update_kernel" ] && [ "/dev/" = "$(get_device "kernel")" ] } reboot_system() { @@ -1947,6 +2320,13 @@ reboot_system() { # Let the dog go, without the magic 'V' -- see watchdog_keep. [ -n "$wdog_keeper" ] && : > "$WDOG_RELEASE" busybox reboot -d 1 -f + # As PID 1 (a handed-off flash phase) the shell must outlive that delayed + # reboot: PID 1 exiting is a kernel panic, which does reboot -- after + # panic= seconds, and through an oops on the console that reads like a + # failed upgrade. + if [ "$$" = "1" ]; then + while :; do sleep 1; done + fi } # Do everything that writes flash, then reboot. A function because it is entered @@ -1978,7 +2358,7 @@ else # committed. exit_update means the rootfs is not being written at all, so # whether it would have fitted is not this run's business. [ "1" = "$update_rootfs" ] && [ "1" != "$exit_update" ] && - check_image_fits "$(rootfs_image)" "$(get_device "rootfs")" rootfs + check_image_fits "$(rootfs_image)" "$(rootfs_device)" rootfs [ "1" = "$update_kernel" ] && do_update_kernel "$kernel_file" [ "1" = "$update_rootfs" ] && do_update_rootfs "$rootfs_file" fi @@ -2148,6 +2528,9 @@ if [ "1" = "$_ramfs_phase" ]; then # missed -- so the whiteout replaced the stray directory it was meant to fix, # on upgrade and on factory reset alike (@usa-, majestic-webui#120). [ "1" = "$ram_root_shipped" ] || rmdir "/mnt$RAM_ROOT" 2>/dev/null + # Handed off: this is PID 1 now, and nothing else is left running from the + # old root, so it can finally be let go -- see need_handoff. + [ "1" = "$_handoff" ] && release_old_root flash_and_reboot fi @@ -2160,7 +2543,7 @@ if [ "1" = "$list_only" ]; then platform=$(grep '^BUILD_PLATFORM=' /etc/os-release 2>/dev/null | cut -d= -f2) if [ -n "$platform" ]; then platform="${model}_${platform#*_}"; else platform="${model}_${osr}"; fi here=$(get_build_id) - builds=$(awk -v p="$platform" '$2==p && $3=="nor" { print $1 }' "$MANIFEST_CACHE" \ + builds=$(awk -v p="$platform" -v f="$(pkg_flash)" '$2==p && $3==f { print $1 }' "$MANIFEST_CACHE" \ | head -n "${list_n:-20}") if [ -z "$builds" ]; then echo_c 33 "\nNo builds available for $platform yet." @@ -2243,6 +2626,13 @@ echo_c 37 "\nProtected: flashing continues even if this terminal disconnects." # failure here falls through and flashes in place exactly as before. if ! rewrites_live_flash; then echo_c 37 "\nFlashing in place: this run does not rewrite the filesystem the camera runs from." +elif need_handoff; then + # A mounted UBI volume cannot be rewritten in place at all (see + # need_handoff), so there is no fallback to fall back to: refuse while + # nothing is written. + enter_ramfs handoff + ramfs_discard + die "Could not move the flash phase into RAM; a mounted UBI volume cannot be rewritten without it. Nothing was written." elif ! enter_ramfs; then # The pivot did not take, so the in-place flash below needs none of what was # staged for it. enter_ramfs bails out from a dozen places and cannot tidy up diff --git a/general/package/goke-osdrv-gk7205v500/files/script/load_goke b/general/package/goke-osdrv-gk7205v500/files/script/load_goke index c3e777ac5..f659eabd8 100755 --- a/general/package/goke-osdrv-gk7205v500/files/script/load_goke +++ b/general/package/goke-osdrv-gk7205v500/files/script/load_goke @@ -23,8 +23,12 @@ os_mem_size=${os_mem_size:=32} # "Conflict MMZ ... to kernel memory". The osmem env can disagree with mem= # (e.g. gk7205v510 boots mem=70M while osmem=32M), so prefer the kernel value # (an explicit -osmem arg below still wins). -_kmem=$(grep -oE 'mem=[0-9]+M' /proc/cmdline | head -1 | tr -dc '0-9') -[ -n "$_kmem" ] && os_mem_size=$_kmem +# Only for the xmedia carve-out: with mmz_allocator=cma, mem= is the whole DDR +# and the MMZ is a CMA zone inside it, so there is no split to derive. +if ! grep -q "mmz_allocator=cma" /proc/cmdline; then + _kmem=$(grep -oE 'mem=[0-9]+M' /proc/cmdline | head -1 | tr -dc '0-9') + [ -n "$_kmem" ] && os_mem_size=$_kmem +fi YUV_TYPE0=0 # 0 -- raw, 1 --DC, 2 --bt1120, 3 --bt656 @@ -63,9 +67,42 @@ calc_mmz_info() { # xm_rgn dereferences the NULL it gets back from CMPI_GetModuleFuncById(SYS) -- # the `RGN_Init+0x1b` NULL oops at 0x0000000c reported on a GK7202V500 in #2428, # after which no vendor module loads and there is no video at all. +# CMA is the default, as on gk7205v200: Linux gets the whole DDR and the MMZ +# is a CMA zone reserved from mmz= on the command line. A command line that +# names no allocator yet -- a fresh install, an env from before this -- is +# rewritten here for the next boot; this boot keeps the carve-out. To stay on +# the carve-out, name it: mmz_allocator=xmedia in bootargs. +# +# Every other bootargs token is kept, in order: rebuilding bootargs from a +# fixed list once dropped ubi.mtd and left a NAND board unbootable (#2281). +# Keys the extras variable sets are left to it, so it is not carried twice. +check_allocator() { + grep -q mmz_allocator /proc/cmdline && return 0 + # Only where the kernel can reserve the zone: this script also loads + # gk7201v200, whose kernel has no CMA. + grep -q '^CmaTotal:' /proc/meminfo || return 0 + local bootargs extras kept osmem totalmem mmz_start mmz_size + bootargs=$(fw_printenv -n bootargs) + [ -n "$bootargs" ] || return 0 + extras=$(fw_printenv -n extras) + kept=$(echo ${bootargs} | awk -v extras="${extras}" 'BEGIN { RS = " "; n = split(extras, e, " "); for (i = 1; i <= n; i++) { k = e[i]; sub(/=.*/, "", k); skip[k] = 1 } skip["mem"] = skip["mmz"] = skip["mmz_allocator"] = 1 } { sub(/\n$/, ""); k = $0; sub(/=.*/, "", k); if ($0 != "" && !(k in skip)) printf "%s ", $0 }') + osmem=$(fw_printenv -n osmem | tr -d 'M'); osmem=${osmem:-32} + totalmem=$(fw_printenv -n totalmem | tr -d 'M'); totalmem=${totalmem:-64} + mmz_start=$(echo "$mem_start $osmem" | awk '{ printf("0x%x00000\n", $1/1024/1024 + $2) }') + mmz_size=$((totalmem - osmem))M + echo "Media memory: switching to CMA from the next boot (mem=${totalmem}M)" + fw_setenv bootargs mem=${totalmem}M ${kept}mmz_allocator=cma mmz=anonymous,0,${mmz_start},${mmz_size} ${extras} +} + insert_osal() { - calc_mmz_info - modprobe xm_osal mmz_allocator=xmedia mmz=anonymous,0,$mmz_start,$mmz_size || report_error + check_allocator + MMZ=$(awk -F '=' '$1=="mmz"{print $2}' RS=" " /proc/cmdline) + if [ -n "$MMZ" ] && grep -q "mmz_allocator=cma" /proc/cmdline; then + modprobe xm_osal mmz_allocator=cma mmz=$MMZ || report_error + else + calc_mmz_info + modprobe xm_osal mmz_allocator=xmedia mmz=anonymous,0,$mmz_start,$mmz_size || report_error + fi } insert_detect() { diff --git a/general/package/hisilicon-opensdk/hisilicon-opensdk.mk b/general/package/hisilicon-opensdk/hisilicon-opensdk.mk index 32e2a9fb0..cd9abc426 100644 --- a/general/package/hisilicon-opensdk/hisilicon-opensdk.mk +++ b/general/package/hisilicon-opensdk/hisilicon-opensdk.mk @@ -5,7 +5,7 @@ ################################################################################ HISILICON_OPENSDK_SITE = $(call github,openipc,openhisilicon,$(HISILICON_OPENSDK_VERSION)) -HISILICON_OPENSDK_VERSION = ecbc855ae41f0d412afb7e775824fcdbf3526428 +HISILICON_OPENSDK_VERSION = dfc3a81022441e579fc6342755b993c7c4b433e7 HISILICON_OPENSDK_LICENSE = GPL-3.0 HISILICON_OPENSDK_LICENSE_FILES = LICENSE diff --git a/general/scripts/rootfs_script.sh b/general/scripts/rootfs_script.sh index 39d04aa0d..8625a8c6f 100755 --- a/general/scripts/rootfs_script.sh +++ b/general/scripts/rootfs_script.sh @@ -107,6 +107,31 @@ if [ -f "${LATE_POST_BUILD_HOOKS}" ]; then done < "${LATE_POST_BUILD_HOOKS}" fi +# NAND FIT: a board whose NAND image carries the kernel as a FIT in its +# `kernel` UBI volume ships board//nand-fit.its. ubinize packs the +# volumes right after this script and before post-image, so the FIT has to +# exist by now. The kernel and its DTB come straight from the kernel tree -- +# BINARIES_DIR only gets the uImage, which has the DTB appended and is what the +# NOR image still boots. +NAND_FIT_ITS="${BR2_EXTERNAL_GENERAL_PATH}/../br-ext-chip-${OPENIPC_SOC_VENDOR}/board/${OPENIPC_SOC_FAMILY}/nand-fit.its" +# One built for another board in a reused output directory must not ride along: +# repack packs whatever fitImage it finds. (cv6xx makes its own in post-image, +# which runs after this.) +rm -f "${BINARIES_DIR}/fitImage" +if [ -f "${NAND_FIT_ITS}" ] && grep -q "^BR2_TARGET_ROOTFS_UBI=y" "${BR2_CONFIG}"; then + KBOOT=$(ls -d "${BUILD_DIR}"/linux-*/arch/arm/boot 2>/dev/null | grep -v headers | head -1) + FIT_DIR="${BINARIES_DIR}/nand-fit" + rm -rf "${FIT_DIR}" && mkdir -p "${FIT_DIR}" || exit 1 + sed "s/@SOC@/${OPENIPC_SOC_MODEL}/" "${NAND_FIT_ITS}" > "${FIT_DIR}/nand-fit.its" || exit 1 + cp "${KBOOT}/zImage" "${FIT_DIR}/" || { echo "NAND FIT: no zImage in ${KBOOT}" >&2; exit 1; } + # Every DTB the .its names, from the kernel's dts output. + for dtb in $(grep -o '/incbin/("[^"]*\.dtb")' "${NAND_FIT_ITS}" | sed 's/.*("\(.*\)")/\1/'); do + cp "${KBOOT}/dts/${dtb}" "${FIT_DIR}/" || { echo "NAND FIT: no ${dtb} in ${KBOOT}/dts" >&2; exit 1; } + done + "${HOST_DIR}/bin/mkimage" -f "${FIT_DIR}/nand-fit.its" "${BINARIES_DIR}/fitImage" || exit 1 + rm -rf "${FIT_DIR}" +fi + # Root's login shell on an unclaimed camera is /usr/sbin/openipc-claim (see # overlay/etc/passwd), and dropbear checks a login shell against /etc/shells # through getusershell() BEFORE it ever runs -- an unlisted shell is rejected at