-
Notifications
You must be signed in to change notification settings - Fork 47
Firmware Updates
- How It Works
- The
fwupdate
Command - Versions
- Latest Version
- Pre-upgrade Scripts
- Initramfs Init Script
- Post-upgrade Scripts
- Partition Sizes
- Rebuilding Initramfs Images
The firmware update process is divided into five successive steps:
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 theOS_PREFIX
OS variable -
\${os_short_name}
- the OS short name, defined by theOS_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
.
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.
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.
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.
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 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).
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 theOS_PREFIX
OS variable -
\${os_short_name}
- the OS short name, defined by theOS_SHORT_NAME
OS variable
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 byOS_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 theOS_PREFIX
OS variable -
\${os_short_name}
- the OS short name, defined by theOS_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
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
androot
partitions are mounted as read-write and still contain the old OS - the new
boot
androot
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.
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.
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
.
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.
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.
- bluetooth.conf
- captive-portal.conf
- cpufreq.conf
- date.conf
- dnsmasq.conf
- docker-compose.yml
- dtoverlays
- dyndns-update.sh
- environment
- firewall.sh
- fstab.user
- hostapd.conf
- ifalias.conf
- localtime
- modprobe.conf
- modules
- mongodb.conf
- netwatch.conf
- ntp.conf
- os.conf
- proftpd.conf
- redis.conf
- smb.conf
- ssh/config
- ssh/sshd_config
- ssl/domain
- ssl/email
- static_ip.conf
- sysctl.conf
- toemmc.conf
- version
- watchdog.conf
- wpa_supplicant.conf