guides/advanced/experimental-features.md
The features described in this document are experimental. They are under consideration and or actively being developed.
Firmware update files (.fw) contain everything your target needs to boot and
run your application. Commonly, this single file package will contain your root
filesystem, the Linux kernel, a bootloader, and some extra files and metadata
specific to your target. Packaging all these files together provides a convenient
and reliable means of distributing firmware that can be used to boot new devices
as well as upgrade existing ones. Unfortunately, this mechanism is not conducive
to applying updates to devices that use expensive metered network connections
where the cost of every byte counts. This problem can be alleviated with firmware
patches.
A firmware patch file's content structure is identical to that of a regular firmware update file. The main difference is that the contents of these files are no longer a bit for bit representation but instead the delta between two known versions of firmware.
To generate a firmware patch file, you will need to supply two full firmware update files, the firmware that the target is updating from (currently running) and the firmware the device will be updating to (the desired new firmware). Attempting to apply a firmware patch to a target that is not running the "from" firmware will return an error.
See fwup_delta for an Elixir library that creates patches.
Firmware update patches will require modifications to the fwup.conf of your
Nerves system. These updates must be applied in full to a running target before
it is capable of applying firmware update patches.
In your fwup.conf, find the references to rootfs.img, in typical systems
there will be 4 references.
file-resource:
Unchangedcomplete task:
Unchanged. When writing a complete firmware on to a new device. A patch
cannot be applied on the target.upgrade.a task:
When new firmware is written in to firmware slot a.upgrade.b task:
When new firmware is written in to firmware slot b.We only need to modify the actions taken in the upgrade.a and upgrade.b steps.
Change the reference in the upgrade.a task:
on-resource rootfs.img { raw_write(${ROOTFS_A_PART_OFFSET}) }
To:
on-resource rootfs.img {
delta-source-raw-offset=${ROOTFS_B_PART_OFFSET}
delta-source-raw-count=${ROOTFS_B_PART_COUNT}
raw_write(${ROOTFS_A_PART_OFFSET})
}
Change the reference in the upgrade.b task:
on-resource rootfs.img { raw_write(${ROOTFS_B_PART_OFFSET}) }
To:
on-resource rootfs.img {
delta-source-raw-offset=${ROOTFS_A_PART_OFFSET}
delta-source-raw-count=${ROOTFS_A_PART_COUNT}
raw_write(${ROOTFS_B_PART_OFFSET})
}
You'll also need to ensure that your system is being build using
nerves_system_br >= 1.11.2. This will be in your mix dependencies. If you
attempt to apply a firmware patch to a device that does not support it, you
will receive an error similar to the following:
Running fwup...
fwup: Upgrading partition B
fwup: File 'rootfs.img' isn't expected size (7373 vs 49201152) and xdelta3 patch support not enabled on it. (Add delta-source-raw-offset or delta-source-raw-count at least)
Creating small firmware deltas requires tradeoffs. Internally, the xdelta3
tool finds common data between firmware images. It's good, but the following
work against it:
One way to start is to experiment with setting mksquashfs_flags to your
project's mix config.
# Customize non-Elixir parts of the firmware. See
# https://hexdocs.pm/nerves/advanced-configuration.html for details.
config :nerves, :firmware,
rootfs_overlay: "rootfs_overlay",
mksquashfs_flags: ["-noI", "-noId", "-noD", "-noF", "-noX"]
Patch sizes can also be optimized by configuring the build system's
source_date_epoch date. This will help with reproducible builds by preventing
timestamps modifications from affecting the output bit representation.
# Set the SOURCE_DATE_EPOCH date for reproducible builds.
# See https://reproducible-builds.org/docs/source-date-epoch/ for more information
config :nerves, source_date_epoch: "1596027629"
Packages can provide custom system environment variables to be exported when
Nerves.Env.bootstrap/0 is called. The initial use case for this feature is to
export system specific information for llvm-based tools. Here is an example from
nerves_system_rpi0
defp nerves_package do
[
# ...
env: [
{"TARGET_ARCH", "arm"},
{"TARGET_CPU", "arm1176jzf_s"},
{"TARGET_OS", "linux"},
{"TARGET_ABI", "gnueabi"}
]
# ...
]
end