mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
- Controllers stop at K80, before forgectrl at K90: runlevel 0/6 no longer tears down the cooling engine, fire gates, and broker while a controller may still be executing a job. - The grblhal/gfcloud init scripts are real emergency levers: stop routes through the supervisor (POST /controller/stop - a bare pkill was safed and respawned seconds later), start resumes supervision, status exists, and the pkill fallback matches full executable paths instead of truncated names or bare substrings. - slotmigrate: the partition grow gets the same 2048-sector tolerance as the filesystem branch (an exact compare rewrote the MBR at S02 on every boot on disks where the grow cannot land on the last sector), verifies it made progress, and the resize2fs retry is bounded at three attempts with the counter kept on p3 itself. - Installer: archive product/platform are verified after the signature, and a validly signed OLDER release now requires an explicit yes instead of installing as a silent downgrade. All predictable /tmp paths in the installer and ffboot are mktemp now. - release.sh rejects multiple positional versions (the last one used to win silently) and a release without factory-era verification dies unless explicitly bypassed; mkfw.sh refuses to pack when the public key for the post-sign self-check is missing. - forgefirm-logrotate: size-capped rotation (boot + hourly) for the /data logs - a full /data breaks settings, update staging, and the controllers own writes. - Bench build scripts derive every path from their own location or FF_SRC_TOP/FF_BUILD_TOP and log to mktemp files.
237 lines
7.9 KiB
Bash
237 lines
7.9 KiB
Bash
#!/bin/sh
|
|
# (C) Copyright 2020-2026
|
|
# Scott Wiederhold, s.e.wiederhold@gmail.com
|
|
# https://community.openglow.org
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Boot-slot tool for Glowforge factory hardware running ForgeFIRM or
|
|
# factory firmware: inventories what is installed on each bootable
|
|
# partition and switches the boot target by rewriting the saved U-Boot
|
|
# environment (mmcdev/mmchwpart/mmcpart/mmcroot).
|
|
#
|
|
# The flip writes all four variables in one fw_setenv -s transaction and
|
|
# read-back verifies them, retrying with the classic u-boot-tools script
|
|
# format ("name value") if the libubootenv format ("name=value") did not
|
|
# take, and per-variable calls as a last resort. A switch target must
|
|
# pass a content probe (rootfs mounts, kernel present) unless forced.
|
|
|
|
usage () {
|
|
cat <<END
|
|
usage: ffboot -l | -s|-e[<partition>] [-n] [-f]
|
|
-l: inventory bootable partitions (key=value lines) and exit
|
|
-s: SDCARD OpenGlow/ForgeFIRM
|
|
-e[<partition>]: eMMC
|
|
1: eMMC slot 1 (factory image 1)
|
|
2: eMMC slot 2 (factory image 2)
|
|
4: legacy OpenGlow/ForgeFIRM partition
|
|
Default: factory image with the most recent firmware
|
|
-n: No reboot
|
|
-f: Force - skip the target content probe
|
|
END
|
|
}
|
|
|
|
# --- fw_env config selection -------------------------------------------------
|
|
# The env lives on the eMMC user area (0x80000/0x82000, redundant). Factory
|
|
# firmware ships per-device configs; the mmcblk2-specific override is honored
|
|
# if present, otherwise the standard /etc/fw_env.config is used.
|
|
if [ -f "/etc/fw_env_mmcblk2.config" ] && [ ! -d "/factory" ]; then
|
|
FWCONFIG="/etc/fw_env_mmcblk2.config"
|
|
else
|
|
FWCONFIG="/etc/fw_env.config"
|
|
fi
|
|
|
|
BOOTED_ROOT=$(sed -n 's/.*root=\([^ ]*\).*/\1/p' /proc/cmdline)
|
|
|
|
# --- partition probe ---------------------------------------------------------
|
|
# probe_part <device>
|
|
# Sets: P_PRESENT P_STATE(ok|empty|unreadable) P_TYPE(forgefirm|factory|unknown)
|
|
# P_VERSION (display) P_DATE (build datetime, for ordering) P_KERNEL(yes|no)
|
|
probe_part () {
|
|
P_PRESENT=no; P_STATE=unreadable; P_TYPE=unknown
|
|
P_VERSION=""; P_DATE=""; P_KERNEL=no
|
|
P_MOUNTED=""
|
|
[ -b "$1" ] || return 1
|
|
P_PRESENT=yes
|
|
if [ "$1" = "$BOOTED_ROOT" ]; then
|
|
ROOT_DIR=""
|
|
else
|
|
# Reuse an existing mount (the image keeps the factory slots mounted
|
|
# under /factory); a fresh mount is explicit -t ext4 - letting mount
|
|
# iterate types provokes a cosmetic kernel "Can't open blockdev" for
|
|
# each foreign-type claim against an already-mounted device.
|
|
ROOT_DIR=$(sed -n "s|^$1 \([^ ]*\).*|\1|p" /proc/mounts | head -n 1)
|
|
if [ -z "$ROOT_DIR" ]; then
|
|
P_MOUNTED=yes
|
|
ROOT_DIR=$(mktemp -d /tmp/ffboot.probe.XXXXXX) || return 1
|
|
if ! mount -o ro -t ext4 "$1" "$ROOT_DIR" 2>/dev/null; then
|
|
rmdir "$ROOT_DIR" 2>/dev/null
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
if [ -f "$ROOT_DIR/etc/forgefirm-version" ]; then
|
|
P_TYPE=forgefirm; P_STATE=ok
|
|
P_VERSION=$(cat "$ROOT_DIR/etc/forgefirm-version")
|
|
elif [ -f "$ROOT_DIR/etc/version" ]; then
|
|
P_TYPE=factory; P_STATE=ok
|
|
# The build datetime orders releases reliably (used by -e); the
|
|
# semantic FIRMWARE_VERSION in /etc/build is what the factory calls
|
|
# the release and what we display (fall back to the datetime).
|
|
P_DATE=$(cat "$ROOT_DIR/etc/version")
|
|
FV=$(sed -n 's/^FIRMWARE_VERSION[[:space:]]*=[[:space:]]*\([^[:space:]]*\).*/\1/p' \
|
|
"$ROOT_DIR/etc/build" 2>/dev/null)
|
|
if [ -n "$FV" ]; then
|
|
P_VERSION="v$FV"
|
|
else
|
|
P_VERSION="$P_DATE"
|
|
fi
|
|
else
|
|
P_STATE=empty
|
|
fi
|
|
[ -f "$ROOT_DIR/boot/zImage" ] && P_KERNEL=yes
|
|
if [ -n "$P_MOUNTED" ]; then
|
|
umount "$ROOT_DIR" 2>/dev/null
|
|
rmdir "$ROOT_DIR" 2>/dev/null
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
env_get () {
|
|
fw_printenv -c "$FWCONFIG" -n "$1" 2>/dev/null
|
|
}
|
|
|
|
# --- inventory ---------------------------------------------------------------
|
|
inventory () {
|
|
E_DEV=$(env_get mmcdev); E_PART=$(env_get mmcpart); E_ROOT=$(env_get mmcroot)
|
|
echo "env.mmcdev=$E_DEV"
|
|
echo "env.mmchwpart=$(env_get mmchwpart)"
|
|
echo "env.mmcpart=$E_PART"
|
|
echo "env.mmcroot=$E_ROOT"
|
|
echo "booted.root=$BOOTED_ROOT"
|
|
for ENTRY in "sd /dev/mmcblk1p1" "a /dev/mmcblk2p1" "b /dev/mmcblk2p2" "legacy /dev/mmcblk2p4"; do
|
|
NAME=${ENTRY%% *}; DEV=${ENTRY#* }
|
|
probe_part "$DEV"
|
|
echo "slot.$NAME.device=$DEV"
|
|
echo "slot.$NAME.present=$P_PRESENT"
|
|
[ "$P_PRESENT" = "yes" ] || continue
|
|
echo "slot.$NAME.state=$P_STATE"
|
|
echo "slot.$NAME.type=$P_TYPE"
|
|
echo "slot.$NAME.version=$P_VERSION"
|
|
echo "slot.$NAME.kernel=$P_KERNEL"
|
|
[ "$DEV" = "$BOOTED_ROOT" ] && echo "slot.$NAME.booted=yes"
|
|
[ "$DEV" = "$E_ROOT" ] && echo "slot.$NAME.next=yes"
|
|
done
|
|
}
|
|
|
|
# --- verified env flip -------------------------------------------------------
|
|
# set_env <mmcdev> <mmchwpart> <mmcpart> <mmcroot>
|
|
env_verify () {
|
|
[ "$(env_get mmcdev)" = "$1" ] && [ "$(env_get mmchwpart)" = "$2" ] && \
|
|
[ "$(env_get mmcpart)" = "$3" ] && [ "$(env_get mmcroot)" = "$4" ]
|
|
}
|
|
|
|
set_env () {
|
|
SCRIPT=$(mktemp /tmp/ffboot.env.XXXXXX) || return 1
|
|
# libubootenv format
|
|
printf 'mmcdev=%s\nmmchwpart=%s\nmmcpart=%s\nmmcroot=%s\n' "$1" "$2" "$3" "$4" > "$SCRIPT"
|
|
fw_setenv -c "$FWCONFIG" -s "$SCRIPT" 2>/dev/null
|
|
if env_verify "$1" "$2" "$3" "$4"; then rm -f "$SCRIPT"; return 0; fi
|
|
# classic u-boot-tools format
|
|
printf 'mmcdev %s\nmmchwpart %s\nmmcpart %s\nmmcroot %s\n' "$1" "$2" "$3" "$4" > "$SCRIPT"
|
|
fw_setenv -c "$FWCONFIG" -s "$SCRIPT" 2>/dev/null
|
|
rm -f "$SCRIPT"
|
|
if env_verify "$1" "$2" "$3" "$4"; then return 0; fi
|
|
# last resort: per-variable
|
|
fw_setenv -c "$FWCONFIG" mmcdev "$1" && \
|
|
fw_setenv -c "$FWCONFIG" mmchwpart "$2" && \
|
|
fw_setenv -c "$FWCONFIG" mmcpart "$3" && \
|
|
fw_setenv -c "$FWCONFIG" mmcroot "$4"
|
|
env_verify "$1" "$2" "$3" "$4"
|
|
}
|
|
|
|
# --- argument parsing --------------------------------------------------------
|
|
REBOOT=1
|
|
FORCE=0
|
|
MODE=""
|
|
|
|
for ARG in "$@"; do
|
|
case "$ARG" in
|
|
-l) MODE=list ;;
|
|
-n) REBOOT=0 ;;
|
|
-f) FORCE=1 ;;
|
|
-s|-e|-e1|-e2|-e4)
|
|
[ -z "$MODE" ] || { usage; exit 2; }
|
|
MODE="$ARG" ;;
|
|
*) usage; exit 2 ;;
|
|
esac
|
|
done
|
|
[ -n "$MODE" ] || { usage; exit 2; }
|
|
|
|
if [ ! -f "$FWCONFIG" ]; then
|
|
echo "ERROR: $FWCONFIG not found; cannot access the U-Boot environment" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$MODE" = "list" ]; then
|
|
inventory
|
|
exit 0
|
|
fi
|
|
|
|
# --- resolve the switch target ----------------------------------------------
|
|
case "$MODE" in
|
|
-s)
|
|
MMCDEV=0; MMCPART=1 ;;
|
|
-e)
|
|
# Pick the factory slot with the newest firmware; a slot occupied by
|
|
# ForgeFIRM is not a factory-restore target.
|
|
MMCDEV=1; MMCPART=""
|
|
BEST=0
|
|
for CAND in 1 2; do
|
|
probe_part "/dev/mmcblk2p$CAND" || continue
|
|
[ "$P_TYPE" = "factory" ] || continue
|
|
# Order by the build datetime (monotonic), not the semantic
|
|
# version string.
|
|
V=$(echo "$P_DATE" | tr -cd '0-9')
|
|
[ -n "$V" ] || V=0
|
|
if [ -z "$MMCPART" ] || [ "$V" -gt "$BEST" ]; then
|
|
MMCPART=$CAND; BEST=$V
|
|
fi
|
|
done
|
|
[ -n "$MMCPART" ] || {
|
|
echo "ERROR: no factory image found in eMMC slot 1 or 2." >&2
|
|
echo "Use -e1/-e2/-e4 to select a slot explicitly." >&2
|
|
exit 1
|
|
} ;;
|
|
-e1) MMCDEV=1; MMCPART=1 ;;
|
|
-e2) MMCDEV=1; MMCPART=2 ;;
|
|
-e4) MMCDEV=1; MMCPART=4 ;;
|
|
esac
|
|
|
|
MMCROOT="/dev/mmcblk$((MMCDEV + 1))p$MMCPART"
|
|
|
|
# --- target sanity probe -----------------------------------------------------
|
|
if [ "$FORCE" -eq 0 ]; then
|
|
if ! probe_part "$MMCROOT"; then
|
|
echo "ERROR: $MMCROOT is missing or unreadable (use -f to override)" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$P_STATE" != "ok" ] || [ "$P_KERNEL" != "yes" ]; then
|
|
echo "ERROR: $MMCROOT does not look bootable (state=$P_STATE kernel=$P_KERNEL; use -f to override)" >&2
|
|
exit 1
|
|
fi
|
|
echo "Target $MMCROOT: $P_TYPE $P_VERSION"
|
|
fi
|
|
|
|
echo "Setting boot to $MMCROOT"
|
|
if ! set_env "$MMCDEV" 0 "$MMCPART" "$MMCROOT"; then
|
|
echo "ERROR: environment write did not verify; boot selection unchanged or inconsistent" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$REBOOT" -gt 0 ]; then
|
|
echo "Rebooting..."
|
|
reboot
|
|
fi
|
|
|
|
exit 0
|