Skip to content

os_update: write bootloader artifacts to an eMMC hardware boot partition - #25

Open
mobileoverlord wants to merge 6 commits into
mainfrom
jschneck/emmc-boot-target
Open

os_update: write bootloader artifacts to an eMMC hardware boot partition#25
mobileoverlord wants to merge 6 commits into
mainfrom
jschneck/emmc-boot-target

Conversation

@mobileoverlord

@mobileoverlord mobileoverlord commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #24 (uses locate_target).

What

A slot target named emmc-boot:<n> (in the manifest's slot_partitions) addresses the n-th eMMC hardware boot partition (/dev/mmcblkXbootN) of the disk holding the var partition.

How

  • Resolved at run time from /dev/disk/by-partlabel/var's parent disk, since mmcblk numbering is kernel-dependent.
  • Write lifts force_ro, writes + fsyncs, reads back and compares, restores force_ro on every path. A disk without boot partitions (SD card) is refused with a clear message instead of writing elsewhere.
  • Staged and streaming paths both supported (streaming spools to a file for the read-back check).

Lets an i.MX stone manifest carry imx_boot as an OS artifact (slot a → boot0, slot b → boot1), so the bootloader — and the FIT key it enforces — ships with the OS update it belongs to. Activation/rollback flip PARTITION_CONFIG with the existing command action (avocado-imx-bootpart <slot>, meta-avocado). No stone changes.

…ly without one

A bundle's layout (devpath + computed offsets) was preferred over the
partition's PARTLABEL whenever the bundle carried one, so a GPT artifact was
written by raw offset to the manifest's devpath. On i.MX that device name is
kernel-dependent (/dev/mmcblk1 in the manifest, /dev/mmcblk2 under 6.18), and
the computed offsets need not match where the flasher placed the partitions:

  Writing rootfs_hash -> /dev/mmcblk1@618659840 (partition: rootfs-b-hash)
  Failed to open device /dev/mmcblk1 for rootfs_hash: No such file or directory

Resolve /dev/disk/by-partlabel/<name> first and fall back to the layout only
when udev has no label for the partition (MBR layouts). Both the staged and
the streaming write paths go through the same locate_target.
…ue test label

A label that exists but fails to resolve (dangling symlink, EACCES, I/O
error) is surfaced as the error it is rather than treated as absent, so a
transient failure can never turn into a computed-offset write. The tests
derive their nonexistent label from the pid so attached media cannot collide
with it.
Path::exists() answers false for a dangling symlink or a metadata error, so
both the label check in locate_target and the neighbour scan in
disk_has_any_label could misclassify a labeled disk as unlabeled and enable
raw offset writes. One helper, label_absent, now decides via symlink_metadata
and only a clean NotFound counts; every other outcome is surfaced by
resolve_partition or treated as evidence the disk is labeled.
test_mode_redirects_os_releases_to_tmpdir set TMPDIR=/scratch under
ENV_VAR_MUTEX, but tests in other modules call TempDir::new() without that
lock and read TMPDIR while it is set; whichever one raced into the window
failed on NotFound (seen as config::tests::test_load_invalid_toml in CI).
Use a real, kept directory so a racing TempDir still has somewhere to go.
A slot target named `emmc-boot:<n>` addresses the n-th hardware boot partition
(/dev/mmcblkXbootN) of the disk that holds the var partition, resolved at run
time because the kernel's mmcblk numbering is not stable. The write lifts
force_ro, writes, fsyncs, reads back and compares, and restores force_ro
whatever happened; a disk without boot partitions (an SD card) is refused with
a message rather than written somewhere else. This lets an i.MX stone manifest
carry imx-boot as an OS artifact (slot a -> boot0, slot b -> boot1) so a
bootloader - and the FIT key it enforces - ships with the OS update it belongs
to; activation flips PARTITION_CONFIG through the manifest's existing command
action.
…iled

A manifest describes every medium a machine can be provisioned on. An update
applied while running from an SD card meets an emmc-boot:<n> target for
hardware that is not there; write everything else and say what was skipped,
so the OS update keeps working on every medium and only the pieces that exist
on this one are written. Pure helper + test for the eMMC/SD split.
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Follow-up in f5c5332: a target the boot medium does not have (emmc-boot:<n> while running from SD) is now a documented skip (Skipping imx_boot (emmc-boot:1): mmcblk1 has no eMMC hardware boot partitions ...), not a failure. OS updates keep working on every medium; only what exists on the running one is written.

@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Board result (imx8mp-evk, eMMC): OS update with a changed rootfs wrote the bundle's imx_boot to /dev/mmcblk2boot1 (boot partitions now differ, both carry an IVT), activation flipped PARTITION_CONFIG 0x48 -> 0x50 via avocado-imx-bootpart b, and the device rebooted into slot b from the boot1 bootloader: root on /dev/mapper/root (verity data p6), new AVOCADO_OS_BUILD_ID, pending marker cleared, 0 failed units.

Comment thread src/os_update.rs
let Some(index) = spec.strip_prefix("emmc-boot:") else {
return Ok(None);
};
let n: u8 = index.parse().map_err(|_| {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error: n is parsed as u8, so emmc-boot:2 is accepted despite the message promising <0|1>. emmc_boot_target then finds /dev/mmcblk2boot2 missing and returns NotOnThisMedium, reporting "has no eMMC hardware boot partitions ... running from an SD card" on a board that does have them. The update then proceeds to activation, and avocado-imx-bootpart's only guard is the IVT magic check on the target partition, which a stale image from a previous update passes. A one-character manifest typo therefore activates a new rootfs against the old bootloader, with no error anywhere in the chain, which is exactly the bootloader/FIT-key coupling this PR exists to establish. Everywhere else in this file a bad target name is a hard error; emmc-boot: is the only target that degrades into a silent skip.

Reject n > 1 here, and gate the skip in emmc_boot_target on boot0 being absent (the disk genuinely has no hardware boot partitions) rather than on boot<n> being absent.

Comment thread src/os_update.rs
})?;
drop(f);
let mut back = vec![0u8; data.len()];
fs::File::open(dev)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problem: this read-back is served from the block device page cache, so it compares memory to memory and never reaches the eMMC. Buffered block-device I/O goes through the bdev inode's page cache; sync_all() writes those pages back but leaves them resident and clean, so the File::open(dev) + read_exact immediately after reads the same pages that were just written. The comparison cannot fail.

Nothing regresses relative to having no read-back at all, since a genuine write failure still surfaces as EROFS/ENOSPC/EIO out of write_all. But the check does not provide the property the doc comment and the PR description claim, and this is the one write path where a real read-back is worth having. Invalidate before reading to make it real: BLKFLSBUF on the fd, posix_fadvise(POSIX_FADV_DONTNEED), or reopen with O_DIRECT. Doing the compare in chunks while you are there would also drop the current peak of two full-size buffers resident (fs::read plus vec![0u8; data.len()]), where every other write path in this file streams with 4 MiB buffers.

Comment thread src/os_update.rs

/// Name of the whole disk holding the `var` partition, e.g. `mmcblk2`.
fn root_disk_name() -> Result<String, OsUpdateError> {
let part = fs::canonicalize("/dev/disk/by-partlabel/var").map_err(|e| {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problem: this hard-errors when /dev/disk/by-partlabel/var is absent, which contradicts the rule the second commit establishes. An unlabeled or MBR medium, exactly the case #24 added offset writes for, carries no by-partlabel entries at all, so an emmc-boot:* artifact fails the entire OS update there instead of being skipped as a target this medium does not have. The NotOnThisMedium path only covers media where var is labeled but the boot device is missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants