Creating a two-component firmware image using the GCC toolchain

I have firmware with GCC that works on the ARM Cortex M0 microcontroller. Currently, the assembly generates a single binary image, which can be recorded in the memory of the microcontroller program.

For reasons related to updating fields, I need to split this image into two parts, which can be updated separately. I will name these Core and App .

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

  • Application : contains the application code. It will be located in the second half of the program memory. It will have one entry point at a known address that is called by the kernel to launch the application. It will access functions and data in the kernel through known addresses.

There are some obvious limitations that I know well about:

  • When creating an application, the addresses of the characters in the kernel must be known. Therefore, the kernel must be built first and must be available when the application is connected.

  • The application image will only be compatible with the specific kernel sample with which it was created.

  • You can update the application without updating the kernel, but not vice versa.

All this is normal.

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

In fact, I want to build the kernel as a regular firmware image, and then create an application image, while the application processes the kernel like a library. But neither general links (which require a dynamic linking mechanism), nor static links (which will copy the main functions used in a binary application) are applicable here. What I'm trying to do is actually a lot simpler: link to an existing binary using its known fixed addresses. Itโ€™s just not clear to me how to do this with tools.

+6
source share
2 answers

It works for us now, so I'm going to answer my own question. Here's what to do, starting with the usual assembly of a single image, turning it into a "core", and then setting up the assembly for the "application".

  • Decide how to separate both flash and RAM into separate areas for the kernel and application. Determine the starting address and size of each area.

  • Create a script builder for the kernel. This will be the same as the standard script builder for the platform, except that it should only use areas reserved for the kernel. This can be done by changing the ORIGIN and LENGTH values โ€‹โ€‹of the flash and RAM records in the MEMORY section of the script builder.

  • Create a header file declaring the entry point for the application. It just requires a prototype, for example:

void app_init(void); .

  1. Include this header from the main C code and call the main call to app_init() to start the application.

  2. Create a symbol file that declares the address of the entry point, which will be the starting address of the flash area for the application. I will call it app.sym . It can be only one line in the following format:

app_init = 0x00010000;

  1. Create the kernel using the main linker script and add --just-symbols=app.sym to the linker options to specify the address of app_init . Save the ELF file from the assembly, which I will name core.elf .

  2. Create a script builder for the application. This will again be based on the standard script builder for the platform, but with flash and RAM memory ranges changed to reserved for the application. In addition, this will require a special section to ensure that app_init is located at the beginning of the application area, to the rest of the code in the .text section:

  SECTIONS
 {
     .text:
     {
         KEEP (* (. App_init))
         * (. text *)
  1. Write a function app_init . This needs to be assembled, as it needs to do some low-level work before any C code in the application can be called. It must be marked with .section .app_init so that the linker places it in the right place at the beginning of the application area. The app_init function should:

    • Fill in the variables in the .data application section with the initial values โ€‹โ€‹from the flash drive.
    • Set the variables in the .bss application section to zero.
    • Call the C app_start() for the application, which I will call app_start() .
  2. Write an app_start() function that launches the application.

  3. Build the application using the app builder script. This link step must be passed to the object files containing app_init , app_start , and any code called by app_start that is not already in the kernel. The linker parameter --just-symbols=core.elf should be passed to the link functions in the kernel at their addresses. In addition, -nostartfiles must be passed in order to exclude normal C runtime code.

It took a while to figure it all out, but now it works well.

+2
source

First of all ... if it's just updating the field, you donโ€™t need to rely on the kernel interrupt vector table for the application , I think that parts of the ARM M0 always have the ability to move them. I know this can be done for some (all?) STM32Fx stuff, but I believe this is an ARM Mx thing, not an ST thing. Take a look at this before deciding to make your ISR applications all of which are hooks called from the kernel.

If you plan to interact a lot with your kernel (by the way, I always call a piece that self-updates the โ€œbootloaderโ€ on the MCU), here is an alternative suggestion:

Do you pass the Core pointer a pointer to a structure / table of functions describing its capabilities to the App entry point?

This will completely separate the code for the application and the kernel, with the exception of a common header (provided that your ABI does not change) and prevent name conflicts.

It also provides a reasonable way to prevent GCC from optimizing any functions that you could only call from within the application without breaking the optimization settings or freezing 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 disadvantage / cost is a small part of the execution time and the amount of memory and more code.

0
source

All Articles