Production-grade guide to edge firmware updates covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.
Edge firmware updates are the process of delivering and applying low-level software—device drivers, bootloaders, OS kernels, and runtime components—to edge devices in the field. They matter when a device must run a new version of its firmware without requiring physical access, and when reliability, consistency, and speed are critical: after a network outage, when a sensor fails to report data, or when a new security patch must roll out across 10,000 edge nodes simultaneously.
To deploy a new firmware image, begin by building a .fit (Flattened Image Tree) file for a device with a U-Boot bootloader. The image must include a kernel, device tree, and root filesystem, and be signed with a public key.
# Build a FIT image using mkimage
mkimage -f fit.its -k kernel.ub -d rootfs.cpio -T flat_dt -C none -n "Edge Node Firmware" -e 0x40000000 -a 0x40000000 -o edge-firmware.fit
The fit.its file defines the image structure:
/dts-v1/;
/include/ "fit.its";
/ {
description = "Edge Node Firmware Image";
#address-cells = <1>;
images {
kernel {
description = "Linux Kernel";
data = /incbin/("zImage");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x40000000>;
entry = <0x40000000>;
hash = "sha256";
};
dtb {
description = "Device Tree Blob";
data = /incbin/("am57xx-evm.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x40000000>;
hash = "sha256";
};
rootfs {
description = "Root Filesystem";
data = /incbin/("rootfs.cpio");
type = "ramdisk";
arch = "arm64";
compression = "none";
load = <0x41000000>;
hash = "sha256";
};
};
configurations {
default = "conf1";
conf1 {
description = "Production Configuration";
kernel = "kernel";
fdt = "dtb";
ramdisk = "rootfs";
loadables = "kernel fdt ramdisk";
os = "linux";
arch = "arm64";
entry = <0x40000000>;
};
};
};
After building the FIT image, sign it using openssl and u-boot-tools:
# Sign the FIT image with a private key
openssl dgst -sha256 -sign private.pem -out edge-firmware.fit.sig edge-firmware.fit
# Embed signature in FIT image
mkimage -l edge-firmware.fit
# Then use: fit_image_add_signature() to append .sig
Use the OTA firmware update protocol defined by the Edge Firmware Update (EFU) standard. The protocol is based on a simple REST API over HTTPS, with a /updates endpoint.
POST /updates HTTP/1.1
Host: firmware.edge.example.com
Content-Type: application/json
{
"target_device": "edge-node-01",
"firmware_version": "2.1.4",
"image_url": "https://firmware.edge.example.com/images/edge-firmware-2.1.4.fit",
"signature_url": "https://firmware.edge.example.com/images/edge-firmware-2.1.4.fit.sig",
"checksum": "sha256:abc123...",
"reboot_after": true,
"timeout": 300
}
The edge device polls the server every 15 minutes using a lightweight MQTT client. When a new update is available, it downloads the image in chunks and verifies it:
// In device firmware (C code)
int efu_download_and_verify(const char *url, const char *signature_url) {
int fd = open("/tmp/firmware.fit", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return -1;
// Download image in 16KB chunks
struct http_client client;
http_client_init(&client, url);
http_client_start(&client);
uint8_t buffer[16384];
size_t total = 0;
while ((size_t len = http_client_read(&client, buffer, sizeof(buffer))) > 0) {
write(fd, buffer, len);
total += len;
}
// Verify SHA256 checksum
uint8_t expected_hash[32];
read_signature_file(signature_url, expected_hash);
uint8_t actual_hash[32];
sha256_file("/tmp/firmware.fit", actual_hash);
if (memcmp(expected_hash, actual_hash, 32) != 0) {
close(fd);
return -1; // Verification failed
}
close(fd);
return 0;
}
Edge devices must support dual-boot partitions to allow seamless updates. The system uses two boot partitions: boot_a and boot_b, each containing a complete firmware image (including kernel, dtb, rootfs).
During update, the new firmware is written to the inactive partition. The device boots from the active partition, and the update process updates the inactive one.
# After downloading the new image to /mnt/updates/edge-firmware.fit
# Mount the inactive partition (e.g., /dev/mmcblk0p2) as /mnt/boot_b
sudo mount /dev/mmcblk0p2 /mnt/boot_b
sudo cp /tmp/firmware.fit /mnt/boot_b/firmware.fit
sudo cp /tmp/firmware.fit.sig /mnt/boot_b/firmware.fit.sig
# Update U-Boot environment variables
sudo fw_setenv boot_part 2
sudo fw_setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait"
sudo fw_setenv bootcmd "mmc dev 0; bootfit 0:1"
The U-Boot boot sequence now checks for a boot_fit flag and chooses the correct partition:
```u-boot => print bootcmd bootcmd=mmc dev 0; bootfit 0:1 => print bootargs bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait => print boot_part boot_part=2 => print boot_fit boot_fit=1
When the update is applied, the device can be rebooted with `boot_fit=1`, and U-Boot will load the new image from the inactive partition.
### Rolling Back After Failure
A common failure mode: the new firmware boots but fails to initialize a critical sensor, causing the device to stall. The device must roll back to the previous firmware.
Use the **rollback mechanism** triggered by a `rollback` flag in U-Boot:
```u-boot
=> setenv rollback 1
=> saveenv
=> reset
On boot, U-Boot checks the rollback variable. If set, it:
boot_a to boot_bboot_part=1rollbackThe rollback logic is implemented in a U-Boot script (rollback.scr):
```u-boot
setenv boot_part 1 setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p1 rw rootwait" setenv bootcmd "mmc dev 0; bootfit 0:1" saveenv reset
To trigger a rollback from within the Linux kernel:
```c
void efu_trigger_rollback(const char *reason) {
FILE *f = fopen("/etc/efu/rollback.reason", "w");
fprintf(f, "Update failed: %s\n", reason);
fclose(f);
// Write to U-Boot environment
system("fw_setenv rollback 1");
system("fw_saveenv");
system("reboot");
}
Each update is tracked via a metadata store. The device maintains a JSON file at /etc/efu/updates.json:
{
"current": {
"version": "2.1.3",
"partition": "boot_a",
"installed_at": "2024-03-15T12:00:00Z",
"checksum": "sha256:ef4567..."
},
"pending": {
"version": "2.1.4",
"partition": "boot_b",
"downloaded_at": "2024-03-15T12:15:30Z",
"signature_verified": true,
"applied": true
},
"history": [
{
"version": "2.1.2",
"status": "success",
"timestamp": "2024-03-01T08:00:00Z",
"rollback_reason": "Sensor calibration failed"
},
{
"version": "2.1.1",
"status": "failed",
"timestamp": "2024-02-28T14:22:10Z",
"rollback_reason": "Network timeout during boot"
}
]
}
The metadata is updated by a efu-update daemon:
# efu-update.py
import json
import os
import subprocess
import time
UPDATE_PATH = "/etc/efu/updates.json"
def read_metadata():
with open(UPDATE_PATH, 'r') as f:
return json.load(f)
def write_metadata(metadata):
with open(UPDATE_PATH, 'w') as f:
json.dump(metadata, f, indent=2)
def apply_update(version, partition, success=True):
meta = read_metadata()
meta["pending"]["version"] = version
meta["pending"]["partition"] = partition
meta["pending"]["applied"] = True
if success:
meta["current"] = meta["pending"]
meta["history"].insert(0, {
"version": version,
"status": "success",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ")
})
else:
meta["history"].insert(0, {
"version": version,
"status": "failed",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ")
})
write_metadata(meta)
The most silent failure: a device boots with the new firmware but never reports back to the update server.
Key error codes:
The device logs these errors and waits 15 minutes before retrying. After three failed attempts, it enters update recovery mode:
# In recovery mode
cd /tmp/recovery
wget http://firmware.edge.example.com/recovery.tar.gz
tar -xzf recovery.tar.gz
./recovery.sh
The recovery script:
#!/bin/sh
# recovery.sh
set -e
echo "Starting edge firmware recovery..."
# Check for network
if ! ping -c 3 firmware.edge.example.com; then
echo "Network unreachable. Trying to configure static IP..."
ip addr add 192.168.1.100/24 dev eth0
ip route add default via 192.168.1.1
fi
# Download latest firmware from server
wget -O /tmp/latest.fit http://firmware.edge.example.com/images/latest.fit
wget -O /tmp/latest.fit.sig http://firmware.edge.example.com/images/latest.fit.sig
# Verify and apply
if ! sha256sum -c /tmp/latest.fit.sha256; then
echo "Checksum mismatch. Rebooting to recovery environment."
fw_setenv boot_part 3
fw_setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p3 rw rootwait"
fw_saveenv
reboot
fi
# Copy to boot_a
mount /dev/mmcblk0p1 /mnt/boot_a
cp /tmp/latest.fit /mnt/boot_a/firmware.fit
cp /tmp/latest.fit.sig /mnt/boot_a/firmware.fit.sig
umount /mnt/boot_a
# Finalize
fw_setenv boot_part 1
fw_setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p1 rw rootwait"
fw_saveenv
reboot
boot_part vs boot_fit: boot_part=1 selects the partition, but boot_fit=1 enables FIT image loading. Misconfiguring one without the other causes the device to boot from the wrong image.reboot_after vs apply_immediately: reboot_after means the device reboots after download but before applying. apply_immediately means the device applies the update and immediately reboots. Confusing the two leads to unexpected boot order and missed updates.timeout in OTA request: if the device takes longer than timeout seconds to apply the update, the server marks it as incomplete. The device must send a PATCH /updates/{id} with status=applied and timestamp.checksum field: the server expects a sha256:... format, but many clients send sha256=... or just abc123.... The server logs checksum format mismatch and retries the update.signature_url is optional but critical: if missing, the device assumes the signature is embedded in the FIT image. If present but mismatched, the device logs signature verification failed: expected <hash>, got <hash> and triggers rollback.To verify that an update succeeded, check the device logs:
journalctl -u efu-update.service --since "2024-03-15 12:00:00"
Expected output:
Mar 15 12:15:30 edge-node-01 efu-update[123]: Downloaded firmware.fit from https://firmware.edge.example.com/images/edge-firmware-2.1.4.fit
Mar 15 12:16:15 edge-node-01 efu-update[123]: Verified SHA256 checksum: abc123...
Mar 15 12:16:20 edge-node-01 efu-update[123]: Applied firmware to boot_b
Mar 15 12:16:25 edge-node-01 efu-update[123]: Update applied successfully
Mar 15 12:16:30 edge-node-01 efu-update[123]: Rebooting in 5 seconds...
The update service also emits a systemd event:
systemctl --state=active --property=SubState efu-update.service
Returns:
SubState=running
When the device reboots, the efu-update.service logs status=applied to the server via:
PATCH /updates/123 HTTP/1.1
Host: firmware.edge.example.com
Content-Type: application/json
{
"status": "applied",
"timestamp": "2024-03-15T12:16:30Z"
}
If the server receives this, the update is marked as complete.
This page was rewritten on 10 October 2026. It replaced a templated version whose text was largely shared with other pages in this section and was not specific to its own title. The new text was drafted with a locally run language model, checked by a separate reviewer model for specificity and for invented figures, and measured against its sibling pages for duplication before publication. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy