Re: Passing information from bootloader to Zephyr


Marti Bolivar <marti.bolivar@...>
 

Hi David,

Thanks for putting this together.

On 28 June 2017 at 11:59, David Brown <david.brown@...> wrote:
Background
==========

On larger platforms, there will generally be a protocol for passing
information from the bootloader to the operating system that it is
booting.  There are various standards for this, from BIOS, UEFI, to
the more simplistic approach used on many ARM targets of passing a
pointer to a flattened device tree in a register.

Now that we are approaching a 1.0 release of mcuboot, it would be nice
to also have a way for the bootloader to pass information to the
running application.  This will likely be a fairly simple method,
given the memory constraints of the devices typically used by Zephyr.


+1 to simplicity. It's also of course worth noting that we can do this slightly differently across arches if necessary, though it'd be nice to avoid that.
 
It is important to note that the bootloader and application will
generally be built separately, and generally, the bootloader will not
be upgraded, but only the application.  This is why it is necessary
for a possibly upgraded application to know about the bootloader
present in a particular device.

At this time, most of this information is needed by over-the-air
updating, since this effectively has to communicate back to the
bootloader in order to be able to use the new image.  Currently, we're
thinking of:

 - Version information about the bootloader.
 - Information about how to perform an upgrade.
 - Information about the flash device itself, specifically the
   bootloader's ideas of the partitions it cares about.

This is a great list. I'd like to get into some additional details, if possible.

First, I think the application needs to be able to find out how the bootloader was configured, not just its version.

For example, applications may want to know whether the bootloader was configured to swap image data, or if it was built for "overwrite only", or if it's a "split loader", or perhaps some other future boot strategy. You might imagine deploying "canary" builds only to devices which support rollback and not ones which always overwrite old images, for instance. As another example, applications may want to know which signature checking algorithms the bootloader supports, to e.g. determine which of multiple equivalent images signed with different algorithms is appropriate for the current bootloader.

Both of these examples assume deploying different bootloader configurations on the same device. It may not be reasonable to permit this, but I think it's worth considering, especially since saying "that can't happen" forces each application to track its own expectations of the bootloader's configuration, which has its own issues.

Second, I'm not sure if the combination of "how to perform an upgrade" and "ideas of the partitions it cares about" implies the application can signal to the bootloader the results of a given boot, but this seems necessary.

For example, if the bootloader is configured to swap, then when a "test" swapped application boots, it needs to decide to either mark itself as OK or request a revert from mcuboot on the next reset. (More on this topic below).

Third, we may want to consider how to access the public keys trusted by mcuboot from the application, and perhaps other key management. This can be left to a future extension, but it may help point the way on the functions vs. data debate.

Right now, mcuboot supports an array of public keys it trusts when checking a key signature. If these keys are ever compromised, device manufacturers are likely to at least ship future devices with different keys in the bootloader. If they want to continue to upgrade old devices, this means the application must convey the supported keys to the source of firmware updates at runtime. Thinking forward to key revocation is another reason why we will likely want this.
 

Proposal
========

https://github.com/runtimeco/mcuboot/pull/61 has current work on this,
although it is quite tentative.  The idea is to pass a pair of
registers from the bootloader to the application.  One register will
have a magic value, and the other a pointer to a structure (likely in
flash).

Passing a pointer to flash simplifies the design because it will not
be necessary to reserve a section of RAM that the application needs to
avoid using for another purpose.

The structure contains an additional magic value, a version field, and
currently, a function pointer.  Additional queries are made by calling
this function with specific commands.

There is additional complexity because of the function pointer, with
some pros and cons.  Pros:

 - It is fairly easy to add new commands, and even deprecate
   commands.

 - It allows data that must be computed at run time rather than just
   being compile-time constant data.

and some cons as well:

 - It could be considered risky to have the application call into
   code in the bootloader.

 - The code has to be carefully written to not use RAM data, which
   will have been overwritten by the application.

The other option would be to define a larger structure, and place the
current data within this structure.  Pros:

 - No risk of running code in the bootloader.

Cons:

 - Versioning becomes more complicated, and the logic to handle
   various versions must make it into every application.

 - Complex data (e.g. a representation of the partitions) needs to be
   determined at compile time so it can be placed in a ROM data
   structure.

It would also be possible to place this structure in RAM, if we can
define an area for this purpose.  Because mcuboot has a goal to work
with more than just Zephyr, this may impose additional constraints on
every OS that wishes to use mcuboot.


Function pointers are also nice from a flash usage perspective, since mcuboot and the RTOS it chain-loads are likely going to have similar routines operating on this information.

 
Zephyr Details
==============
Supporting this in Zephyr involves adding a small amount of assembly
to system startup to detect if the register contains the magic value,
and if so, stashing away the pointer in the other register.  Once this
is stored, the rest of the use can be done at a library level or by an
application.

In addition, the MPU/MMU may need to be configured to allow reading
and/or execution from the area of flash containing the bootloader.

Questions
=========

 - What do people think of this proposal?

I think we need to define at least Zephyr's relationship to its bootloader, so I'm really glad about this proposal.
 

 - Preferences for: ROM+callback, ROM only, or RAM only

ROM+callback seems best (less duplication of code across RTOSes, lower resource usage on the device) if it is possible.
 

 - How formally should we define this proposal?  Is a de-facto
   implementation by mcuboot sufficient at this time?

I think this should be defined formally. Whenever there's protocol versioning, there needs to be a spec.

In general I also think that we need to start thinking about other issues related to passing control from mcuboot to Zephyr. Beyond just setting up the stack and jumping to the entry point, mcuboot on Zephyr also locks IRQs and disables the system clock. However, those aren't the only hardware resources it uses, and it isn't always "clean" about putting them back into their reset state.

For example, Zephyr mcuboot currently leaves the STM32 clock tree configured to use the PLL, it leaves devices powered on, clocked, and configured (e.g. UART), etc. The story is similar on other targets.

Leaving this vaguely defined is undesirable, especially for power management.

It's likely future work, but it seems inevitable we'll have to define exactly the state of the device that mcuboot provides to the chainloaded image. Having a place to put formal definitions will simplify this work.
 

 - Thoughts on future data that may need to be passed?

Please see above.

Thanks,
Marti
 

Thanks,
David Brown
_______________________________________________
Zephyr-devel mailing list
Zephyr-devel@...ct.org
https://lists.zephyrproject.org/mailman/listinfo/zephyr-devel

Join devel@lists.zephyrproject.org to automatically receive all group messages.