User Tools

Site Tools


manual:firmware_info_check

Firmware Info Check

Wouldn't it be nice if the bootloader can check if the firmware file, selected for the firmware update, is actually the correct one for the hardware? Why? Well, here's a potential scenario:

Imagine if you have multiple products on which you run the OpenBLT bootloader. In this case it can happen that the person initiating the firmware update, selected the wrong firmware file. As a result, the firmware for product A got programmed onto product B. This could have disastrous consequences. For example, if GPIOD-pin1 on product A is connected to an LED and on product B connected to a hydrogen purge valve. Instead of toggling an LED, hydrogen is purged. A bit of an extreme example, yet not impossible.

Thanks to the Firmware Info Check feature of the OpenBLT bootloader, you can implement a check in your bootloader application, and verify that the to-be-programmed firmware is actually meant for the hardware.

Firmware info table

When making use of the Firmware Info Check feature, the idea is that you add a table with firmware info details to your firmware, such that this table is always located at the same fixed memory address in non-volatile memory:

The actual contents of the firmware info table are completely up to you. In the illustration above, the firmware info table consists of the following elements:

  • Table identifier ⇒ Fixed value to identify as a firmware info table. Example: 0x9A4B8107.
  • Product identifier ⇒ Product identification, e.g. 1234 is an air pump.
  • Firmware version ⇒ Version of the firmware, e.g. 10429 is version 1.4.29.

How to use the firmware info check feature

If you would like to make use of the firmware info check feature, this section contains step-by-step instructions to help you get started. As a reference, you can look at the Olimexino-STM32F3 demo programs, as these demo programs are configured to support the firmware info check feature.

Add the info table to your firmware

Since the firmware info check feature hinges on the presence of an info table in your firmware, the first step is adding such an info table to your firmware. Note that you are free to choose whatever information you would like to include in the table. Just make sure to declare the table as const such that the linker places it in non-volatile memory:

struct firmwareInfoTable
{
  unsigned long tableId;         /**< fixed value for identification as an info table. */
  unsigned long productId;       /**< product identification. E.g. 1234 = Airpump.     */
  unsigned long firmwareVersion; /**< firmware version. E.g. 10429 = v1.4.29           */
};
 
const struct firmwareInfoTable firmwareInfoTable =
{
  .tableId = 0x9A4B8107UL,
  .productId = 1234UL,
  .firmwareVersion = 10429UL
};

Place the info table at a fixed address

After adding the info table, we now need to make sure it is always present at the same fixed memory address. My personal preference is to place it after the vector table. Another option would be to place it at the end of flash memory.

How to actually place the info table at a specific location, is compiler dependent. The following example assumes that you are using the GNU ARM Embedded toolchain. This is the one used by STM32CubeIDE. Note that you can find IAR and Keil examples in the Olimexino-STM32F3 OpenBLT demo programs.

Start by adding the section attribute to the info table constant declaration. Change:

const struct firmwareInfoTable firmwareInfoTable =

To:

const struct firmwareInfoTable firmwareInfoTable __attribute__ ((section (".infoTable"))) =

Next, open up your firmware project's linker script. This is the file that typically has the .ld file extension. By default the start address of the firmware was moved forward already by 40kb (0xA000) to make space for the bootloader.

I added a VECTAB memory definition of 512 bytes for the vector table. Next, I added a INFO_TABLE memory definition of 12 bytes for the actual info table. Then the start of the FLASH memory definition was moved forward to make space for (a) the bootloader, (b) the vector table and © the info table:

/* Memories definition */
MEMORY
{
  CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 8K
  RAM       (xrw)    : ORIGIN = 0x20000000,   LENGTH = 40K
  VECTAB     (rx)    : ORIGIN = 0x0800A000,   LENGTH = 512
  INFO_TABLE (rx)    : ORIGIN = 0x0800A200,   LENGTH = 12
  FLASH      (rx)    : ORIGIN = 0x0800A20C,   LENGTH = 256K - 40K - 512 - 12
}

With the memory definitions updated, the next step is to update the sections such that the vector table (.isr_vector) and info table (.infoTable) are linked to the correct memory regions. In a nutshell, the .isr_vector and .infoTable sections were newly added:

/* Sections */
SECTIONS
{
  /* The vector table goes into "VECTAB". */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Vector table */
    . = ALIGN(4);
  } >VECTAB
 
  /* The firmware info table goes into INFO_TABLE. */
  .infoTable :
  {
    . = ALIGN(4);
    KEEP(*(.infoTable)) /* Info table */
    . = ALIGN(4);
  } >INFO_TABLE
 
  /* Remainder of the sections was left unchanged... */
}   

If you now rebuild your firmware and inspect the MAP-file afterwards, you'll see that the linker placed the 12-byte long info table starting at address 0x0800A200:

Enable and configure the firmware info check feature

With the firmware info table added to a fixed location into your firmware, it's now time to switch to your bootloader project. As a first step, enable and configure the firmware info check feature. Open up your “blt_conf.h” bootloader configuration header-file and add the following three macros:

  • BOOT_INFO_TABLE_ENABLE ⇒ Set to 1 to enable the firmware info check feature.
  • BOOT_INFO_TABLE_LEN ⇒ Set to the length in bytes of the info table you added to your firmware.
  • BOOT_INFO_TABLE_ADDR ⇒ Set to the base address where the info table is located in your firmware.

Example:

Implement the info table check hook-function

After adding the info table to a fixed location in your firmware and enabling the info table check feature in the bootloader, the last step is to implement the hook-function that actually checks the info table. Add the function InfoTableCheckHook() somewhere in your bootloader. I typically place all hook-functions in the “hooks.c” source-file.

This function is automatically called during the firmware updates, when it is time to check the info table. The function receives two memory addresses as parameters:

  • newInfoTable ⇒ Address of the opaque pointer to the info table that was extracted from the firmware file that was selected for the firmware update.
  • currentInfoTable ⇒ Address of the opaque pointer to the info table of the currently programmed firmware.

With the return value, you can inform the bootloader if the info table check passed or not. If not passed, then the firmware update will be aborted before any changes were made to the non-volatile memory.

Example implementation of the InfoTableCheckHook() function:

/************************************************************************************//**
** \brief     Callback that gets called at the start of the firmware update, before
**            performing erase and program operations on non-volatile flash. It enables
**            you to implement info table comparison logic to determine if the firmware
**            update is allowed to proceed. Could for example be used to make sure a
**            firmware update only goes through if the selected firmware file contains
**            firmware for the correct product type.
** \param     newInfoTable Address of the opaque pointer to the info table that was
**            extracted from the firmware file that was selected for the firmware update.
** \param     oldInfoTable Address of the opaque pointer to the info table of the
**            currently programmed firmware.
** \return    BLT_TRUE if the info table check passed and the firmware update is allowed
**            to proceed. BLT_FALSE if the firmware update is not allowed to proceed.
**
****************************************************************************************/
blt_bool InfoTableCheckHook(blt_addr newInfoTable,  blt_addr currentInfoTable)
{
  blt_bool result = BLT_FALSE;
  /* Important: This structure must have the same layout as the actual info table in the
   * firmware itself.
   */
  struct firmwareInfoTable
  {
    blt_int32u tableId;          /**< fixed value for identification as an info table. */
    blt_int32u productId;        /**< product identification. E.g. 1234 = Airpump.     */
    blt_int32u firmwareVersion;  /**< firmware version. E.g. 10429 = v1.4.29           */
  };
 
  /* Cast addresses of opaque pointers to info table pointers. */
  struct firmwareInfoTable const * newInfoTablePtr     = (void const *)newInfoTable;
  struct firmwareInfoTable const * currentInfoTablePtr = (void const *)currentInfoTable;
 
  /* Sanity check on the configured length of the info table at compile time. */
  ASSERT_CT(BOOT_INFO_TABLE_LEN == sizeof(struct firmwareInfoTable));
 
  /* Do table IDs match? The table ID identifies the tables as firmware info tables. */
  if (currentInfoTablePtr->tableId == newInfoTablePtr->tableId)
  {
    /* Only allow the firmware update to proceed if it's firmware for the same
     * product type.
     */
    if (currentInfoTablePtr->productId == newInfoTablePtr->productId)
    {
      /* Allow the firmware update to proceed. */
      result = BLT_TRUE;
    }
  }
 
  /* Give the result back to the caller. */
  return result;
}

Firmware update flow chart

To provide a better understanding of how the info table check feature works during a firmware update, refer to the following flowchart:

At the start of the firmware update, a check is performed to see if the firmware info table check feature is actually enabled. For firmware updates from a firmware file that is stored on a locally attached file system (e.g. SD-card), macro BOOT_INFO_TABLE_ENABLE is evaluated for this. For remote firmware updates, a custom XCP command is sent to request the info table's start address and length. A positive response to this XCP command indicates that the firmware info table check feature is enabled, a command unknown negative response indicates that it is disabled and/or not supported.

With the firmware info table check feature enabled, the next step is to determine the info table start address and length. For remote firmware updates, the previously mentioned custom XCP command already obtained this information. For firmware updates from a locally attached file system, this information is obtained from the BOOT_INFO_TABLE_ADDR and BOOT_INFO_TABLE_LEN macros.

Once it's known where the info table is located and how large it is, the info table contents are extracted from the firmware file that was selected for the firmware update. For remote firmware updates, the info table contents are sent to the bootloader with the help of another custom XCP command. Upon reception, the bootloader stores it in a dedicated RAM buffer. For firmware updates from a locally attached file system, the bootloader directly copies the info table contents into this RAM buffer, while extracting it from the firmware file.

With the info table contents present in the dedicated RAM buffer, the bootloader calls the InfoTableCheckHook() that you implemented in your bootloader application. It passes the pointer to the dedicated RAM buffer, which holds the info table of the to-be-programmed firmware, and a pointer to the info table of the currently programmed firmware. This allows you to compare the two info tables and decide if it's okay for the firmware update to continue, based on the return value you give from this hook-function.

Note that the bootloader only calls InfoTableCheckHook(), if firmware is currently actually programmed. It uses the internal checksum mechanism to make this determination. If no firmware is currently programmed, then the bootloader always allows the firmware update to proceed.

Custom XCP commands

As mentioned in the previous section, custom XCP commands were designed and implemented to support the firmware info table check feature, during remote firmware updates. The following three commands were added:

  • GET_INFO ⇒ To discover the memory address and size of the info table.
  • DOWNLOAD ⇒ To send the info table, extracted from the firmware file, to the bootloader.
  • CHECK ⇒ To request the bootloader to perform the actual info table check and send the results back in the response.

Command layout

  • BYTE1: Command Code = 0xF1 is for USER_CMD
  • BYTE2: Sub Command Code for info table = 0x17
  • BYTE3: Info Table Command ID
    • IT_CID_GET_INFO = 0x04
    • IT_CID_DOWNLOAD = 0x06
    • IT_CID_CHECK = 0x08

Command details

IT_CID_GET_INFO

Obtains information about the memory location and length of the info table in the user program firmware.

Position Type Description
0 BYTE Command Code = 0xF1
1 BYTE Sub Command Code = 0x17
2 BYTE Info Table Command ID = 0x04

Positive Response

Position Type Description
0 BYTE Packet ID = 0xFF
1 BYTE Info Table Command ID = 0x04
2,3 WORD Info Table Length [BYTE]
4..7 DWORD Info Table Base Address

After successful reception of this command, the bootloader clear the contents of the info table RAM buffer and sets its write pointer to the start of the RAM buffer.

Note that this command does not make use of the address extension, which is typically added whenever addresses are exchanged. The address extension is usually not used and if at all used, it is to select the memory device that the address belong to. The bootloader knows in which memory the info table is stored and therefore no address extension is needed.

IT_CID_DOWNLOAD

Once the memory location and length of the info table are known, it can be extracted from the firmware file that was selected for the firmware update. With this info table command ID the extracted table is downloaded to a RAM buffer in the bootloader.

Position Type Description
0 BYTE Command Code = 0xF1
1 BYTE Sub Command Code = 0x17
2 BYTE Info Table Command ID = 0x06
3 BYTE Length of info table bytes to download
4..MAX_CTO-1 BYTE Info table content

If the total length of the info table is bigger than MAX_CTO-4, the master has to send the info table bytes with consecutive IT_CID_DOWNLOAD command(s). Similar to how the XCP DOWNLOAD command works.

After successful reception of this command, the bootloader attempts to store the new data at the current write pointer of the internal RAM buffer and automatically increments the write pointer. If this data does not fit in the info table RAM buffer anymore, the ERR_OUT_OF_RANGE negative response is issued.

Positive Response

Position Type Description
0 BYTE Packet ID = 0xFF
1 BYTE Info Table Command ID = 0x06

IT_CID_CHECK

After downloading the info table of the firmware that is selected for the firmware update, the bootloader can access it in the RAM buffer. The bootloader can also access the info table of the currently programmed firmware in non-volatile memory. When the bootloader receives this info table command ID, it invokes a hook-function for comparing the two info tables, allowing to perform a final check to decide if it allows this firmware update to proceed.

Position Type Description
0 BYTE Command Code = 0xF1
1 BYTE Sub Command Code = 0x17
2 BYTE Info Table Command ID = 0x08

Positive Response

Position Type Description
0 BYTE Packet ID = 0xFF
1 BYTE Info Table Command ID = 0x08
2 BYTE Okay (1) or Abort (any other value)

After successful reception of this command, the bootloader first checks if the info table in RAM was fully written to. If not the ERR_SEQUENCE negative response is issued.

Command timeout

All commands use the T1 timeout.

Negative responses

In case the info table feature is disabled or an invalid info table command ID is received:

Position Type Description
0 BYTE Packet ID = 0xFE
1 BYTE ERR_CMD_UNKNOWN = 0x20

In case more info table data than expected is downloaded:

Position Type Description
0 BYTE Packet ID = 0xFE
1 BYTE ERR_OUT_OF_RANGE = 0x22

In case the check command is received but not yet the full contents of the info table was received:

Position Type Description
0 BYTE Packet ID = 0xFE
1 BYTE ERR_SEQUENCE = 0x29

Known Limitations

The firmware info table check feature is available starting with OpenBLT stable release 1.20. If you read this before the release of version 1.20, it is already present in the development trunk. Refer to the download page for details on where to download stable releases and how to access the development trunk.

Note that the firmware info table check feature is not a security feature. It's a convenience feature. In fact, it is relatively easy to bypass the firmware info table check feature during a remote firmware update. The already existing seed/key security feature can be used to better control who is authorized to perform firmware updates.

Some users perform a firmware update in two steps with two firmware files. One firmware file with the actual firmware code and another firmware file with calibration and/or configuration parameters. This now doesn't work anymore, because one of these files probably doesn't have the info table included, resulting in the bootloader aborting the firmware update for the firmware file that doesn't have the info table.

A fairly simple workaround is to make sure the info table is always included in each firmware file. For example, by placing the firmware info table in the same area as the calibration / configuration parameters. The first firmware file that contains the actual firmware, should then also include the firmware info table and default values for the calibration / configuration parameters. When programming the second firmware file, the one with the up-to-date calibration / configuration parameters, this one now also includes the info table, meaning that all works as expected.

manual/firmware_info_check.txt · Last modified: 2025/05/13 15:09 by voorburg