Skip to content

Firmware Updates

Calin Crisan edited this page Nov 8, 2023 · 33 revisions

How It Works

The firmware update process is divided into five successive steps:

Downloading The New OS Image Archive

The very first thing the update process does is to make sure there's enough space on the SD card. The process will stop immediately unless there are at least 500MB available on the data partition

The OS_FIRMWARE_* settings from os.conf are used to determine the URL where the OS images live and what the latest available version is. See Latest Version below for more details on how the latest version is determined.

The OS_FIRMWARE_URL variable defines a parametrized URL where the firmware OS images can be downloaded from. If the variable is defined, an arbitrary version can be downloaded and installed onto the system. The following placeholders are defined for the content of this variable:

  • \${version} - the version name
  • \${platform} - the name of the platform (board), e.g. raspberrypi64
  • \${os_prefix} - the OS prefix, defined by the OS_PREFIX OS variable
  • \${os_short_name} - the OS short name, defined by the OS_SHORT_NAME OS variable

The backslash \ before the $ dollar sign prevents expanding those values when the OS conf files are sourced in.

The archive file is downloaded in the /data/.fwupdate folder, using curl.

Extracting The OS Image Archive

The OS image archive needs to be extracted before it can be used. It contains the entire disk image that's going to be written to the SD card, compressed using gzip or xz. During this step, the file is decompressed.

The pre-upgrade scripts are run at the end of this steps.

Flashing Boot Partition

First off we make a backup of the root partition in /data/.fwupdate/old_boot. We need this backup for preserving some settings that live on the boot partition and need to be kept across upgrades.

Then we make sure the system isn't rebooted during update (at least not internally), by temporarily renaming the /sbin/reboot file to /sbin/reboot.bak. Scripts and watchdogs that use this command to reboot the system won't be able to do it.

The full OS image contains two partitions: the boot partition and the root partition. During this step, only the boot partition is written. But before that, we ensure that the allocated size for the partition is enough, reallocating it if necessary and possible.

The boot partition is then unmounted and flashed using dd. fdisk is used to determine the partition offset inside the disk image.

The last part of this step is restoring boot configuration. After the boot partition is remounted, if the file /usr/libexec/fw-restore-boot-cfg is present, it is executed with the boot backup directory as argument. The script is responsible for effectively restoring files and/or contents inside files from the old boot directory to the new one. This file is board-specific and may be missing for some boards.

Preparing For Initramfs

We now ensure that the allocated size for the root partition is enough, reallocating it if necessary and possible.

The boot partition is remounted read-write so that boot configuration can be tweaked. The board-specific shell script /usr/libexec/fw-prepare-boot is executed then and it is expected to prepare the boot configuration so that it after reboot, the firmware update initramfs will be used.

The system is then rebooted.

Flashing Root Partition

The board-specific initramfs image file that is present on the boot partition is now used to assure an in-memory root filesystem so that the actual root partition can be safely overwritten.

The special init script on the initramfs partition is executed. It starts by mounting the required pseudo-filesystems, the boot and the data partitions.

If the optional, board-specific file /prepare_initramfs is present on the initramfs image, it is executed to help preparing the board ramfs environment.

Then the root partition is read from the extracted disk image and written on the data partition, using the dd command.

Finally, the board-specific /remove_initramfs shell script is executed to commute the boot configuration back to normal (that is, straight to the OS, using the disk root filesystem).

The system is then rebooted.

The fwupdate Command

The firmware update process is controlled via one single shell script called fwupdate. It can be invoked separately for each upgrade phase or it can be executed once and be instructed to perform a full "unattended" upgrade. The separate phases way is meant to be used by frontends so that some kind of progress can be displayed. The full upgrade command is usually helpful when upgrading manually, from the command line.

Here's a brief explanation of the fwupdate script usage:

  • fwupdate current will display the current version
  • fwupdate latest will display the latest available firmware version
  • fwupdate download <version|latest|url|file> will download the OS image with the given version, from the given URL or local file
  • fwupdate extract will extract the previously downloaded OS image
  • fwupdate flashboot will flash the boot partition using the previously extracted OS image
  • fwupdate flashreboot will switch the boot configuration to flashing mode and will reboot
  • fwupdate status can be used at any moment to display the current firmware updating status, which is one of:
    • idle
    • downloading
    • downloaded [version]
    • extracting [version]
    • extracted [version]
    • flashing boot [version]
    • boot flashed [version]
    • rebooting [version]
    • error: message
  • fwupdate install <version|latest|url|file> will perform a full install of the given version, URL or local file
  • fwupdate auto [on|off] controls automatic updates
  • fwupdate prereleases [on|off] controls prereleases channel

If the phase order is not respected (e.g. extract is called before download), the fwupdate script will complain about missing required files. Calling the script for the same phase twice will simply repeat the phase (e.g. redownload the OS image).

Versions

The version text should respect the semver specifications.

The file /etc/version contains the version of the currently running firmware. The file is also copied to /data/etc/version so that the OS can detect version mismatches between the system (root partition) and the data partition.

If the OS_FIRMWARE_VERSIONS_STABLE/OS_FIRMWARE_VERSIONS_BETA OS variables are set, their content will be used as an URL to download a JSON file with with available versions. Depending on the OS_PRERELEASES value, the stable or the beta variant will be used.

The contents of the versions file is a list of objects with the following fields:

  • url: a full URL to the OS image
  • version: the version name
  • date: the release date in the following format: YYYY-MM-DD

The value of OS_FIRMWARE_VERSIONS_* variables, as well as the content of the url field may can contain the following placeholders:

  • \${platform} - the name of the platform (board), e.g. raspberrypi64
  • \${os_prefix} - the OS prefix, defined by the OS_PREFIX OS variable
  • \${os_short_name} - the OS short name, defined by the OS_SHORT_NAME OS variable

Latest Version

If the OS_FIRMWARE_LATEST_STABLE/OS_FIRMWARE_LATEST_BETA OS variables are set, their content will be used as an URL to download a JSON file with the latest version info. Depending on the OS_PRERELEASES value, the stable or the beta variant will be used.

The contents of the latest version info file is an object with the following fields:

  • url: a full URL to the OS image
  • path: a path (relative to the HTTP root of the domain indicated by OS_FIRMWARE_LATEST_*) to the OS image
  • version: the version name
  • date: the release date in the following format: YYYY-MM-DD

Either url or path must be specified, but not both.

The value of OS_FIRMWARE_LATEST_* variables, as well as the content of the url and path fields may can contain the following placeholders:

  • \${platform} - the name of the platform (board), e.g. raspberrypi64
  • \${os_prefix} - the OS prefix, defined by the OS_PREFIX OS variable
  • \${os_short_name} - the OS short name, defined by the OS_SHORT_NAME OS variable

In the absence of the OS_FIRMWARE_LATEST_* OS variables, the script /usr/libexec/os-latest-version is executed and is expected to return two lines:

  • the first line is the latest stable version
  • the second line is the latest beta version

Each version line is a space-separated line with the following fields:

  • the version name
  • the full URL to the OS image
  • the release date in the following format: YYYY-MM-DD

Pre-upgrade Scripts

Right before actually writing the partitions that come with the new OS image, the root partition of the new image will be unpacked and all scripts in /usr/share/pre-upgrade will be executed, in shell order, if executable. These scripts can be used to prepare the existing system for the upgrade.

The following conditions are met when these scripts are executed:

  • the boot and root partitions are mounted as read-write and still contain the old OS
  • the new boot and root partitions are mounted at ${TMP_BOOT_DIR} and ${TMP_ROOT_DIR}, respectively
  • all environment variables defined in fwupdate are available
  • all environment variables from os.conf and version are available

If a script returns a non-zero exit code, the upgrade process is aborted.

Initramfs Init Script

When the firmware update initramfs boots, it will execute its init script, which will:

  • mount the system SD card/eMMC partitions as needed
  • prepare the Initramfs environment
  • check the firmware files (normally the root partition)
  • write the root partition using dd
  • switch to normal (non-initramfs) boot

As part of the Initramfs prepare step, the /data/.fwupdate/exec_initramfs script will be executed, if present and executable. If the script exits with non-zero status, the Initramfs process is halted. This allows for customizing whatever happens during the Initramfs step.

Post-upgrade Scripts

Using the contents of the files /etc/version and /data/etc/version, the OS can detect if the firmware has been updated and can execute a series of scripts called post-upgrade scripts. Knowing that the data partition is not updated by the firmware update mechanism, these can be used to apply any migrations required for the data partition to properly function with the new version.

Post-upgrade scripts live in /usr/share/post-upgrade and must have the name <version>.sh (e.g. 3.14.15-beta.2.sh). The S14postupgrade init script will check if system version higher than the one on the data partition and will run all post-upgrade scripts that correspond to versions higher than the data version, one by one, in order.

If the file /usr/share/post-upgrade/post-upgrade.sh is present and executable, it will be executed after each upgrade.

Normally post-upgrade scripts do not require a network connection and are executed as early as possible. If however some of your post-upgrade scripts require a network connection, just add a -net suffix to the filename and the S52postupgradenet will execute it right after the network connection is established. For example, 3.14.15-beta.2-net.sh and post-upgrade-net.sh will be executed with a network connection.

Similarly, If some of your post-upgrade scripts require most of the services to be running (e.g. a database), just add an -app suffix to the filename and the S79postupgradeapp will execute it right before starting the app services. For example, 3.14-app.sh and post-upgrade-app.sh will be executed right before starting the app services.

All output from upgrade scripts goes to /var/log/post-upgrade.log.

Partition Sizes

The minimum required free space on the data partition is set to 500MB; it is needed to download and extract the firmware image. The upgrade process will be aborted if free space is not enough.

If the new boot or root partitions are different (normally larget) than the current ones, the upgrade mechanism will try to reallocate them within the available remaining space. With the default size and offset settings, the boot partition can grow up to approximately 100MB, while the root partition can grow up to 900MB. The upgrade process will be aborted if unallocated space is not enough for partition growth.

For more details on partitions, see Partitions.

Rebuilding Initramfs Images

Initramfs images differ from board to board. Each board has its own initramfs image configuration file that has to be used with BuildRoot just like the configuration for the board OS image. These initramfs configuration files live in configs/<board>_initramfs_defconfig. For a Raspberry PI board you would run the following command to rebuild the initramfs image:

./build.sh raspberrypi initramfs

The resulted image is automatically copied to board/<board> directory and will overwrite the existing initramfs image.

Clone this wiki locally