Skip to content

Commit db154d4

Browse files
committed
rpi-eeprom-update: Add the option to use flashrom for updates on Raspberry Pi 5
On Raspberry Pi 5 there are dedicated pins for the bootloader SPI EEPROM. This makes it possible to do immediate updates via flashrom. The "current" EEPROM config is the EEPROM config at boot rather than what has just been written to the SPI flash because this is consistent with current behaviour. To use flashrom instead of recovery.bin for bootloader updates set RPI_EEPROM_USE_FLASHROM=1 in /etc/defaults/rpi-eeprom-update BCM2711 On CM4, Pi4, CM4-S, Pi400 config.txt must be modified to disable the analog audio driver which shares the GPIO pins used by the bootloader EEPROM. dtparam=spi=on dtoverlay=audremap dtoverlay=spi-gpio40-45
1 parent aded082 commit db154d4

File tree

3 files changed

+136
-17
lines changed

3 files changed

+136
-17
lines changed

rpi-eeprom-config

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,22 @@ def exit_error(msg):
109109
sys.stderr.write("ERROR: %s\n" % msg)
110110
sys.exit(1)
111111

112-
def shell_cmd(args):
112+
def shell_cmd(args, timeout=5, echo=False):
113113
"""
114114
Executes a shell command waits for completion returning STDOUT. If an
115115
error occurs then exit and output the subprocess stdout, stderr messages
116116
for debug.
117117
"""
118118
start = time.time()
119119
arg_str = ' '.join(args)
120-
result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
121-
122-
while time.time() - start < 5:
120+
bufsize = 0 if echo else -1
121+
result = subprocess.Popen(args, bufsize=bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
122+
123+
while time.time() - start < timeout:
124+
if echo:
125+
s = result.stdout.read(80).decode('utf-8')
126+
if s != "":
127+
sys.stdout.write(s)
123128
if result.poll() is not None:
124129
break
125130

@@ -128,8 +133,8 @@ def shell_cmd(args):
128133

129134
if result.returncode != 0:
130135
exit_error("%s failed: %d\n %s\n %s\n" %
131-
(arg_str, result.returncode, result.stdout.read(), result.stderr.read()))
132-
else:
136+
(arg_str, result.returncode, result.stdout.read().decode('utf-8'), result.stderr.read().decode('utf-8')))
137+
elif not echo:
133138
return result.stdout.read().decode('utf-8')
134139

135140
def get_latest_eeprom():
@@ -170,8 +175,10 @@ def apply_update(config, eeprom=None, config_src=None):
170175
# with EEPROMs with configs delivered outside of APT.
171176
# The checksums are really just a safety check for automatic updates.
172177
args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update]
173-
resp = shell_cmd(args)
174-
sys.stdout.write(resp)
178+
179+
# If flashrom is used then the command will not return until the EEPROM
180+
# has been updated so use a larger timeout.
181+
shell_cmd(args, timeout=20, echo=True)
175182

176183
def edit_config(eeprom=None):
177184
"""
@@ -377,6 +384,15 @@ class BootloaderImage(object):
377384
% (src_filename, len(src_bytes), MAX_FILE_SIZE))
378385
self.update(src_bytes, dst_filename)
379386

387+
def set_timestamp(self, timestamp):
388+
"""
389+
Sets the self-update timestamp in an EEPROM image file. This is useful when
390+
using flashrom to write to SPI flash instead of using the bootloader self-update mode.
391+
"""
392+
ts = int(timestamp)
393+
struct.pack_into('<L', self._bytes, len(self._bytes) - 4, ts)
394+
struct.pack_into('<L', self._bytes, len(self._bytes) - 8, ~ts & 0xffffffff)
395+
380396
def write(self):
381397
"""
382398
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
498514
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)
499515
parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False)
500516
parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False)
517+
parser.add_argument('-t', '--timestamp', help='Set the timestamp in the EEPROM image file', required=False)
501518
parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input')
502519
args = parser.parse_args()
503520

@@ -518,6 +535,8 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
518535
apply_update(args.apply, args.eeprom, args.apply)
519536
elif args.eeprom is not None:
520537
image = BootloaderImage(args.eeprom, args.out)
538+
if args.timestamp is not None:
539+
image.set_timestamp(args.timestamp)
521540
if args.config is not None:
522541
if not os.path.exists(args.config):
523542
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
527546
if args.pubkey is not None:
528547
image.update_key(args.pubkey, PUBKEY_BIN)
529548
image.write()
549+
elif args.config is None and args.timestamp is not None:
550+
image.write()
530551
else:
531552
image.read()
532553
elif args.config is None and args.eeprom is None:

rpi-eeprom-update

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ cleanup() {
8989
if [ -f "${NEW_EEPROM_CONFIG}" ]; then
9090
rm -f "${NEW_EEPROM_CONFIG}"
9191
fi
92+
if [ -f "${FLASHROM_LOG}" ]; then
93+
rm -f "${FLASHROM_LOG}"
94+
fi
9295
if [ -d "${TMP_BOOTFS_MNT}" ]; then
9396
umount "${TMP_BOOTFS_MNT}"
9497
rmdir "${TMP_BOOTFS_MNT}"
@@ -97,6 +100,7 @@ cleanup() {
97100
TMP_EEPROM_IMAGE=
98101
TMP_EEPROM_CONFIG=
99102
NEW_EEPROM_CONFIG=
103+
FLASHROM_LOG=
100104
}
101105
trap cleanup EXIT
102106

@@ -169,7 +173,14 @@ prepareImage()
169173
if [ "${OVERWRITE_CONFIG}" = 0 ]; then
170174
"${script_dir}/rpi-eeprom-config" \
171175
--out "${TMP_EEPROM_IMAGE}" \
172-
--config "${NEW_EEPROM_CONFIG}" "${BOOTLOADER_UPDATE_IMAGE}"
176+
--config "${NEW_EEPROM_CONFIG}" \
177+
--timestamp "$(date -u +%s)" \
178+
"${BOOTLOADER_UPDATE_IMAGE}"
179+
else
180+
"${script_dir}/rpi-eeprom-config" \
181+
--out "${TMP_EEPROM_IMAGE}" \
182+
--timestamp "$(date -u +%s)" \
183+
"${BOOTLOADER_UPDATE_IMAGE}"
173184
fi
174185
}
175186

@@ -200,7 +211,7 @@ applyRecoveryUpdate()
200211
# and the current timestamp.
201212
rpi-eeprom-digest -i "${TMP_EEPROM_IMAGE}" -o "${BOOTFS}/pieeprom.sig"
202213

203-
cp -f "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \
214+
cp -fv "${TMP_EEPROM_IMAGE}" "${BOOTFS}/pieeprom.upd" \
204215
|| die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}"
205216

206217
# For NFS mounts ensure that the files are readable to the TFTP user
@@ -225,7 +236,7 @@ applyRecoveryUpdate()
225236
RPI_EEPROM_SELF_UPDATE=0
226237
fi
227238

228-
# Setting bootlaoder_update=0 was really intended for use with network-boot with shared
239+
# Setting bootloader_update=0 was really intended for use with network-boot with shared
229240
# config.txt files. However, if it looks as though self-update has been disabled then
230241
# assume recovery.bin is required.
231242
config_txt="${BOOTFS}/config.txt"
@@ -237,8 +248,33 @@ applyRecoveryUpdate()
237248

238249
[ "${BOOTLOADER_CURRENT_VERSION}" -ge "${RPI_EEPROM_SELF_UPDATE_MIN_VER}" ] || RPI_EEPROM_SELF_UPDATE=0
239250

240-
if [ "${RPI_EEPROM_SELF_UPDATE}" != "1" ]; then
241-
echo "Using recovery.bin for EEPROM update"
251+
# For immediate updates via flash the recovery.bin update is created and then discarded if the
252+
# flashrom update was successful. For SD boot (most common) this provides a rollback in the event
253+
# of power loss.
254+
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
255+
echo
256+
echo "UPDATING bootloader."
257+
echo
258+
echo "*** WARNING: Do not disconnect the power until the update is complete ***"
259+
echo "If a problem occurs then the Raspberry Pi Imager may be used to create"
260+
echo "a bootloader rescue SD card image which restores the default bootloader image."
261+
echo
262+
FLASHROM_LOG="$(mktemp)"
263+
echo "flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 -w ${BOOTFS}/pieeprom.upd"
264+
if flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 -w "${BOOTFS}/pieeprom.upd" > "${FLASHROM_LOG}"; then
265+
# Success - remove update files from the boot partition
266+
removePreviousUpdates
267+
echo "UPDATE SUCCESSFUL"
268+
else
269+
# Leave the recovery files in case the EEPROM has been partially updated
270+
cat "${FLASHROM_LOG}"
271+
die "UPDATE FAILED"
272+
fi
273+
return
274+
elif [ "${RPI_EEPROM_SELF_UPDATE}" = "1" ]; then
275+
echo "Using self-update"
276+
else
277+
echo "Copying recovery.bin to ${BOOTFS} for EEPROM update"
242278
cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" || die "Failed to copy ${RECOVERY_BIN} to ${BOOTFS}"
243279
fi
244280

@@ -269,6 +305,25 @@ applyUpdate() {
269305
) || die "Unable to validate EEPROM image package checksums"
270306
fi
271307

308+
# Disable flashrom if the SPI device is not found
309+
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
310+
flashrom_probe_ok=0
311+
if ! [ -e "${SPIDEV}" ]; then
312+
echo "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
313+
fi
314+
315+
if ! flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 > /dev/null 2>&1; then
316+
echo "WARNING: Flashrom probe of ${SPIDEV} failed"
317+
else
318+
flashrom_probe_ok=1
319+
fi
320+
if [ "${flashrom_probe_ok}" != 1 ]; then
321+
echo "Setting RPI_EEPROM_USE_FLASHROM to 0"
322+
echo
323+
export RPI_EEPROM_USE_FLASHROM=0
324+
fi
325+
fi
326+
272327
applyRecoveryUpdate
273328
}
274329

@@ -334,14 +389,21 @@ checkDependencies() {
334389
BCM_CHIP=2711
335390
EEPROM_SIZE=524288
336391
BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1599135103}"
392+
393+
SPIDEV=/dev/spidev0.0
337394
elif [ $(((0x$BOARD_INFO >> 12) & 15)) = 4 ]; then
338395
BCM_CHIP=2712
339396
EEPROM_SIZE=2097152
340397
BOOTLOADER_AUTO_UPDATE_MIN_VERSION="${BOOTLOADER_AUTO_UPDATE_MIN_VERSION:-1697650217}"
398+
SPIDEV=/dev/spidev10.0
341399
else
342400
chipNotSupported
343401
fi
344402

403+
# Default to off - in the future Raspberry Pi 5 may default to using flashrom if
404+
# flashrom is available.
405+
[ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0
406+
345407
FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}"
346408
if ! [ -d "${FIRMWARE_IMAGE_DIR}" ]; then
347409
# Use unadorned name for backwards compatiblity
@@ -358,6 +420,18 @@ checkDependencies() {
358420
echo "The recommended method for flashing the EEPROM is rpiboot."
359421
echo "See: https://github.com/raspberrypi/usbboot/blob/master/Readme.md"
360422
echo "Run with -h for more information."
423+
echo
424+
echo "To enable flashrom programming of the EEPROM"
425+
echo "Add these the following entries to /etc/default/rpi-eeprom-update"
426+
echo "RPI_EEPROM_USE_FLASHROM=1"
427+
echo "CM4_ENABLE_RPI_EEPROM_UPDATE=1"
428+
echo
429+
echo "and these entries to config.txt and reboot"
430+
echo "[cm4]"
431+
echo "dtparam=spi=on"
432+
echo "dtoverlay=audremap"
433+
echo "dtoverlay=spi-gpio40-45"
434+
echo
361435
exit ${EXIT_SUCCESS}
362436
fi
363437

@@ -404,6 +478,10 @@ checkDependencies() {
404478
if [ "${BCM_CHIP}" = 2711 ] && [ ! -f "${RECOVERY_BIN}" ]; then
405479
die "${RECOVERY_BIN} not found."
406480
fi
481+
482+
if ! command -v flashrom > /dev/null; then
483+
RPI_EEPROM_USE_FLASHROM=0
484+
fi
407485
}
408486

409487
usage() {
@@ -542,6 +620,22 @@ N.B. If there is a power failure during SELF_UPDATE the EEPROM write may fail an
542620
usbboot must be used to flash the bootloader EEPROM. SELF_UPDATE is not recommended
543621
for updating the bootloader on remote systems.
544622
623+
FLASHROM:
624+
625+
If the RPI_EEPROM_USE_FLASHROM variable is set to 1 then flashrom is used to perform
626+
an immediate update to the SPI flash rather than installing the recovery.bin plus
627+
pieeprom.upd files. The power must not be disconnected during this update otherwise the
628+
EEPROM will need to be re-flashed using the Rasberry Pi Imager bootloader restore feature.
629+
630+
On Raspberry Pi 4, CM4, CM4-S and Pi400 flashrom updates are not enabled by default
631+
because the SPI GPIOs are shared with analog audio. To enable this add the following
632+
entries to config.txt. This moves analog audio to GPIO pins 12,13 and may not be
633+
compatible with some HATS / CM4 IO boards.
634+
635+
dtparam=spi=on
636+
dtoverlay=audremap
637+
dtoverlay=spi-gpio40-45
638+
545639
EOF
546640
exit ${EXIT_SUCCESS}
547641
}
@@ -595,7 +689,9 @@ findBootFS()
595689
elif [ -z "$BOOTFS" ]; then
596690
if ! BOOTFS=$(/usr/lib/raspberrypi-sys-mods/get_fw_loc 2> /dev/null); then
597691
for BOOTFS in /boot/firmware /boot; do
598-
if findmnt --fstab "$BOOTFS" > /dev/null; then
692+
if [ -f "${BOOTFS}/config.txt" ]; then
693+
break
694+
elif findmnt --fstab "$BOOTFS" > /dev/null; then
599695
break
600696
fi
601697
done
@@ -714,7 +810,7 @@ checkAndApply()
714810
fi
715811

716812
if [ "${ACTION_UPDATE_BOOTLOADER}" = 1 ] || [ "${ACTION_UPDATE_VL805}" = 1 ]; then
717-
echo "*** INSTALLING EEPROM UPDATES ***"
813+
echo "*** PREPARING EEPROM UPDATES ***"
718814
echo ""
719815

720816
printVersions
@@ -727,7 +823,7 @@ checkAndApply()
727823
fileUpdate()
728824
{
729825
removePreviousUpdates
730-
echo "*** INSTALLING ${BOOTLOADER_UPDATE_IMAGE} ${VL805_UPDATE_IMAGE} ***"
826+
echo "*** CREATED UPDATE ${BOOTLOADER_UPDATE_IMAGE} ${VL805_UPDATE_IMAGE} ***"
731827
echo
732828

733829
if [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then

rpi-eeprom-update-default

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
FIRMWARE_ROOT=/lib/firmware/raspberrypi/bootloader
33
FIRMWARE_RELEASE_STATUS="default"
44
FIRMWARE_BACKUP_DIR="/var/lib/raspberrypi/bootloader/backup"
5-
USE_FLASHROM=0
65
EEPROM_CONFIG_HOOK=
76

87
# BOOTFS can be set here to override auto-detection in rpi-eeprom-update
98
#BOOTFS=/boot
9+
10+
# Use flashrom if available to update the bootloader without rebooting - Raspberry Pi 5
11+
#RPI_EEPROM_USE_FLASHROM=1

0 commit comments

Comments
 (0)