forked from raspberrypi/rpi-eeprom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rpi-eeprom-update
executable file
·411 lines (344 loc) · 12.7 KB
/
rpi-eeprom-update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#!/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
CRITICAL_UPDATE_IMAGE=""
CRITICAL_UPDATE_VERSION=0
getLatestCriticalUpdate() {
CRITICAL_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
CRITICAL_UPDATE_VERSION=$(strings "${latest}" | grep BUILD_TIMESTAMP | sed 's/.*=//g')
CRITICAL_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 critical updates directory ${FIRMWARE_IMAGE_DIR} not found."
fi
if vcgencmd bootloader_config | grep -qi "Command not registered"; then
die "vcgencmd: bootloader_config. 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 there 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 critical 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 paramters.
The official documentation for the Raspberry Pi bootloader EEPROM is available here:-
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
getLatestCriticalUpdate
if [ "${CRITICAL_UPDATE_VERSION}" -gt "${CURRENT_VERSION}" ]; then
printVersions "${CURRENT_VERSION}" "${CRITICAL_UPDATE_VERSION}"
echo "*** INSTALLING REQUIRED UPDATE ***"
applyUpdate "${CRITICAL_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
getLatestCriticalUpdate
if [ "${CRITICAL_UPDATE_VERSION}" -gt "${CURRENT_VERSION}" ]; then
echo "*** UPDATE REQUIRED ***"
printVersions "${CURRENT_VERSION}" "${CRITICAL_UPDATE_VERSION}"
write_status_info EXIT_UPDATE_REQUIRED "${CURRENT_VERSION}" "${CRITICAL_UPDATE_VERSION}"
exit ${EXIT_UPDATE_REQUIRED}
else
echo "Bootloader EEPROM is up to date"
printVersions "${CURRENT_VERSION}" "${CRITICAL_UPDATE_VERSION}"
write_status_info EXIT_SUCCESS "${CURRENT_VERSION}" "${CRITICAL_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