#!/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 LOCAL_MODE=0 if [ -n "$FIRMWARE_ROOT" ]; then # Provided by environment true elif [ -d /lib/firmware/raspberrypi/bootloader ]; then # Default firmware root exists FIRMWARE_ROOT=/lib/firmware/raspberrypi/bootloader else # Work from local git checkout LOCAL_MODE=1 FIRMWARE_ROOT="${script_dir}/firmware" fi # Selects the release sub-directory FIRMWARE_RELEASE_STATUS=${FIRMWARE_RELEASE_STATUS:-default} FIRMWARE_IMAGE_DIR=${FIRMWARE_IMAGE_DIR:-${FIRMWARE_ROOT}/${FIRMWARE_RELEASE_STATUS}} FIRMWARE_BACKUP_DIR=${FIRMWARE_BACKUP_DIR:-/var/lib/raspberrypi/bootloader/backup} ENABLE_VL805_UPDATES=${ENABLE_VL805_UPDATES:-1} RECOVERY_BIN=${RECOVERY_BIN:-${FIRMWARE_ROOT}/${FIRMWARE_RELEASE_STATUS}/recovery.bin} BOOTFS=${BOOTFS:-/boot} VCMAILBOX=${VCMAILBOX:-/opt/vc/bin/vcmailbox} CM4_ENABLE_RPI_EEPROM_UPDATE=${CM4_ENABLE_RPI_EEPROM_UPDATE:-0} RPI_EEPROM_UPDATE_CONFIG_TOOL="${RPI_EEPROM_UPDATE_CONFIG_TOOL:-raspi-config}" DT_BOOTLOADER_TS=${DT_BOOTLOADER_TS:-/proc/device-tree/chosen/bootloader/build-timestamp} EXIT_SUCCESS=0 EXIT_UPDATE_REQUIRED=1 EXIT_FAILED=2 EXIT_EEPROM_FROZEN=3 # Reserved # EXIT_PREVIOUS_UPDATE_FAILED=4 OVERWRITE_CONFIG=0 # Timestamp for first release which doesn't have a timestamp field BOOTLOADER_FIRST_VERSION=1557513636 EEPROM_SIZE=524288 BOARD_INFO= BOARD_REVISION= BOARD_TYPE= # Newer board revisions embed the VLI firmware in the bootloader EEPROM and # there is no way to separately update the VLI firmware. Consequently, # standalone vl805 update files do not trigger automatic updates. # Recovery.bin and the the SPI bootloader ignore vl805.bin files on boards # without a dedicate VL805 EEPROM. HAVE_VL805_EEPROM=0 TMP_EEPROM_IMAGE="" TMP_BOOTFS_MNT="" VL805_CURRENT_VERSION= VL805_UPDATE_VERSION= # The update actions selected by the version check ACTION_UPDATE_BOOTLOADER=0 ACTION_UPDATE_VL805=0 CHECKSUMS='' cleanup() { if [ -f "${CHECKSUMS}" ]; then rm -f "${CHECKSUMS}" fi 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 [ -f "${NEW_EEPROM_CONFIG}" ]; then rm -f "${NEW_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= NEW_EEPROM_CONFIG= } trap cleanup EXIT die() { echo "$@" >&2 exit ${EXIT_FAILED} } getBootloaderConfig() { # Prefer extracting bootloader's config from DT. # # In order to find the right nvmem device, we build the sysfs path of the # bootloader reserved memory DT node to then match that path against all # nvmem device's ofnode path. # # If the path isn't there, default to using vcgencmd. local blconfig_alias="/sys/firmware/devicetree/base/aliases/blconfig" local blconfig_nvmem_path="" if [ -f "${blconfig_alias}" ]; then local blconfig_ofnode_path="/sys/firmware/devicetree/base"$(tr -cd '[:print:]' < "${blconfig_alias}")"" local blconfig_ofnode_link=$(find -L /sys/bus/nvmem -samefile "${blconfig_ofnode_path}" 2>/dev/null) if [ -e "${blconfig_ofnode_link}" ]; then blconfig_nvmem_path=$(dirname "${blconfig_ofnode_link}") fi fi if [ -n "${blconfig_nvmem_path}" ]; then cat "${blconfig_nvmem_path}"/nvmem else vcgencmd bootloader_config fi } prepareImage() { [ -f "${BOOTLOADER_UPDATE_IMAGE}" ] || die "EEPROM image '${BOOTLOADER_UPDATE_IMAGE}' not found" TMP_EEPROM_IMAGE="$(mktemp)" TMP_EEPROM_CONFIG="$(mktemp)" NEW_EEPROM_CONFIG="$(mktemp)" mkdir -p "${FIRMWARE_BACKUP_DIR}" # Backup the configuration of the currently loaded bootloader getBootloaderConfig > "${TMP_EEPROM_CONFIG}" backup="${FIRMWARE_BACKUP_DIR}/pieeprom-backup-$(date +%Y%m%d-%H%M%S).conf" cp -f "${TMP_EEPROM_CONFIG}" "${backup}" if [ -x "${EEPROM_CONFIG_HOOK}" ]; then echo "Running EEPROM config hook ${EEPROM_CONFIG_HOOK}" if ! "${EEPROM_CONFIG_HOOK}" -u "${BOOTLOADER_UPDATE_IMAGE}" < "${TMP_EEPROM_CONFIG}" > "${NEW_EEPROM_CONFIG}"; then echo "EEPROM config hook \"${EEPROM_CONFIG_HOOK}\" failed. Using original configuration" cp -f "${TMP_EEPROM_CONFIG}" "${NEW_EEPROM_CONFIG}" fi else cp -f "${TMP_EEPROM_CONFIG}" "${NEW_EEPROM_CONFIG}" fi if [ "$(wc -l "${NEW_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 "${BOOTLOADER_UPDATE_IMAGE}" "${TMP_EEPROM_IMAGE}" if [ "${OVERWRITE_CONFIG}" = 0 ]; then "${script_dir}/rpi-eeprom-config" \ --out "${TMP_EEPROM_IMAGE}" \ --config "${NEW_EEPROM_CONFIG}" "${BOOTLOADER_UPDATE_IMAGE}" fi } applyRecoveryUpdate() { [ -n "${BOOTLOADER_UPDATE_IMAGE}" ] || [ -n "${VL805_UPDATE_IMAGE}" ] || die "No update images specified" findBootFS echo "BOOTFS ${BOOTFS}" # A '.sig' file is created so that recovery.bin can check that the # EEPROM image has not been corrupted (e.g. SD card corruption). # Format of the .sig file. # -- # SHA256\n # ts: UPDATE-TIMESTAMP\n # -- # SHA256 is a 64 character hex string # UPDATE-TIMESTAMP is an unsigned decimal. # # The 'filename' output from sha256 MUST be omitted. if [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then [ -f "${BOOTLOADER_UPDATE_IMAGE}" ] || die "${BOOTLOADER_UPDATE_IMAGE} not found" TMP_EEPROM_IMAGE="$(mktemp)" prepareImage # 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. sha256sum "${TMP_EEPROM_IMAGE}" | awk '{print $1}' > "${BOOTFS}/pieeprom.sig" \ || die "Failed to create ${BOOTFS}/pieeprom.sig" # Appends the update creation timestamp on a newline in pieeprom.sig # During a self-update mode the bootloader examines the update-timestamp # and will only update itself if it is newer than the current update # timestamp. # # The update-timestamp is independent of the bootloader version and # does not have to be timestamp. echo "ts: $(date -u +%s)" >> "${BOOTFS}/pieeprom.sig" cp -f "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \ || die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}" # For NFS mounts ensure that the files are readable to the TFTP user chmod -f go+r "${BOOTFS}/pieeprom.upd" "${BOOTFS}/pieeprom.sig" \ || die "Failed to set permissions on eeprom update files" fi if [ -n "${VL805_UPDATE_IMAGE}" ]; then sha256sum "${VL805_UPDATE_IMAGE}" | awk '{print $1}' > "${BOOTFS}/vl805.sig" \ || die "Failed to create ${BOOTFS}/vl805.sig" cp -f "${VL805_UPDATE_IMAGE}" "${BOOTFS}/vl805.bin" \ || die "Failed to copy ${VL805_UPDATE_IMAGE} to ${BOOTFS}/vl805.bin" # For NFS mounts ensure that the files are readable to the TFTP user chmod -f go+r "${BOOTFS}/vl805.bin" "${BOOTFS}/vl805.sig" \ || die "Failed to set permissions on eeprom update files" fi cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" \ || die "Failed to copy ${RECOVERY_BIN} to ${BOOTFS}" } applyUpdate() { [ "$(id -u)" = "0" ] || die "* Must be run as root - try 'sudo rpi-eeprom-update'" if [ "${IGNORE_DPKG_CHECKSUMS}" = 0 ]; then ( package_checksums_file="${PACKAGE_INFO_DIR}/rpi-eeprom.md5sums" if ! grep -qE '\.bin$' "${PACKAGE_INFO_DIR}/rpi-eeprom.md5sums"; then # Try the old rpi-eeprom-images package package_checksums_file="${PACKAGE_INFO_DIR}/rpi-eeprom-images.md5sums" fi CHECKSUMS=$(mktemp) grep -E '\.bin$' "${package_checksums_file}" > "${CHECKSUMS}" cd / if ! md5sum -c "${CHECKSUMS}" > /dev/null 2>&1; then md5sum -c "${CHECKSUMS}" die "rpi-eeprom checksums failed - try reinstalling this package" fi ) || die "Unable to validate EEPROM image package checksums" fi applyRecoveryUpdate } BOOTLOADER_CURRENT_VERSION= getBootloaderCurrentVersion() { if [ -f "${DT_BOOTLOADER_TS}" ]; then # Prefer device-tree to vcgencmd BOOTLOADER_CURRENT_VERSION=$(printf "%d" "0x$(od "${DT_BOOTLOADER_TS}" -v -An -t x1 | tr -d ' ' )") elif vcgencmd bootloader_version | grep -q timestamp; then BOOTLOADER_CURRENT_VERSION=$(vcgencmd bootloader_version | grep timestamp | awk '{print $2}') if [ "${BOOTLOADER_CURRENT_VERSION}" = "0" ]; then # If a timestamp of zero is returned then it's new firmware but an # old bootloader. Assume bootloader v0 BOOTLOADER_CURRENT_VERSION="${BOOTLOADER_FIRST_VERSION}" fi else # New bootloader / old firmware ? Try to parse the date BOOTLOADER_CURRENT_VERSION=$(date -u +%s --date "$(vcgencmd bootloader_version | head -n1)" 2>/dev/null || true) fi # Failed to parse the version. Default to the initial production release. if [ -z "${BOOTLOADER_CURRENT_VERSION}" ]; then BOOTLOADER_CURRENT_VERSION="${BOOTLOADER_FIRST_VERSION}" fi } # Find latest applicable update version BOOTLOADER_UPDATE_IMAGE="" BOOTLOADER_UPDATE_VERSION=0 getBootloaderUpdateVersion() { BOOTLOADER_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 BOOTLOADER_UPDATE_VERSION=$(tr -c '[:print:]' "\n" < "${latest}" | sed '/^BUILD_TIMESTAMP=/s/.*=//p;d') BOOTLOADER_UPDATE_IMAGE="${latest}" fi } checkDependencies() { if [ -f "/sys/firmware/devicetree/base/system/linux,revision" ]; then BOARD_INFO="$(od -v -An -t x1 /sys/firmware/devicetree/base/system/linux,revision | tr -d ' \n')" elif grep -q Revision /proc/cpuinfo; then BOARD_INFO="$(sed -n '/^Revision/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo)" else BOARD_INFO="$(vcgencmd otp_dump | grep '30:' | sed 's/.*://')" fi if [ $(((0x$BOARD_INFO >> 23) & 1)) -ne 0 ] && [ $(((0x$BOARD_INFO >> 12) & 15)) -eq 3 ]; then echo "BCM2711 detected" else # Not a BCM2711, no EEPROMs to update. echo "This tool only works with a Raspberry Pi 4" exit ${EXIT_SUCCESS} fi BOARD_TYPE=$(((0x$BOARD_INFO >> 4) & 0xff)) BOARD_REVISION=$((0x$BOARD_INFO & 0xf)) if [ ${BOARD_TYPE} -eq 20 ] && [ "${CM4_ENABLE_RPI_EEPROM_UPDATE}" != '1' ]; then # For CM4, USB device boot is the recommended method for EEPROM updates. echo "rpi-eeprom-update is not enabled by default on CM4. Run with -h for more information." exit ${EXIT_SUCCESS} fi if [ ${BOARD_TYPE} -eq 17 ] && [ ${BOARD_REVISION} -lt 4 ]; then echo "Dedicated VL805 EEPROM detected" HAVE_VL805_EEPROM=1 else echo "VL805 firmware in bootloader EEPROM" HAVE_VL805_EEPROM=0 fi if ! command -v lspci > /dev/null; then die "lspci not found. Try installing the pciutils package." fi # vcgencmd bootloader_version is deprecated. Use device-tree if available to # reduce the number of dependencies on VCHI. if ! [ -f "${DT_BOOTLOADER_TS}" ]; then if ! command -v vcgencmd > /dev/null; then die "vcgencmd not found. Try installing the libraspberrypi-bin package." fi fi if [ ! -e "${FIRMWARE_IMAGE_DIR}" ]; then die "EEPROM updates directory ${FIRMWARE_IMAGE_DIR} not found." fi # If a board revision specific firmware directory is defined then use that # in preference to the generic directory. if [ -e "${FIRMWARE_IMAGE_DIR}-${BOARD_INFO}" ]; then FIRMWARE_IMAGE_DIR="${FIRMWARE_IMAGE_DIR}-${BOARD_INFO}" fi if ! getBootloaderConfig > /dev/null; then die "Unable to get bootloader config, please update VC firmware and reboot." fi if ! command -v sha256sum > /dev/null; then die "sha256sum not found. Try installing the coreutilities package." fi if [ ! -f "${RECOVERY_BIN}" ]; then die "${RECOVERY_BIN} not found." fi } usage() { cat < "${MACHINE_OUTPUT}" < "${MACHINE_OUTPUT}" <