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.
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".
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.
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.
Create a header file declaring the entry point for the app. This just needs a prototype e.g.:
void app_init(void);
.
Include this header from the core C code and have the core call app_init()
to start the app.
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;
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
.
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*)
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:
.data
section with initial values from flash..bss
section to zero.app_start()
.Write the app_start()
function that starts the app.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With