Starting mcuboot-rs

Page content

MCUboot is a bootloader for microcontrollers. It has a fairly long history and has been under development for a number of years. Over the years, it has gained some significant functionality, and is used as the bootloader for a number of projects (TF-M and Zephyr coming to mind).

MCUboot is somewhat unique as an embedded application in that it tends to be fairly isolated. There are a lot of other projects that make use of MCUboot itself, but MCUboot itself does not have a large number of dependencies. It needs the ability to run as an early application on the target, operate on the flash device, and perform some cryptographic operations. It optionally can support some types of logging to help with debugging and development.

The code is mostly written in C, and makes use of several possibly platforms for board support, including Zephyr, MyNewt, as well as a couple of board-specific SDKs for bare-metal. Although there are some ifdefs throughout the code to support this, the code largely is indepent of the platform it runs on.

In addition to these targets, the project also contains a simulator, written in Rust, that links against the C code and stress tests the code, especially simulating bad powerdowns that would be difficult to recreate on real hardware.

Because of this somewhat isolated nature of MCUboot, it seems like it would be an almost ideal application to test the waters, as it were, of rust-embedded.

I have developed the beginnings of mcuboot-rs to do just this. The README there gives a better summary of the status, but suffice it to say that this is just a starting point, and only scratches the surface of what MCUboot does. But, it is still a good example of what can be done with rust-embedded.

Rust Embedded

I spend some of my time working on Zephyr. Zephyr is a fairly traditional type of real-time embedded OS (RTOS). It is a fairly large body of software that provides most of the functionality needed to develop an embedded application, including scheduling, board support and device drivers.

The rust-embedded project takes a bit of a different approach. Instead of a single entity that provides everything, the needed functionality is provided by various crates. Because the bootloader does not need threading or scheduling, I have not needed to pull in any kind of scheduler or executor. For my case, I pull in just a few crates into this project:

  • lpc55-hal. This is a hardware abstraction layer provider for the LPC55S69 board that I am starting with. It makes use of a “PAC” crate that contains the actual register definitions for the hardware, and provides a friendlier set of routines for accessing the devices. There is ongoing work to have the various HALs comply with an embedded-hal crate. Because I am only starting with a single board, this generalization isn’t yet useful to me.
  • cortex-m and cortex-m-rt. These provide support for running bare-metal on various Cortex-M processors. I mainly need to directly reference these to make use of a routine to chain boot into another image.
  • embedded-storage. This is a set of traits that a hal can implement to provide device drivers for flash devices. I have built my main boot code around this abstraction to avoid tying it to any particular device.

There are two parts of the bootloader code, currently:

  • boot. This is a crate that doesn’t know anything about the hardware or environment it is running on. In fact, it builds just fine on a Linux machine, where cargo test is able to run the unit tests from it. Being able to develop the core of the code as if it were just a regular Linux program is very convenient. Some care does have to be taken so that it can build and run in the no_std environment needed on the target.
  • boards/lpc55s69. This is the main of the program. Ideally, it will be rather small, and only contain the code that is specific to a given board. For this prototype, it is a bit larget. I am not using flash drivers, yet, since the code only reads from the flash, so a faked driver is part of this crate as well.

This configuration makes a fairly small bootloader that can verify the SHA256 of images, and chain boot into the application.

Impressions

There is definitely a lot more work that would need to be done to make something that had the functionality of mcuboot. However, this is a really good start, and demonstrates some of the real benefits to developing this type of application in Rust.

Better Isolation

Rust is very good at allowing abstractions. A signifant mechanism it uses is that of a “trait”. A trait defines a set of methods, as well as associated types and constants, that code be used as an interface. It can be thought of similarly to an interface in Java. In the case of the bootloader, the interface to a flash device is represented by the ReadNorFlash trait, which merely requires a few things to be implemented:

  • READ_SIZE is an associated constant that gives the minimum size the read command supports.
  • capacity returns the size of the area.
  • read does the actual read.

In addition, the trait inherits on ErrorType which requires there to be an error type. This error type has a query method kind() which allows the code to be generally used, in code that doesn’t have to know the specific error types used in a given flash driver.

In addition to this, I have also added my own trait MappedFlash that is supported by flash memories that are directly mapped into memory. For many MCUboot targets, the primary flash is also where code is run from (execute in place, or XIP). These drivers can support this trait to indicate how, given a flash area, what is he base address in memory where that data will appear. It is important, however, that boot not make use of this, but instead use the abstractions provided by the ReadNorFlash. Fortunately, Rust happily enforces this for any code that does not directly state the dependency on MappedFlash.

Later, I will make use of NorFlash which adds erase and write support.

Awkward starting

It is easy to get a bit spoiled by Zephyr’s almost magical target and board indepence. Much of the hardware is abstracted away, and MCUboot running on Zephyr really has very little need to know abuot hardware specifics. Rust-embedded, on the other hand, requires me to have a main crate that knows intimately about a particular platform. The general idea is to keep these small, only performing the necessary initialization, and have the general boot code in a crate that is completely hardware independent. It is definitely different, and from the perspective of how most embedded work is done, where a focus is made on a specific environment, this works well. The advantage is that not only is boot not dependent on the hardware, but it is independent of even the OS or platform the code might be running on.

Where to go from here

I believe this proof of concept demonstrates that Rust is a viable language and ecosystem to write something like mcuboot. To continue this work, there are multiple areas work can be done:

  • Increase board support. By adding at least one more bare-metal board, it will become more clear what can be abstracted away, and made board independent, and what really needs to be board-specific.
  • More features from mcuboot. Some of the features of mcuboot do depend, not directly on the board, but on the capabilities of the board. This will likely become features within the boot crate. Some ideas of improvements to make:
    • Check signed images. This is a core feature of mcuboot, and fairly straightfoward to do in Rust, as the needed cryptography libraries are available in the no_std environment.
    • Upgrade. This is also an important feature of MCUboot. There are several upgrade strategies, and this is also a good environment to experiment with additional strategies. One of the reasons I chose the LPC board is because it is not currently well supported by MCUboot, and this is a good place to experiment with upgrade algorithms that support this target better.
  • Platforms other than bare-metal. There is some interest in allowing Rust development on top of a more classic RTOS, such as Zephyr. This would provide a useful application to develop this support on.