A Strategy for Reporting Version Information from Bootloaders

It’s not uncommon in the embedded world to have multiple firmware images that are used as part of the boot process. Most commonly, you will need a bootloader that checks for a valid firmware image and loads it (or performs a fallback strategy in the event of a problem). Depending on the process we might need an earlier bootloader, one that initializes core components of the system (such as initializing external RAM in a processor with limited internal memory) and loads the second-stage bootloader.

One challenge with this approach is that the software versions of the different boot stages are not likely available to us when we are in the final firmware image. We might want to know the version of these bootloaders for debugging purposes, or because our application needs to check whether a bootloader update it is necessary.

Here’s the outline of a strategy we’ve used in the past to access versions in our software. For this example, we will consider the case of a two-stage bootloader and an application.

  • Define .noinit sections of memory, representing a small area in our SRAM storage that will not be initialized on boot. This is necessary because we will be writing data to this area and reading it in succeeding application stages.
    • For example, we might create a .noinit region of memory at the end of the processor’s SRAM section. This will be needed in the second-stage bootloader. If the second-stage performs the same strategy as the first stage, it will also be needed in the application.
  • The first stage bootloader will write to the .noinit section of memory:
    • A 32-bit codeword (e.g., 0xdeadbeef, 0xcafebabe), which will be used to identify whether valid version information was written
    • A version number, or version string with a terminator, or a 8-bit length followed by a version string (storage is dependent upon your system’s versioning scheme)
  • The first stage bootloader will load the second stage bootloader
    • If the second-stage bootloader has sufficient smarts, it can check the first-stage bootloader’s codeword to determine whether a valid version is included. If so, it can compare the reported version against a non-volatile storage mechanism, updating the stored version if it doesn’t match. The second-stage bootloader’s version can be stored in the same way. The application will be able to read the versions out of the non-volatile memory.
    • If the second-stage bootloader does not have sufficient smarts, then the same strategy applies as the first-stage bootloader. The second-stage bootloader will go beyond the value of the first-stage bootloader and write:
      • A 32-bit codeword
      • A version number, or version string with a terminator, or a 8-bit length followed by a version string (storage is dependent upon your system’s versioning scheme)
  • The second stage bootloader will boot the application:
    • If the versions are stored in non-volatile memory, no further action is required. The versions can be read from memory as needed.
    • If the versions are stored in SRAM, read the values out of .noinit memory (checking the codewords for both bootloader versions to confirm validity) and copy the versions into an internal version storage structure. That RAM can now be reclaimed for other purposes (stack, heap).

Considerations:

  • Only have one bootloader stage, followed by an application? Strip out the logic for the second stage!
  • You can always capture the versions by dumping the specified region of memory if something goes wrong
  • You can change the codeword to indicate a change in storage format, allowing the version storage & parsing to change across without trouble
  • You will want to set a maximum length for version strings, which means that some version information may not be available to you in the application due to truncation.

References

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.