Merge pull request #492 from timg236/raspberrypi5-use-flashrom

rpi-eeprom-update: Add the option to use flashrom for updates on Raspberry Pi 5
This commit is contained in:
timg236
2023-11-09 12:09:42 +00:00
committed by GitHub
3 changed files with 135 additions and 16 deletions

View File

@@ -109,7 +109,7 @@ def exit_error(msg):
sys.stderr.write("ERROR: %s\n" % msg) sys.stderr.write("ERROR: %s\n" % msg)
sys.exit(1) sys.exit(1)
def shell_cmd(args): def shell_cmd(args, timeout=5, echo=False):
""" """
Executes a shell command waits for completion returning STDOUT. If an Executes a shell command waits for completion returning STDOUT. If an
error occurs then exit and output the subprocess stdout, stderr messages error occurs then exit and output the subprocess stdout, stderr messages
@@ -117,9 +117,14 @@ def shell_cmd(args):
""" """
start = time.time() start = time.time()
arg_str = ' '.join(args) arg_str = ' '.join(args)
result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) bufsize = 0 if echo else -1
result = subprocess.Popen(args, bufsize=bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while time.time() - start < 5: while time.time() - start < timeout:
if echo:
s = result.stdout.read(80).decode('utf-8')
if s != "":
sys.stdout.write(s)
if result.poll() is not None: if result.poll() is not None:
break break
@@ -128,8 +133,8 @@ def shell_cmd(args):
if result.returncode != 0: if result.returncode != 0:
exit_error("%s failed: %d\n %s\n %s\n" % exit_error("%s failed: %d\n %s\n %s\n" %
(arg_str, result.returncode, result.stdout.read(), result.stderr.read())) (arg_str, result.returncode, result.stdout.read().decode('utf-8'), result.stderr.read().decode('utf-8')))
else: elif not echo:
return result.stdout.read().decode('utf-8') return result.stdout.read().decode('utf-8')
def get_latest_eeprom(): def get_latest_eeprom():
@@ -170,8 +175,10 @@ def apply_update(config, eeprom=None, config_src=None):
# with EEPROMs with configs delivered outside of APT. # with EEPROMs with configs delivered outside of APT.
# The checksums are really just a safety check for automatic updates. # The checksums are really just a safety check for automatic updates.
args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update] args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update]
resp = shell_cmd(args)
sys.stdout.write(resp) # If flashrom is used then the command will not return until the EEPROM
# has been updated so use a larger timeout.
shell_cmd(args, timeout=20, echo=True)
def edit_config(eeprom=None): def edit_config(eeprom=None):
""" """
@@ -377,6 +384,15 @@ class BootloaderImage(object):
% (src_filename, len(src_bytes), MAX_FILE_SIZE)) % (src_filename, len(src_bytes), MAX_FILE_SIZE))
self.update(src_bytes, dst_filename) self.update(src_bytes, dst_filename)
def set_timestamp(self, timestamp):
"""
Sets the self-update timestamp in an EEPROM image file. This is useful when
using flashrom to write to SPI flash instead of using the bootloader self-update mode.
"""
ts = int(timestamp)
struct.pack_into('<L', self._bytes, len(self._bytes) - 4, ts)
struct.pack_into('<L', self._bytes, len(self._bytes) - 8, ~ts & 0xffffffff)
def write(self): def write(self):
""" """
Writes the updated EEPROM image to stdout or the specified output file. Writes the updated EEPROM image to stdout or the specified output file.
@@ -498,6 +514,7 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False) parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False)
parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False) parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False)
parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False) parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False)
parser.add_argument('-t', '--timestamp', help='Set the timestamp in the EEPROM image file', required=False)
parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input')
args = parser.parse_args() args = parser.parse_args()
@@ -518,6 +535,8 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
apply_update(args.apply, args.eeprom, args.apply) apply_update(args.apply, args.eeprom, args.apply)
elif args.eeprom is not None: elif args.eeprom is not None:
image = BootloaderImage(args.eeprom, args.out) image = BootloaderImage(args.eeprom, args.out)
if args.timestamp is not None:
image.set_timestamp(args.timestamp)
if args.config is not None: if args.config is not None:
if not os.path.exists(args.config): if not os.path.exists(args.config):
exit_error("config file '%s' not found" % args.config) exit_error("config file '%s' not found" % args.config)
@@ -527,6 +546,8 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
if args.pubkey is not None: if args.pubkey is not None:
image.update_key(args.pubkey, PUBKEY_BIN) image.update_key(args.pubkey, PUBKEY_BIN)
image.write() image.write()
elif args.config is None and args.timestamp is not None:
image.write()
else: else:
image.read() image.read()
elif args.config is None and args.eeprom is None: elif args.config is None and args.eeprom is None:

View File

@@ -89,6 +89,9 @@ cleanup() {
if [ -f "${NEW_EEPROM_CONFIG}" ]; then if [ -f "${NEW_EEPROM_CONFIG}" ]; then
rm -f "${NEW_EEPROM_CONFIG}" rm -f "${NEW_EEPROM_CONFIG}"
fi fi
if [ -f "${FLASHROM_LOG}" ]; then
rm -f "${FLASHROM_LOG}"
fi
if [ -d "${TMP_BOOTFS_MNT}" ]; then if [ -d "${TMP_BOOTFS_MNT}" ]; then
umount "${TMP_BOOTFS_MNT}" umount "${TMP_BOOTFS_MNT}"
rmdir "${TMP_BOOTFS_MNT}" rmdir "${TMP_BOOTFS_MNT}"
@@ -97,6 +100,7 @@ cleanup() {
TMP_EEPROM_IMAGE= TMP_EEPROM_IMAGE=
TMP_EEPROM_CONFIG= TMP_EEPROM_CONFIG=
NEW_EEPROM_CONFIG= NEW_EEPROM_CONFIG=
FLASHROM_LOG=
} }
trap cleanup EXIT trap cleanup EXIT
@@ -169,7 +173,14 @@ prepareImage()
if [ "${OVERWRITE_CONFIG}" = 0 ]; then if [ "${OVERWRITE_CONFIG}" = 0 ]; then
"${script_dir}/rpi-eeprom-config" \ "${script_dir}/rpi-eeprom-config" \
--out "${TMP_EEPROM_IMAGE}" \ --out "${TMP_EEPROM_IMAGE}" \
--config "${NEW_EEPROM_CONFIG}" "${BOOTLOADER_UPDATE_IMAGE}" --config "${NEW_EEPROM_CONFIG}" \
--timestamp "$(date -u +%s)" \
"${BOOTLOADER_UPDATE_IMAGE}"
else
"${script_dir}/rpi-eeprom-config" \
--out "${TMP_EEPROM_IMAGE}" \
--timestamp "$(date -u +%s)" \
"${BOOTLOADER_UPDATE_IMAGE}"
fi fi
} }
@@ -200,7 +211,7 @@ applyRecoveryUpdate()
# and the current timestamp. # and the current timestamp.
rpi-eeprom-digest -i "${TMP_EEPROM_IMAGE}" -o "${BOOTFS}/pieeprom.sig" rpi-eeprom-digest -i "${TMP_EEPROM_IMAGE}" -o "${BOOTFS}/pieeprom.sig"
cp -f "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \ cp -fv "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \
|| die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}" || die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}"
# For NFS mounts ensure that the files are readable to the TFTP user # For NFS mounts ensure that the files are readable to the TFTP user
@@ -225,7 +236,7 @@ applyRecoveryUpdate()
RPI_EEPROM_SELF_UPDATE=0 RPI_EEPROM_SELF_UPDATE=0
fi fi
# Setting bootlaoder_update=0 was really intended for use with network-boot with shared # Setting bootloader_update=0 was really intended for use with network-boot with shared
# config.txt files. However, if it looks as though self-update has been disabled then # config.txt files. However, if it looks as though self-update has been disabled then
# assume recovery.bin is required. # assume recovery.bin is required.
config_txt="${BOOTFS}/config.txt" config_txt="${BOOTFS}/config.txt"
@@ -237,8 +248,33 @@ applyRecoveryUpdate()
[ "${BOOTLOADER_CURRENT_VERSION}" -ge "${RPI_EEPROM_SELF_UPDATE_MIN_VER}" ] || RPI_EEPROM_SELF_UPDATE=0 [ "${BOOTLOADER_CURRENT_VERSION}" -ge "${RPI_EEPROM_SELF_UPDATE_MIN_VER}" ] || RPI_EEPROM_SELF_UPDATE=0
if [ "${RPI_EEPROM_SELF_UPDATE}" != "1" ]; then # For immediate updates via flash the recovery.bin update is created and then discarded if the
echo "Using recovery.bin for EEPROM update" # flashrom update was successful. For SD boot (most common) this provides a rollback in the event
# of power loss.
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
echo
echo "UPDATING bootloader."
echo
echo "*** WARNING: Do not disconnect the power until the update is complete ***"
echo "If a problem occurs then the Raspberry Pi Imager may be used to create"
echo "a bootloader rescue SD card image which restores the default bootloader image."
echo
FLASHROM_LOG="$(mktemp)"
echo "flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 -w ${BOOTFS}/pieeprom.upd"
if flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 -w "${BOOTFS}/pieeprom.upd" > "${FLASHROM_LOG}"; then
# Success - remove update files from the boot partition
removePreviousUpdates
echo "UPDATE SUCCESSFUL"
else
# Leave the recovery files in case the EEPROM has been partially updated
cat "${FLASHROM_LOG}"
die "UPDATE FAILED"
fi
return
elif [ "${RPI_EEPROM_SELF_UPDATE}" = "1" ]; then
echo "Using self-update"
else
echo "Copying recovery.bin to ${BOOTFS} for EEPROM update"
cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" || die "Failed to copy ${RECOVERY_BIN} to ${BOOTFS}" cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" || die "Failed to copy ${RECOVERY_BIN} to ${BOOTFS}"
fi fi
@@ -269,6 +305,25 @@ applyUpdate() {
) || die "Unable to validate EEPROM image package checksums" ) || die "Unable to validate EEPROM image package checksums"
fi fi
# Disable flashrom if the SPI device is not found
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
flashrom_probe_ok=0
if ! [ -e "${SPIDEV}" ]; then
echo "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
fi
if ! flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 > /dev/null 2>&1; then
echo "WARNING: Flashrom probe of ${SPIDEV} failed"
else
flashrom_probe_ok=1
fi
if [ "${flashrom_probe_ok}" != 1 ]; then
echo "Setting RPI_EEPROM_USE_FLASHROM to 0"
echo
export RPI_EEPROM_USE_FLASHROM=0
fi
fi
applyRecoveryUpdate applyRecoveryUpdate
} }
@@ -334,14 +389,21 @@ checkDependencies() {
BCM_CHIP=2711 BCM_CHIP=2711
EEPROM_SIZE=524288 EEPROM_SIZE=524288
BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1599135103}" BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1599135103}"
SPIDEV=/dev/spidev0.0
elif [ $(((0x$BOARD_INFO >> 12) & 15)) = 4 ]; then elif [ $(((0x$BOARD_INFO >> 12) & 15)) = 4 ]; then
BCM_CHIP=2712 BCM_CHIP=2712
EEPROM_SIZE=2097152 EEPROM_SIZE=2097152
BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1697650217}" BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1697650217}"
SPIDEV=/dev/spidev10.0
else else
chipNotSupported chipNotSupported
fi fi
# Default to off - in the future Raspberry Pi 5 may default to using flashrom if
# flashrom is available.
[ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0
FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}" FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}"
if ! [ -d "${FIRMWARE_IMAGE_DIR}" ]; then if ! [ -d "${FIRMWARE_IMAGE_DIR}" ]; then
# Use unadorned name for backwards compatiblity # Use unadorned name for backwards compatiblity
@@ -358,6 +420,18 @@ checkDependencies() {
echo "The recommended method for flashing the EEPROM is rpiboot." echo "The recommended method for flashing the EEPROM is rpiboot."
echo "See: https://github.com/raspberrypi/usbboot/blob/master/Readme.md" echo "See: https://github.com/raspberrypi/usbboot/blob/master/Readme.md"
echo "Run with -h for more information." echo "Run with -h for more information."
echo
echo "To enable flashrom programming of the EEPROM"
echo "Add these the following entries to /etc/default/rpi-eeprom-update"
echo "RPI_EEPROM_USE_FLASHROM=1"
echo "CM4_ENABLE_RPI_EEPROM_UPDATE=1"
echo
echo "and these entries to config.txt and reboot"
echo "[cm4]"
echo "dtparam=spi=on"
echo "dtoverlay=audremap"
echo "dtoverlay=spi-gpio40-45"
echo
exit ${EXIT_SUCCESS} exit ${EXIT_SUCCESS}
fi fi
@@ -404,6 +478,10 @@ checkDependencies() {
if [ "${BCM_CHIP}" = 2711 ] && [ ! -f "${RECOVERY_BIN}" ]; then if [ "${BCM_CHIP}" = 2711 ] && [ ! -f "${RECOVERY_BIN}" ]; then
die "${RECOVERY_BIN} not found." die "${RECOVERY_BIN} not found."
fi fi
if ! command -v flashrom > /dev/null; then
RPI_EEPROM_USE_FLASHROM=0
fi
} }
usage() { usage() {
@@ -542,6 +620,22 @@ N.B. If there is a power failure during SELF_UPDATE the EEPROM write may fail an
usbboot must be used to flash the bootloader EEPROM. SELF_UPDATE is not recommended usbboot must be used to flash the bootloader EEPROM. SELF_UPDATE is not recommended
for updating the bootloader on remote systems. for updating the bootloader on remote systems.
FLASHROM:
If the RPI_EEPROM_USE_FLASHROM variable is set to 1 then flashrom is used to perform
an immediate update to the SPI flash rather than installing the recovery.bin plus
pieeprom.upd files. The power must not be disconnected during this update otherwise the
EEPROM will need to be re-flashed using the Rasberry Pi Imager bootloader restore feature.
On Raspberry Pi 4, CM4, CM4-S and Pi400 flashrom updates are not enabled by default
because the SPI GPIOs are shared with analog audio. To enable this add the following
entries to config.txt. This moves analog audio to GPIO pins 12,13 and may not be
compatible with some HATS / CM4 IO boards.
dtparam=spi=on
dtoverlay=audremap
dtoverlay=spi-gpio40-45
EOF EOF
exit ${EXIT_SUCCESS} exit ${EXIT_SUCCESS}
} }
@@ -595,7 +689,9 @@ findBootFS()
elif [ -z "$BOOTFS" ]; then elif [ -z "$BOOTFS" ]; then
if ! BOOTFS=$(/usr/lib/raspberrypi-sys-mods/get_fw_loc 2> /dev/null); then if ! BOOTFS=$(/usr/lib/raspberrypi-sys-mods/get_fw_loc 2> /dev/null); then
for BOOTFS in /boot/firmware /boot; do for BOOTFS in /boot/firmware /boot; do
if findmnt --fstab "$BOOTFS" > /dev/null; then if [ -f "${BOOTFS}/config.txt" ]; then
break
elif findmnt --fstab "$BOOTFS" > /dev/null; then
break break
fi fi
done done
@@ -714,7 +810,7 @@ checkAndApply()
fi fi
if [ "${ACTION_UPDATE_BOOTLOADER}" = 1 ] || [ "${ACTION_UPDATE_VL805}" = 1 ]; then if [ "${ACTION_UPDATE_BOOTLOADER}" = 1 ] || [ "${ACTION_UPDATE_VL805}" = 1 ]; then
echo "*** INSTALLING EEPROM UPDATES ***" echo "*** PREPARING EEPROM UPDATES ***"
echo "" echo ""
printVersions printVersions
@@ -727,7 +823,7 @@ checkAndApply()
fileUpdate() fileUpdate()
{ {
removePreviousUpdates removePreviousUpdates
echo "*** INSTALLING ${BOOTLOADER_UPDATE_IMAGE} ${VL805_UPDATE_IMAGE} ***" echo "*** CREATED UPDATE ${BOOTLOADER_UPDATE_IMAGE} ${VL805_UPDATE_IMAGE} ***"
echo echo
if [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then if [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then

View File

@@ -2,8 +2,10 @@
FIRMWARE_ROOT=/lib/firmware/raspberrypi/bootloader FIRMWARE_ROOT=/lib/firmware/raspberrypi/bootloader
FIRMWARE_RELEASE_STATUS="default" FIRMWARE_RELEASE_STATUS="default"
FIRMWARE_BACKUP_DIR="/var/lib/raspberrypi/bootloader/backup" FIRMWARE_BACKUP_DIR="/var/lib/raspberrypi/bootloader/backup"
USE_FLASHROM=0
EEPROM_CONFIG_HOOK= EEPROM_CONFIG_HOOK=
# BOOTFS can be set here to override auto-detection in rpi-eeprom-update # BOOTFS can be set here to override auto-detection in rpi-eeprom-update
#BOOTFS=/boot #BOOTFS=/boot
# Use flashrom if available to update the bootloader without rebooting - Raspberry Pi 5
#RPI_EEPROM_USE_FLASHROM=1