#!/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 } # Find latest applicable update 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 < "${MACHINE_OUTPUT}" < "${MACHINE_OUTPUT}" <