Skip to content

Commit 67ef1cf

Browse files
committed
Update partitioning to support tryboot to auto-revert
This updates the `fwup` configuration to support the Raspberry Pi tryboot feature so that it's possible to recover from messed up Linux kernels and OTP boot failures. The way this works is that after the firmware update is applied, the system is rebooted one time into the new firmware slot. If the firmware slot is good, then the app needs to call `Nerves.Runtime.validate_firmware` to make it so that subsequent boots use the new slot. The theory is that the app won't validate the firmware unless it's good, so that if anything crashes, the previously working firmware will be used. Even though the partitioning looks different, it's possible to upgrade non-tryboot Nerves Raspberry Pi firmware without losing application data. This is supported by lining up partitions so that they're close to the same places. There's no going back, though. Also, there's a window where cancelling the firmware update can brick the Pi due to the rootfs partition needing to be shifted, so be careful.
1 parent cbe6cdd commit 67ef1cf

File tree

10 files changed

+512
-129
lines changed

10 files changed

+512
-129
lines changed

REUSE.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ SPDX-License-Identifier = "CC0-1.0"
6666
[[annotations]]
6767
path = [
6868
"busybox.fragment",
69-
"cmdline.txt",
69+
"cmdline-a.txt",
70+
"cmdline-b.txt",
7071
"config.txt",
7172
"rootfs_overlay/etc/boardid.config",
7273
"rootfs_overlay/etc/erlinit.config",

cmdline.txt renamed to cmdline-a.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
# 2. If not using HDMI, remove console=tty1 and consoleblank=0
66
# 3. quiet skips printing kernel messages to the output and significantly
77
# shortens boot time
8-
dwc_otg.lpm_enable=0 console=tty1 console=serial0,115200 fbcon=scrollback:1024k root=/dev/mmcblk0p2 rootfstype=squashfs rootwait consoleblank=0 quiet
8+
dwc_otg.lpm_enable=0 console=tty1 console=serial0,115200 fbcon=scrollback:1024k root=/dev/mmcblk0p5 rootfstype=squashfs rootwait consoleblank=0 quiet

cmdline-b.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# See cmdline-a.txt
2+
dwc_otg.lpm_enable=0 console=tty1 console=serial0,115200 fbcon=scrollback:1024k root=/dev/mmcblk0p6 rootfstype=squashfs rootwait consoleblank=0 quiet

fwup-ops.conf

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,37 @@ task factory-reset {
4141
##
4242
task prevent-revert.a {
4343
# Check that we're running on B
44-
require-partition-offset(0, ${BOOT_B_PART_OFFSET})
45-
require-partition-offset(1, ${ROOTFS_B_PART_OFFSET})
46-
require-uboot-variable(uboot-env, "nerves_fw_active", "b")
44+
require-path-at-offset("/", ${ROOTFS_B_PART_OFFSET})
4745

48-
on-init {
46+
on-resource autoboot-b.txt {
4947
info("Preventing reverts to partition A")
48+
# Ensure that autoboot is booting the B partition
49+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
50+
uboot_setenv(uboot-env, "nerves_fw_active", "b")
51+
uboot_setenv(uboot-env, "b.nerves_fw_validated", "1")
5052
# Remove U-Boot variables that fwup uses to allow reverting images
5153
uboot_unsetenv(uboot-env, "a.nerves_fw_platform")
5254
uboot_unsetenv(uboot-env, "a.nerves_fw_architecture")
55+
uboot_setenv(uboot-env, "a.nerves_fw_validated", "0")
5356
# Clear out the old image using TRIM. This requires --enable-trim
5457
trim(${ROOTFS_A_PART_OFFSET}, ${ROOTFS_A_PART_COUNT})
5558
trim(${BOOT_A_PART_OFFSET}, ${BOOT_A_PART_COUNT})
5659
}
5760
}
5861
task prevent-revert.b {
5962
# Check that we're running on A
60-
require-partition-offset(0, ${BOOT_A_PART_OFFSET})
61-
require-partition-offset(1, ${ROOTFS_A_PART_OFFSET})
62-
require-uboot-variable(uboot-env, "nerves_fw_active", "a")
63+
require-path-at-offset("/", ${ROOTFS_A_PART_OFFSET})
6364

64-
on-init {
65+
on-resource autoboot-a.txt {
6566
info("Preventing reverts to partition B")
67+
# Ensure that autoboot is booting the A partition
68+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
69+
uboot_setenv(uboot-env, "nerves_fw_active", "a")
70+
uboot_setenv(uboot-env, "a.nerves_fw_validated", "1")
6671
# Remove U-Boot variables that fwup uses to allow reverting images
6772
uboot_unsetenv(uboot-env, "b.nerves_fw_platform")
6873
uboot_unsetenv(uboot-env, "b.nerves_fw_architecture")
74+
uboot_setenv(uboot-env, "b.nerves_fw_validated", "0")
6975
# Clear out the image using TRIM. This requires --enable-trim
7076
trim(${ROOTFS_B_PART_OFFSET}, ${ROOTFS_B_PART_COUNT})
7177
trim(${BOOT_B_PART_OFFSET}, ${BOOT_B_PART_COUNT})
@@ -82,63 +88,47 @@ task prevent-revert.fail {
8288
##
8389
task revert.a {
8490
# This task reverts to the A partition, so check that we're running on B
85-
require-partition-offset(0, ${BOOT_B_PART_OFFSET})
86-
require-partition-offset(1, ${ROOTFS_B_PART_OFFSET})
87-
require-uboot-variable(uboot-env, "nerves_fw_active", "b")
88-
89-
# Verify that partition A has the expected platform/architecture
90-
require-uboot-variable(uboot-env, "a.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
91-
require-uboot-variable(uboot-env, "a.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
91+
# and A is valid.
92+
require-path-at-offset("/", ${ROOTFS_B_PART_OFFSET})
93+
require-uboot-variable(uboot-env, "a.nerves_fw_validated", "1")
9294

93-
on-init {
95+
on-resource autoboot-a.txt {
9496
info("Reverting to partition A")
95-
96-
# Switch over
97+
# Update the autoboot first and then the tracking U-Boot variables
98+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
9799
uboot_setenv(uboot-env, "nerves_fw_active", "a")
98-
mbr_write(mbr-a)
99100
}
100101
}
101102

102103
task revert.b {
103104
# This task reverts to the B partition, so check that we're running on A
104-
require-partition-offset(0, ${BOOT_A_PART_OFFSET})
105-
require-partition-offset(1, ${ROOTFS_A_PART_OFFSET})
106-
require-uboot-variable(uboot-env, "nerves_fw_active", "a")
107-
108-
# Verify that partition B has the expected platform/architecture
109-
require-uboot-variable(uboot-env, "b.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
110-
require-uboot-variable(uboot-env, "b.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
105+
require-path-at-offset("/", ${ROOTFS_A_PART_OFFSET})
106+
require-uboot-variable(uboot-env, "b.nerves_fw_validated", "1")
111107

112-
on-init {
108+
on-resource autoboot-b.txt {
113109
info("Reverting to partition B")
114-
115-
# Switch over
110+
# Update the autoboot first and then the tracking U-Boot variables
111+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
116112
uboot_setenv(uboot-env, "nerves_fw_active", "b")
117-
mbr_write(mbr-b)
118113
}
119114
}
120115

121116
task revert.unexpected.a {
122-
require-uboot-variable(uboot-env, "a.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
123-
require-uboot-variable(uboot-env, "a.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
117+
require-uboot-variable(uboot-env, "a.nerves_fw_validated", "1")
124118
on-init {
125119
# Case where A is good, and the desire is to go to B.
126-
error("It doesn't look like there's anything to revert to in partition B.")
120+
error("It doesn't look like there's valid firmware to revert to in partition B.")
127121
}
128122
}
129123
task revert.unexpected.b {
130-
require-uboot-variable(uboot-env, "b.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
131-
require-uboot-variable(uboot-env, "b.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
124+
require-uboot-variable(uboot-env, "b.nerves_fw_validated", "1")
132125
on-init {
133126
# Case where B is good, and the desire is to go to A.
134-
error("It doesn't look like there's anything to revert to in partition A.")
127+
error("It doesn't look like there's valid firmware to revert to in partition A.")
135128
}
136129
}
137-
138-
task revert.wrongplatform {
139-
on-init {
140-
error("Expecting platform=${NERVES_FW_PLATFORM} and architecture=${NERVES_FW_ARCHITECTURE}")
141-
}
130+
task revert.fail {
131+
on-init { error("Failed") }
142132
}
143133

144134
##
@@ -173,20 +163,26 @@ task status.fail {
173163
##
174164
# validate
175165
#
176-
# The fwup configuration for this device always validates, so this doesn't do anything.
166+
# Update the autoboot.txt file to unconditionally boot the active partition.
177167
##
178168
task validate.a {
179169
require-path-at-offset("/", ${ROOTFS_A_PART_OFFSET})
180-
on-init {
170+
171+
on-resource autoboot-a.txt {
181172
info("Validate A")
173+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
182174
uboot_setenv(uboot-env, "a.nerves_fw_validated", "1")
175+
uboot_setenv(uboot-env, "nerves_fw_active", "a")
183176
}
184177
}
185178
task validate.b {
186179
require-path-at-offset("/", ${ROOTFS_B_PART_OFFSET})
187-
on-init {
180+
181+
on-resource autoboot-b.txt {
188182
info("Validate B")
183+
fat_write(${AUTOBOOT_PART_OFFSET}, "autoboot.txt")
189184
uboot_setenv(uboot-env, "b.nerves_fw_validated", "1")
185+
uboot_setenv(uboot-env, "nerves_fw_active", "b")
190186
}
191187
}
192188
task validate.fail {

0 commit comments

Comments
 (0)