Building a two-part firmware image using GCC toolchain

依然范特西╮ 提交于 2019-12-07 03:08:00

问题


I have some firmware built with GCC that runs on an ARM Cortex M0 based microcontroller. The build currently generates a single binary image that can be written into the program memory of the microcontroller.

For reasons to do with field update, I need to split this image into two parts that can be updated separately. I'll call these Core and App.

  • Core: contains the interrupt vector table, main() routine, and various drivers and library routines. It will be located in the first half of the program memory.

  • App: contains application-specific code. It will be located in the second half of the program memory. It will have a single entry point, at a known address, which is called by the core to start the application. It will access functions and data in the core via known addresses.

There are some obvious limitations here, which I'm well aware of:

  • When building the app, the addresses of symbols in the core will need to be known. So the core must be built first, and must be available when linking the app.

  • An app image will only be compatible with the specific core image it was built against.

  • It will be possible to update the app without updating the core, but not vice versa.

All of that is OK.

My question is simply, how can I build these images using GCC and the GNU binutils?

Essentially I want to build the core like a normal firmware image, and then build the app image, with the app treating the core like a library. But neither shared linking (which would require a dynamic linking mechanism) or static linking (which would copy the core functions used into the app binary) are applicable here. What I'm trying to do is actually a lot simpler: link against an existing binary using its known, fixed addresses. It's just not clear to me how to do so with the tools.


回答1:


We have this working now so I am going to answer my own question. Here is what was necessary to do this, starting from a normal single image build, turning that into the "core" and then setting up the build for the "app".

  1. Decide how to split up both the flash and the RAM into separate areas for the core and the app. Define the start address and size of each area.

  2. Create a linker script for the core. This will be the same as the standard linker script for the platform except that it must only use the areas reserved for the core. This can be done by changing the ORIGIN and LENGTH of the flash & RAM entries in the MEMORY section of the linker script.

  3. Create a header file declaring the entry point for the app. This just needs a prototype e.g.:

void app_init(void);.

  1. Include this header from the core C code and have the core call app_init() to start the app.

  2. Create a symbol file declaring the address of the entry point, which will be the start address of the flash area for the app. I'll call this app.sym. It can just be one line in the following format:

app_init = 0x00010000;

  1. Build the core, using the core linker script and adding --just-symbols=app.sym to the linker parameters to give the address of app_init. Retain the ELF file from the build, which I'll call core.elf.

  2. Create a linker script for the app. This will again be based on the standard linker script for the platform, but with the flash & RAM memory ranges changed to those reserved for the app. Additionally, it will need a special section to ensure that app_init is placed at the start of the app flash area, before the rest of the code in the .text section:

SECTIONS
{
    .text :
    {
        KEEP(*(.app_init))
        *(.text*)
  1. Write the app_init function. This will need to be in assembly, as it must do some low level work before any C code in the app can be called. It will need to be marked with .section .app_init so that the linker puts it in the correct place at the start of the app flash area. The app_init function needs to:

    1. Populate variables in the app's .data section with initial values from flash.
    2. Set variables in the app's .bss section to zero.
    3. Call the C entry point for the app, which I'll call app_start().
  2. Write the app_start() function that starts the app.

  3. Build the app, using the app linker script. This link step should be passed the object files containing app_init, app_start, and any code called by app_start that is not already in the core. The linker parameter --just-symbols=core.elf should be passed to link functions in the core by their addresses. Additionally, -nostartfiles should be passed to leave out the normal C runtime startup code.

It took a while to figure all this out but it is now working nicely.




回答2:


First of all... if this is just for field updating, you don't need to rely on the interrupt vector table in the core space for the app. I think ARM M0 parts always have the ability to move it. I know it can be done on some (all?) the STM32Fx stuff, but I believe this is an ARM M-x thing, not an ST thing. Look into this before committing yourself to the decision to make your application ISRs all be hooks called from the core.

If you plan on having a lot of interaction with your core (btw, I always call the piece that does self-updating a "bootloader" on MCUs), here's an alternate suggestion:

Have the Core pass a pointer to a struct / table of functions that describes its capabilities into the App entry point?

This would allow complete separation of the code for the app vs core except for a shared header (assuming your ABI doesn't change) and prevent name collisions.

It also provides a reasonable way to prevent GCC from optimizing away any functions that you might call only from the App without messing up your optimization settings or screwing around with pragmas.

core.h:

struct core_functions
{
    int (*pcore_func1)(int a, int b);
    void (*pcore_func2)(void);
};

core.c:

int core_func1(int a, int b){ return a + b; }
void core_func2(void){ // do something here }

static const struct core_functions cfuncs= 
{
    core_func1,
    core_func2
};

void core_main()
{
   // do setup here
   void (app_entry*)(const struct core_functions *) = ENTRY_POINT;
   app_entry( &cfuncs );
}

app.c

void app_main(const struct core_functions * core)
{
   int res;
   res = core->pcore_func1(20, 30);
}

The downside / cost is a slight runtime & memory overhead and more code.



来源:https://stackoverflow.com/questions/35183874/building-a-two-part-firmware-image-using-gcc-toolchain

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!