firmware icon indicating copy to clipboard operation
firmware copied to clipboard

[FEAT] Support `tryboot` helpers without ramdisk

Open bryceschober opened this issue 10 months ago • 11 comments

I'm digesting the implications of the tryboot_a_b update flow, and it's becoming clear that it is more designed for boot systems that depend on a ramdisk. Since the ramdisk would be named the same on the A/B boot partitions, it doesn't need any differences in cmdline.txt, and init logic within it can then use the device tree's chosen/bootloader properties to drive its dynamic logic.

But for systems that would prefer not to have a separate ramdisk, they are currently required to modify their kernel cmdline appropriately for the A/B partition that is being booted, and set up for that during their update process along with modifying the autoboot.txt.

I can think of two possible ways that the rpi firmware could support this use-case:

  1. Support the cmdline setting in the autoboot.txt in addition to the config.txt. This way, the A/B boot partitions could be identical and contain both cmdline_A.txt and cmdline_B.txt files with the appropriate root= values.
  2. Support a [boot_partition=n] filter in config.txt that would be filled by the active configuration from the autoboot.txt logic. This way, the config.txt could conditionally point to an appropriate cmdline*.txt next to it.

bryceschober avatar Feb 01 '25 00:02 bryceschober

  1. Support the cmdline setting in the autoboot.txt in addition to the config.txt. This way, the A/B boot partitions could be identical and contain both cmdline_A.txt and cmdline_B.txt files with the appropriate root= values.

Now that I'm reading this again, I see that this wouldn't be a great option, since the config.txt "downstream" could easily want a different cmdline setting based on other conditions.

Perhaps a third option would be to enable a way for the user to set part of the cmdline contents independently of the cmdline.txt file(s). Perhaps something like this, in autoboot.txt:

[all]
tryboot_a_b=1
boot_partition=2
cmdline_opts=root=/dev/mmcblk0p4
[tryboot]
boot_partition=3
cmdline_opts=root=/dev/mmcblk0p5

@timg236 I notice that you had submitted the rpi-eeprom package to buildroot recently... What do you think the chances are of this use-case gaining support in the rpi firmware?

bryceschober avatar Feb 06 '25 23:02 bryceschober

2. Support a [boot_partition=n] filter in config.txt that would be filled by the active configuration from the autoboot.txt logic. This way, the config.txt could conditionally point to an appropriate cmdline*.txt next to it.

This seems the better choice when combined with secure boot, as otherwise unauthenticated cmdline arguments could be injected via autoboot.txt

jluebbe avatar Feb 07 '25 07:02 jluebbe

Hi. Are you suggesting that in the following case:

[all]
tryboot_a_b=1
boot_partition=2
[tryboot]
boot_partition=3

..you would have a config.txt on both p2 and p3, but rather than read cmdline.txt from p2 (or p3), have something like the following in each config.txt (example below for p2):

[boot_partition=2]
<cmdline args specific to p2>

?

What advantage would that give you over what's supported today? If the bootloader has been instructed to boot from p2 it loads config.txt from p2 and the cmdline.txt found on p2 can be generated at build time to be specific to the OS that's loaded from p2 (e.g. it can can specify a root device that's associated to OS boot from p2 via UUID=xxx or by-label etc). I don't know what you'd gain from having a [boot_partition=2] filter in a config.txt on p2?

learmj avatar Feb 07 '25 11:02 learmj

Hi @learmj, let me explain the option 2 in a bit more detail:

First, the use-case I'm targeting is where the tryboot_a_b feature is used to switch between partitions that are identical in purpose, integrating with a software update mechanism that executes out of one set of partitions, and supports updating a new version of firmware into the other set of partitions. Because the raspberrypi firmware requires a FAT partition for the "firmware" components, this ends up meaning that an A/B-swapping update system with a separate rootfs partition will have a total of at least 5 partitions, for example on an SD card:

  1. mmcblk0p1: (FAT) shared boot partition with only autoboot.txt
  2. mmcblk0p2: (FAT) firmware "slot" 0 with config.txt, cmdline.txt, and kernel image
  3. mmcblk0p3: (FAT) firmware "slot" 1 with config.txt, cmdline.txt, and kernel image
  4. mmcblk0p4: (WhateverFS) root filesystem "slot" 0 with the remainder of the system
  5. mmcblk0p5: (WhateverFS) root filesystem "slot" 1 with the remainder of the system

(FYI, I noticed that I had the wrong root= parameters in my previous comment, which might have caused confusion, so I've fixed it.)

These "slot" 0/1 partitions must be selected in tandem. The firmware configurations and kernel image for slot 1 are built to go together with the rootfs for slot 1, and it is the responsibility of the sw update integration to maintain them in tandem.

The software update system needs to build and deliver "images" (of whatever variety) for the firmware partition that are capable of executing from either partition slot. As it is, the software update mechanism is required to modify the contents of the config.txt after installing it in a particular partition (let's say the "slot" 1), so that it can boot the corresponding rootfs partition.

What I'm suggesting for option 2 is that the firmware partition images would contain a config.txt:

...
# Select rootfs partition matching the firmware partition we're executing from
[boot_partition=2]
cmdline=cmdline.slot0.txt
[boot_partition=3]
cmdline=cmdline.slot1.txt
[all]
...

And every firmware partition image will contain both a cmdline.slot1.txt with:

root=/dev/mmcblk0p4 ...

and also a cmdline.slot2.txt with:

root=/dev/mmcblk0p5 ...

What advantage would that give you over what's supported today? If the bootloader has been instructed to boot from p2 it loads config.txt from p2 and the cmdline.txt found on p2 can be generated at build time to be specific to the OS that's loaded from p2 (e.g. it can can specify a root device that's associated to OS boot from p2 via UUID=xxx or by-label etc). I don't know what you'd gain from having a [boot_partition=2] filter in a config.txt on p2?

And now that I've written all of the above explanation, I see that you're suggesting that the software update builds generate a unique UUID or label for each build of the rootfs partition image, and that the matching cmdline.txt in the matching firmware partition image use the unique filesystem UUID or label to address the matching rootfs partition.

Is that what you meant, @learmj? I guess that does sound like it could just work at the current level of support. Our partition image generation might get a bit more complex, but that's not a big deal.

bryceschober avatar Feb 07 '25 19:02 bryceschober

Is that what you meant

Yes.

The software update system needs to build and deliver "images" (of whatever variety) for the firmware partition that are capable of executing from either partition slot.

IMHO that should be a mandatory requirement for any system intended to generate and deploy updates to a device.

As it is, the software update mechanism is required to modify the contents of the config.txt after installing it in a particular partition (let's say the "slot" 1), so that it can boot the corresponding rootfs partition.

IMHO that should not be required because since there is a config.txt per boot partition (the bootloader looks for this when booting from the partition), this file (and others like cmdline.txt) can be generated offline and as part of the build. This is also stated by the tryboot_a_b documentation

This enables the tryboot switch to be made at the partition level rather than the file-level without having to modify configuration files in the A/B partitions.

I'm afraid I don't agree with your initial view that the tryboot_a_b scheme favours a ramdisk boot.

But for systems that would prefer not to have a separate ramdisk, they are currently required to modify their kernel cmdline appropriately for the A/B partition that is being booted

As I mentioned above, it's possible to generate cmdline.txt offline as part of the build and as you wrote above, generating a known UUID or disk label ahead of time (ie baked into the image by the build system) allows you to embed that in the cmdline.txt, meaning there is no on-device modification required and the update can be written and booted as-is.

learmj avatar Feb 18 '25 15:02 learmj

| As I mentioned above, it's possible to generate cmdline.txt offline as part of the build and as you wrote above, generating a known UUID or disk label ahead of time (ie baked into the image by the build system) allows you to embed that in the cmdline.txt, meaning there is no on-device modification required and the update can be written and booted as-is.

@learmj Looking into using root=UUID= or root=LABEL=, this does not seem to be supported by the kernel directly, meaning that one needs a ramdisk to use those options. The kernel itself only supports a device name (e.g. /dev/nvme0n1p1) or root=PARTUUID=.

So without a ramdisk, your suggestion of having the build generate a known UUID or disk label is not applicable, as far as I can tell.

Using root=PARTUUID= is an option, but will be problematic for updates. Indeed, the PARTUUID will be specific to the partition on which an update will be installed. However, in order to support an A/B update scheme, this decision will need to be taken at runtime, not at buildtime, so it cannot be embedded in the update's cmdline,txt.

Once deployed, the cmdline.txt will still need to be edited to make sure that the root=PARTUUID= option on the bootFS matches the UUID of the partition on which the updated rootFS was installed

Did I miss something?

oli-ben avatar Mar 13 '25 13:03 oli-ben

The latest Pi5 bootloader release adds an experimental feature to config.txt (not autoboot!) so you can write conditionals.

boot_partition here is the partition number that the boot.img was loaded from

[boot_partition=1] cmdline=cmdline-rootfs-A.txt

[boot_partition=2] cmdline=cmdline-rootfs-B.txt

timg236 avatar Mar 13 '25 13:03 timg236

@timg236 This is exactly what we are looking for!

If I read this correctly, the release you are referring to is pieeprom-2025-03-10, as can be seen in this commit

oli-ben avatar Mar 13 '25 13:03 oli-ben

Yes 2025-03-10.

timg236 avatar Mar 13 '25 15:03 timg236

@timg236 Thanks for the feature! (Though I see as you've documented in the changelog, it's also not just for non-ramdisk boot.) We have verified that the new [boot_partition=<n>] filter works as expected in config.txt, and we're currently building a rauc backend around it.

What are the chances of making this feature available also in the RPi 4 / CM4 boot-loader?

bryceschober avatar Mar 21 '25 02:03 bryceschober

The latest version of start4.elf in rpi-update supports "boot_partition" so if you update the start4.elf in your boot.img ramdisk it should work.

N.B. boot_partition doesn't do anything (useful) before the boot-partition is fully resolved, i.e. after EEPROM config, file-system enumeration partition walk.

These features are pretty new and I can't rule out small behavioral tweaks for corner cases. However, I am using for some internal projects so it's good to get feedback from more users.

timg236 avatar Mar 21 '25 08:03 timg236