Categories
Blog Posts

Torrox Prefers C++ Over C

If you ask me to write firmware for your embedded hardware, I will most likely offer you a solution written in C++.

I will provide a C++ solution unless you explicitly request it to be written in C. While there are a few legitimate reasons to choose C over C++, many arguments suggesting that C is preferable to C++ for embedded systems are often misleading or untrue.

Technical arguments, such as “bloated program code” or “excessive memory usage” can be easily debunked. C++ is essentially a superset of C, so there is no inherent reason why a C program compiled with a C++ compiler should become bloated or suddenly consume more RAM. If a certain C++ feature incurs costs that are not affordable, that feature can simply be avoided.

More subjective arguments like “code obscures what it does” or “C++ is too complex/complicated” boil down to the need to know the tools in use. For example, using floating-point arithmetic on an 8-bit MCU can lead to the same surprises as the naive use of STL containers.

This leads to the first valid reason to avoid C++: If customers plan to maintain the firmware themselves but do not have personnel proficient in C++, it might be more practical to write the firmware in C. (And the lack of C++ knowledge could simply be due to not being a full-time software developer.)

Secondly, once in a while (but rather seldom), a C++ compiler might not be available for the hardware platform.

C++ Has a Lot to Offer

C++ has so much to offer that its features simply cannot be ignored. Most C compilers nowadays are C++ compilers under the hood that have some special C mode. So the tooling comes basically for free and is already installed on your machines.

Testability

Correctness and robustness are software attributes that are highly valued in the embedded industry due to the consequences that come with bugs in software. This leads to an increased value of tests. This begins with thorough unit tests, where abstractions are required to isolate the different parts of the software so that they become testable. C++ provides a rich set of abstractions which, most of the time, have zero additional costs/overhead (so-called Zero Cost Abstractions).

Correctness

C++’s rich type system allows attaching all kinds of information at compile time to a value. Tracking the physical unit and scale of a value is an example where this can be utilized to find bugs of mismatching units at compile time without any additional runtime costs.

Catching bugs at compile time greatly reduces the time required to debug software at runtime.

Small Binaries

To keep firmware sizes small, it makes sense to move as much logic/calculations from runtime to compile time. When searching for the correct settings of a PLL, looping over a set of settings to find the one that results in the desired frequency is a straightforward algorithm that we find in a lot of vendors’ SDKs. When the desired frequency is fixed and known at compile time, a C++ implementation can guarantee that the algorithm will find the correct settings at compile time!

Coping with Complexity, Increasing Productivity

Software development for embedded systems started with a couple of hundred assembler lines a long time ago. Currently, we see a trend to implement an increasing amount of a system’s requirements in software. Complex protocols like WLAN or Bluetooth are more flexible to be handled in software and implemented by increasingly large SoCs (or even multiple SoCs).

C++ provides better tools to handle increasing complexity.

Categories
Blog Posts

A Bootloader Checklist

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.

Communication Interfaces

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.

Client

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.

Different Architectures

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.

Bootloader Update

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

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.

The Check List