mirror of
https://github.com/raspberrypi/rpi-eeprom.git
synced 2026-01-20 21:13:36 +08:00
Function does not necessarily check for latest version. Also, it does not actually get the update, just the version, so rename function to 'getUpdateVersion' to match 'getCurrentVersion'. Similarly with 2 variable names.
412 lines
13 KiB
Bash
Executable File
412 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
# Raspberry Pi4 boot EEPROM updater.
|
|
|
|
set -e
|
|
|
|
script_dir=$(cd "$(dirname "$0")" && pwd)
|
|
|
|
if [ -f /etc/default/rpi-eeprom-update ]; then
|
|
. /etc/default/rpi-eeprom-update
|
|
fi
|
|
|
|
FIRMWARE_ROOT=${FIRMWARE_ROOT:-/lib/firmware/raspberrypi/bootloader}
|
|
# May be used to select beta releases instead of the default critical
|
|
# updates.
|
|
FIRMWARE_RELEASE_STATUS=${FIRMWARE_RELEASE_STATUS:-critical}
|
|
FIRMWARE_IMAGE_DIR=${FIRMWARE_IMAGE_DIR:-${FIRMWARE_ROOT}/${FIRMWARE_RELEASE_STATUS}}
|
|
FIRMWARE_BACKUP_DIR=${FIRMWARE_BACKUP_DIR:-/var/lib/raspberrypi/bootloader/backup}
|
|
USE_FLASHROM=${USE_FLASHROM:-0}
|
|
RECOVERY_BIN=${RECOVERY_BIN:-${FIRMWARE_ROOT}/recovery.bin}
|
|
BOOTFS=${BOOTFS:-/boot}
|
|
|
|
EXIT_SUCCESS=0
|
|
EXIT_UPDATE_REQUIRED=1
|
|
EXIT_FAILED=2
|
|
EXIT_EEPROM_FROZEN=3
|
|
# Reserved
|
|
# EXIT_PREVIOUS_UPDATE_FAILED=4
|
|
|
|
OVERWRITE_CONFIG=0
|
|
# Maximum safe SPI speed for EEPROM access 16000, slower is ok.
|
|
SPI_SPEED=16000
|
|
# Timestamp for first release which doesn't have a timestamp field
|
|
FIRST_VERSION=1557513636
|
|
EEPROM_SIZE=524288
|
|
|
|
# Simple bootloader which is able to load start.elf in the event of a power
|
|
# cut. This runs SDRAM at low speed and may have reduced functionality but
|
|
# should be enough to run flashrom again.
|
|
|
|
TMP_EEPROM_IMAGE=""
|
|
TMP_BOOTFS_MNT=""
|
|
|
|
cleanup() {
|
|
if [ -f "${TMP_EEPROM_IMAGE}" ]; then
|
|
rm -f "${TMP_EEPROM_IMAGE}"
|
|
fi
|
|
if [ -f "${TMP_EEPROM_CONFIG}" ]; then
|
|
rm -f "${TMP_EEPROM_CONFIG}"
|
|
fi
|
|
if [ -d "${TMP_BOOTFS_MNT}" ]; then
|
|
umount "${TMP_BOOTFS_MNT}"
|
|
rmdir "${TMP_BOOTFS_MNT}"
|
|
fi
|
|
TMP_BOOTFS_MNT=
|
|
TMP_EEPROM_IMAGE=
|
|
TMP_EEPROM_CONFIG=
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
die() {
|
|
echo "$@" >&2
|
|
exit ${EXIT_FAILED}
|
|
}
|
|
|
|
prepareImage()
|
|
{
|
|
eeprom_image="$1"
|
|
|
|
[ -f "${eeprom_image}" ] || die "EEPROM image \'${eeprom_image}\' not found"
|
|
TMP_EEPROM_IMAGE="$(mktemp)"
|
|
TMP_EEPROM_CONFIG="$(mktemp)"
|
|
|
|
mkdir -p "${FIRMWARE_BACKUP_DIR}"
|
|
|
|
# Backup the configuration of the currently loaded bootloader
|
|
vcgencmd bootloader_config > "${TMP_EEPROM_CONFIG}"
|
|
backup="${FIRMWARE_BACKUP_DIR}/pieeprom-backup-$(date +%Y%m%d-%H%M%S).conf"
|
|
cp -f "${TMP_EEPROM_CONFIG}" "${backup}"
|
|
|
|
if [ "$(wc -l "${TMP_EEPROM_CONFIG}" | awk '{print $1}')" -lt 3 ]; then
|
|
# Don't propagate empty EEPROM config files and also prevent the initial
|
|
# bootloader config with WAKE_ON_GPIO=0 propgating to newer versions by
|
|
# accident.
|
|
OVERWRITE_CONFIG=1
|
|
fi
|
|
|
|
cp -f "${eeprom_image}" "${TMP_EEPROM_IMAGE}"
|
|
|
|
if [ "${OVERWRITE_CONFIG}" = 0 ]; then
|
|
"${script_dir}/rpi-eeprom-config" \
|
|
--out "${TMP_EEPROM_IMAGE}" \
|
|
--config "${TMP_EEPROM_CONFIG}" "${eeprom_image}"
|
|
fi
|
|
}
|
|
|
|
applyRecoveryUpdate()
|
|
{
|
|
eeprom_image="$1"
|
|
[ -f "${eeprom_image}" ] || die "${eeprom_image} not found"
|
|
TMP_EEPROM_IMAGE="$(mktemp)"
|
|
|
|
findBootFS
|
|
prepareImage "${eeprom_image}"
|
|
|
|
# If recovery.bin encounters pieeprom.upd then it will select it in
|
|
# preference to pieeprom.bin. The .upd file also causes recovery.bin
|
|
# to rename itself to recovery.000 and reboot if the update is successful.
|
|
# The rename causes the ROM to ignore this file and use the newly flashed
|
|
# EEPROM image instead.
|
|
cp -f "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \
|
|
|| die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}"
|
|
|
|
cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" \
|
|
|| die "Failed to copy ${RECOVERY_BIN} to ${BOOTFS}"
|
|
}
|
|
|
|
applyUpdate() {
|
|
eeprom_image="$1"
|
|
|
|
[ "$(id -u)" = "0" ] || die "* Must be run as root - try 'sudo rpi-eeprom-update'"
|
|
|
|
if [ "${USE_FLASHROM}" = 0 ]; then
|
|
applyRecoveryUpdate "${eeprom_image}"
|
|
return
|
|
fi
|
|
|
|
# Bootloader EEPROM chip-select is muxed with audio pin so disable audio
|
|
# LDO first to avoid sending noise to analog audio.
|
|
/opt/vc/bin/vcmailbox 0x00030056 4 4 0 > /dev/null || true
|
|
dtparam audio=off
|
|
|
|
# Switch the SPI pins to boot EEPROM
|
|
dtoverlay spi-gpio40-45
|
|
modprobe spidev
|
|
modprobe spi-bcm2835
|
|
|
|
prepareImage "${eeprom_image}"
|
|
|
|
echo "Applying update ${eeprom_image}"
|
|
flashrom -p "linux_spi:dev=/dev/spidev0.0,spispeed=${SPI_SPEED}" -w "${TMP_EEPROM_IMAGE}" || die "flashrom EEPROM update failed"
|
|
|
|
dtparam -R spi-gpio40-45
|
|
dtparam audio=on
|
|
/opt/vc/bin/vcmailbox 0x00030056 4 4 1 > /dev/null || true
|
|
}
|
|
|
|
# Use the version reported by the loaded EEPROM instead of attempting to retrieve
|
|
# this via flashrom to avoid unnecessary audio glitches.
|
|
CURRENT_VERSION=
|
|
getCurrentVersion() {
|
|
if vcgencmd bootloader_version | grep -q timestamp; then
|
|
CURRENT_VERSION=$(vcgencmd bootloader_version | grep timestamp | awk '{print $2}')
|
|
if [ "${CURRENT_VERSION}" = "0" ]; then
|
|
# If a timestamp of zero is returned then it's new firmware but an
|
|
# old bootloader. Assume bootloader v0
|
|
CURRENT_VERSION="${FIRST_VERSION}"
|
|
fi
|
|
else
|
|
# New bootloader / old firmware ? Try to parse the date
|
|
CURRENT_VERSION=$(date -u +%s --date "$(vcgencmd bootloader_version | head -n1)")
|
|
fi
|
|
|
|
# Failed to parse the version. Default to the initial production release.
|
|
if [ -z "${CURRENT_VERSION}" ]; then
|
|
CURRENT_VERSION="${FIRST_VERSION}"
|
|
fi
|
|
}
|
|
|
|
# Set to the latest critical firmware version
|
|
UPDATE_IMAGE=""
|
|
UPDATE_VERSION=0
|
|
getUpdateVersion() {
|
|
UPDATE_VERSION=0
|
|
match=".*/pieeprom-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].bin"
|
|
latest="$(find "${FIRMWARE_IMAGE_DIR}" -maxdepth 1 -type f -size "${EEPROM_SIZE}c" -regex "${match}" | sort -r | head -n1)"
|
|
if [ -f "${latest}" ]; then
|
|
UPDATE_VERSION=$(strings "${latest}" | grep BUILD_TIMESTAMP | sed 's/.*=//g')
|
|
UPDATE_IMAGE="${latest}"
|
|
fi
|
|
}
|
|
|
|
checkDependencies() {
|
|
CPU_VER="$(vcgencmd otp_dump | grep 30: | cut -c8)"
|
|
|
|
if [ "${CPU_VER}" != "3" ]; then
|
|
# Not a BCM2711, no EEPROMs to update.
|
|
exit ${EXIT_SUCCESS}
|
|
fi
|
|
|
|
if [ ! -d "${FIRMWARE_IMAGE_DIR}" ]; then
|
|
die "Bootloader updates directory ${FIRMWARE_IMAGE_DIR} not found."
|
|
fi
|
|
|
|
if vcgencmd bootloader_config | grep -qi "Command not registered"; then
|
|
die "vcgencmd: 'bootloader_config' command not supported. Please update VC firmware"
|
|
fi
|
|
|
|
if ! flashrom --version > /dev/null 2>&1; then
|
|
[ "${USE_FLASHROM}" = 0 ] || die "flashrom not found."
|
|
fi
|
|
|
|
if [ "${USE_FLASHROM}" = 0 ]; then
|
|
[ -f "${RECOVERY_BIN}" ] || die "${RECOVERY_BIN} not found."
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
rpi-eeprom-update [options]... [FILE]
|
|
Checks whether the Raspberry Pi bootloader EEPROM is up-to-date and
|
|
optionally updates the EEPROM at the next reboot.
|
|
|
|
The default update mechanism writes recovery.bin and pieeprom.upd to the
|
|
boot partition on the sd-card. At the next reboot the ROM runs recovery.bin
|
|
which flashes pieeprom.upd to the EEPROM. If the EEPROM update was successful
|
|
recovery.bin renames itself to recovery.000 to prevent it from running a
|
|
second time then resets the system. The system should then boot normally.
|
|
|
|
If /boot does not correspond to the boot partition on the sd-card and this
|
|
is not a NOOBS system then the mount point for BOOTFS should be defined
|
|
in /etc/default/rpi-eeprom-update
|
|
|
|
For reference, the flashrom update mechanism may be enabled by defining
|
|
USE_FLASHROM=1 in /etc/default/rpi-eeprom-update. This not recommended
|
|
because the SPI pins are muxed with audio and other device drivers may
|
|
be using SPI (e.g. HATs). This is also not safe in the event of a power
|
|
failure during the update of the EEPROM.
|
|
|
|
A backup of the current EEPROM config file is written to ${FIRMWARE_BACKUP_DIR}
|
|
before applying the update.
|
|
|
|
-a Install the latest update if necessary
|
|
-d Use the default bootloader config instead of migrating the current settings
|
|
-f Install the given file instead of the latest critical update
|
|
Ignores the FREEZE_VERSION flag in bootloader and is intended for manual
|
|
firmware updates.
|
|
WARNING: This command should only be run from console mode in order to
|
|
avoid conflicts/deadlock with dtoverlay/dtparam settings.
|
|
-h Display help text and exit
|
|
-j Write status information using JSON notation
|
|
-m Write status information to the given file when run without -a or -f
|
|
|
|
To extract the configuration file from an EEPROM image
|
|
rpi-eeprom-config pieeprom.bin --out bootconf.txt
|
|
|
|
To update the configuration file in an EEPROM image
|
|
rpi-eeprom-config pieeprom.bin --config bootconf.txt --out pieeprom-new.bin
|
|
|
|
To flash the new image
|
|
sudo rpi-eeprom-update -d -f ./pieeprom-new.bin
|
|
|
|
The syntax is the same as config.txt but section filters etc are not supported. See
|
|
online documentation for the list of parameters.
|
|
|
|
The official documentation for the Raspberry Pi bootloader EEPROM is available at
|
|
https://www.raspberrypi.org/documentation/hardware/raspberrypi/booteeprom.md
|
|
|
|
EOF
|
|
exit ${EXIT_SUCCESS}
|
|
}
|
|
|
|
printVersions()
|
|
{
|
|
cur="$1"
|
|
new="$2"
|
|
echo "CURRENT: $(date -u "-d@${cur}") (${cur})"
|
|
echo " LATEST: $(date -u "-d@${new}") (${new})"
|
|
}
|
|
|
|
findBootFS()
|
|
{
|
|
# recovery.bin is loaded by the ROM from the boot partition, this is normally
|
|
# ${BOOTFS} but on NOOBS this is /dev/mmcblk0p1 with volume label RECOVERY
|
|
# If ${BOOTFS} is not writable OR is not on /dev/mmcblk0 then error because the ROM
|
|
# can only load recovery.bin from the on-board SD-CARD slot or the EEPROM.
|
|
|
|
# To skip installing the safe mode recovery.bin use the -s option
|
|
if blkid | grep -qE "/dev/mmcblk0p1.*LABEL_FATBOOT.*RECOVERY.*TYPE.*vfat"; then
|
|
TMP_BOOTFS_MNT="$(mktemp -d)"
|
|
mount /dev/mmcblk0p1 "${TMP_BOOTFS_MNT}"
|
|
BOOTFS="${TMP_BOOTFS_MNT}"
|
|
fi
|
|
|
|
# If BOOTFS is not a directory or doesn't contain any .elf files then
|
|
# it's probably not the boot partition so assume that it cannot be used for a
|
|
# safe mode recovery
|
|
[ -d "${BOOTFS}" ] || die "BOOTFS: \"${BOOTFS}\" is not a directory"
|
|
[ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" -gt 0 ] || die "BOOTFS: \"${BOOTFS}\" contains no .elf files"
|
|
}
|
|
|
|
checkAndApply()
|
|
{
|
|
getCurrentVersion
|
|
getUpdateVersion
|
|
|
|
if [ "${UPDATE_VERSION}" -gt "${CURRENT_VERSION}" ]; then
|
|
printVersions "${CURRENT_VERSION}" "${UPDATE_VERSION}"
|
|
echo "*** INSTALLING REQUIRED UPDATE ***"
|
|
applyUpdate "${UPDATE_IMAGE}"
|
|
echo "Bootloader EEPROM update pending. Please reboot to apply the update."
|
|
else
|
|
echo "Bootloader EEPROM is up to date. $(date -d@${CURRENT_VERSION})"
|
|
fi
|
|
}
|
|
|
|
fileUpdate()
|
|
{
|
|
echo "*** INSTALLING ${1} ***"
|
|
[ -f "${1}" ] || die "\"${1}\" not found"
|
|
applyUpdate "$1"
|
|
echo "Bootloader EEPROM update pending. Please reboot to apply the update."
|
|
}
|
|
|
|
removePreviousUpdates()
|
|
{
|
|
if [ "$(id -u)" = "0" ]; then
|
|
findBootFS
|
|
|
|
# Remove any stale recovery.bin files or EEPROM images
|
|
# N.B. recovery.bin is normally ignored by the ROM if is not a valid
|
|
# executable but it's best to not have the file at all.
|
|
rm -f "${BOOTFS}/recovery.bin"
|
|
rm -f "${BOOTFS}/pieeprom.bin" "${BOOTFS}/pieeprom.upd"
|
|
# Case insensitive for FAT bootfs
|
|
find "${BOOTFS}" -maxdepth 1 -type f -iname "recovery.*" -regex ".*\.[0-9][0-9][0-9]$" -exec rm -f {} \;
|
|
fi
|
|
}
|
|
|
|
checkVersion()
|
|
{
|
|
getCurrentVersion
|
|
getLatestVersion
|
|
if [ "${UPDATE_VERSION}" -gt "${CURRENT_VERSION}" ]; then
|
|
echo "*** UPDATE REQUIRED ***"
|
|
printVersions "${CURRENT_VERSION}" "${UPDATE_VERSION}"
|
|
write_status_info EXIT_UPDATE_REQUIRED "${CURRENT_VERSION}" "${UPDATE_VERSION}"
|
|
exit ${EXIT_UPDATE_REQUIRED}
|
|
else
|
|
echo "Bootloader EEPROM is up to date"
|
|
printVersions "${CURRENT_VERSION}" "${UPDATE_VERSION}"
|
|
write_status_info EXIT_SUCCESS "${CURRENT_VERSION}" "${UPDATE_VERSION}"
|
|
exit ${EXIT_SUCCESS}
|
|
fi
|
|
}
|
|
|
|
write_status_info()
|
|
{
|
|
[ -z "${MACHINE_OUTPUT}" ] && return 0
|
|
|
|
exit_code="${1}"
|
|
cur=${2}
|
|
new=${3}
|
|
|
|
if [ "${JSON_OUTPUT}" = "no" ]; then
|
|
cat > "${MACHINE_OUTPUT}" <<EOF
|
|
EXITCODE="${exit_code}"
|
|
CURRENT_TS=${cur}
|
|
LATEST_TS=${new}
|
|
EOF
|
|
else
|
|
cat > "${MACHINE_OUTPUT}" <<EOF
|
|
{
|
|
"EXITCODE": "${exit_code}",
|
|
"CURRENT_TS": ${cur},
|
|
"LATEST_TS": ${new}
|
|
}
|
|
EOF
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
AUTO_UPDATE=""
|
|
FILE_UPDATE=""
|
|
MACHINE_OUTPUT=""
|
|
JSON_OUTPUT="no"
|
|
|
|
while getopts adhf:m:j option; do
|
|
case "${option}" in
|
|
a) AUTO_UPDATE=1
|
|
;;
|
|
d) OVERWRITE_CONFIG=1
|
|
;;
|
|
f) FILE_UPDATE="${OPTARG}"
|
|
;;
|
|
j) JSON_OUTPUT="yes"
|
|
;;
|
|
m) MACHINE_OUTPUT="${OPTARG}"
|
|
;;
|
|
h) usage
|
|
;;
|
|
*) echo "Unknown argument \"${option}\""
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
|
|
checkDependencies
|
|
removePreviousUpdates
|
|
if [ -n "${AUTO_UPDATE}" ]; then
|
|
if vcgencmd bootloader_config | grep FREEZE_VERSION=1; then
|
|
echo "EEPROM version is frozen. Skipping update"
|
|
exit ${EXIT_EEPROM_FROZEN}
|
|
else
|
|
checkAndApply
|
|
fi
|
|
elif [ -n "${FILE_UPDATE}" ]; then
|
|
fileUpdate "${FILE_UPDATE}"
|
|
else
|
|
checkVersion
|
|
fi
|