A Bootloader Checklist
When looking systematically on bootloaders involved in embedded software projects, listing the requirements to the bootloader can help to estimate required hardware resources, project costs, and time. No matter if the bootloader is tailored to the project, open source, or comes embedded in the hardware. Checking the requirements once will prevent surprises at the end of the project or even later, when a firmware update in the field is required.
Bootloaders are often added or implemented very late in projects as they usually don’t add value to the development process. Searching for values, that early bootloader integration can add to the project, can help to argue for an early integration. The earlier a bootloader is regularly used during the development process, the better tested the bootloader will be!
Sometime there is only the requirement, that the product under development have to have a bootloader, without any further specification beside the used interface to communicate with the bootloader. I hope this article might be useful to others who are currently in the process to make certain decisions about the use and requirements of a bootloader (or bootloaders) in a project ahead.
What are the questions to ask? What might be the details to keep in mind? What can prove usefulness in the future (even things, one currently can’t think of)? This list does not claim to be complete. If you find something missing on the list, please contact me and I’ll try to add your suggestion to the list.
Update / Install firmware
The ability to update existing firmware or to install firmware the first time on an embedded device might be the feature that is required the most by projects using a bootloader. This feature allows to fix bugs in the firmware, that where found after the firmware was deployed, to add features to the product after it was sold, or even defer the installation of the first firmware until the moment, the product is used the first time. To implement that feature, the bootloader needs to obtain the new firmware somehow and has to store it somewhere. Implementing incremental updates of a firmware can reduce the required bandwidth by an order of magnitude, if only tiny changes to the firmware have to be transmitted.
Used protocols and data formats depend highly on the overall set of implemented requirements, list of supported interface, and hardware to be supported. That’s why bootloaders are often tailored to a specific project.
A bootloader can not only update or install firmware, but of cause also any kind of data like configuration data, licence data, library code, or the bootloader itself.
Integrity Check of the Firmware
If the bootloader is the first piece of software that runs on an embedded hardware, it’s up to the bootloader to start the firmware. There might be reasons (constrains in the startup time for example), to start the firmware without the interaction of the bootloader. If the bootloader is starting the firmware, the bootloader has the opportunity to check the integrity of the firmware before starting the firmware.
By checking the integrity, the bootloader makes sure that the firmware binary was not modified unexpectedly. This can be done, for example by calculating a checksum over the firmware and compare that checksum against a checksum that was calculated when installing the firmware or when the firmware was build. Testing the checksum of the firmware binary before starting the firmware prevents from starting damaged firmware. The reason for damaged firmware binaries could be buggy firmware that wrote configuration data to the wrong address in the flash memory, that is shared between firmware and configuration data or defect storage devices like a defect SD card.
When the integrity check fails, the bootloader would normally not start the firmware but inform the user, start a prior version of the firmware, or go into some kind of error mode.
Such a test could also easily be implemented in the startup code of the firmware itself.
Correct Hardware / Hardware Version?
If a bootloader and it’s associated protocols and update formats are used in multiple projects, care should be taken, that a bootloader does not accept and install firmware from one project to hardware that the firmware is not intended to run on. In that case meta data could be added to the firmware update formats and protocols and the bootloader will then check, that the firmware update is indeed targeted to the hardware, the bootloader is running on.
Authenticity Check the Firmware
In opposite to an integrity check, an authenticity check guaranties, that the checked firmware update comes from a trusted source that is authorized to issue firmware updates for the intended hardware. Cryptographic functions are used to detect tampered firmware. Usually this can not prevent installing software on a device if access to the hardware is given and for example the SWD or JTAG interface of a microcontroller can still be used.
Encrypted Firmware Updates
Most microcontrollers allow to enable some kind of read-back-protection, that prohibits to read the firmware from flash using SWD or JTAG. If this feature is used to protect the intellectual property of the firmwares author, firmware updates have to be encrypted, if the firmware update process implemented by the bootloader is invoked by a not trusted person.
If a connection to a trusted computer can be established, key exchange algorithms could be implemented to encrypt the connection and use it to receive the firmware update.
If read-back-protection is enabled, encryption keys can be stored in flash along with the bootloader. If an attacker would be able to read-back the key, the attacker would probably also be able to read the firmware itself. Once a key was revealed all later updates could also be decrypted.
A bootloader needs some kind of interface (or even multiple interfaces) to receive firmware updates. This could be either bidirectional interfaces like an UART or unidirectional like a firmware image on a read-only boot device. The options greatly differ in terms of bandwidth, reliability, protocol complexity, latency, implementation effort, and so on.
The bootloader sits on one end of the communication interface, on the other end, there have to be a client speaking the same protocols. In the simples case, this client is a file on a SD card. In more complex situations, the client is an application on its own or has to be integrated into existing software or products.
The set of planned supported client platforms can change the design of the client protocol. In most cases, the client has more computing and power resources, which should be kept in mind to keep the design of the bootloader simple and robust.
In any case, thinking about what clients have to be implemented, should help to estimate the required resources.
Usually, the bootloader is deployed to the same hardware and is responsible to update the firmware for a single, given device. To share communication interfaces or to update multiple device, different architectures / topologies can be used.
For example one device (the Gateway or Hub) contains a bootloader or part of an application, that logically connects the communication interface of the device to bootloaders that are deployed on other microcontrollers on the system. That Gateway device uses other communication interfaces to communicate with other bootloaders or directly flashed firmware to other microcontrollers by using their SWD or JTAG interface.
The reason to use that pattern could be to reuse a single communication interface for all devices that need firmware updates in the system or because not all devices have an interface that is accessible.
Usually bootloaders should be simple enough to not need to be updated. However, especially, if complex communication protocols like WLAN or TCP/IP are used, there is a good chance that some of these protocols need an update during the live time of the device.
In the simplest case, a bootloader could be updated by temporarily installing a firmware, that simply reinstalls a new bootloader. Care have to be taken, that flash write protections, enabled by the bootloader does not prohibit this approach.
Reliability is required by every project, but it comes with a price. Basically, there are only three ways, an embedded microcontroller based hardware system can be bricked: First, by corrupting the bootloader itself, second, by not given the bootloader access to the microcontroller (e.g. never running it) and last, but not least by having bugs in the bootloader itself.
If the bootloader gets damaged by a firmware that tries to store some configuration parameters in flash memory, but accidentally stores the configuration in the part of the flash memory, where the bootloader resides, running the bootloader will most likely result in crash. A lot of hardware architectures provide means to secure the bootloader image against being overwritten or being deleted by malfunctioning firmware. If that feature is used, care has to be taken, that this feature does not prevent an update of the bootloader itself.
If the cooperation of the firmware is required to start the bootloader, a bug in the firmware can easily brick the system. By running the bootloader first and by implementing some mean to prevent the bootloader from starting the firmware immediately, the bootloader will always have a chance to replace broken firmware. This could be implemented by sampling a button press or by listening on a communication interface for a defined period, before starting the firmware.
Bugs in the bootloader itself are prevented best, by keeping the bootloader as simple as possible and by thoroughly testing the bootloader. Using a bootloader as early as possible in a project gives confidence, that the used bootloader is reliable and that most bugs are found.