Dynamically load apps to zephyr RTOS
Minimal example of dynamic apps for Cortex-M
Doxygen docs »
This is a minimal example of having dynamic apps with syscall functionality. It is divided into 2 parts, the kernel and the app. The kernel is responsible for loading the app and handling syscall functionality. The syscall functionality is implemented using zephyr syscalls.
At the moment the kernel is nothing but zephyr and 2 functions, the SetLed
and LoadApp
.
The syscall functionality is implemented in the modules/sys_module while the app loading is implemented in the
modules/app_loader.
The kernel contains the actual implementation of SetLed
, This code does not get compiled into the app again. So this API
can be changed without needing to rebuild the app.
There are some changes required in zephyr to support this. Have a look at [zephyr changes](#Changes required in zephyr) and a diff here
The app is compiled as a Position independent Code.
The following flags are used:-fpic -msingle-pic-base -mpic-register=r9 -mno-pic-data-is-text-relative -mlong-calls
.
This creates an executable which can be run from any memory location given that the GOT address is in the register R9.
This value is given as a parameter while calling the app, so this gets placed in R0.
The AppStart
function defined in app_startup.s is used to copy the value from R0 to R9. This function contains the
first instruction that gets executed when the app is started. Followed by the main
function defined in app. This sequence is
defined using the app linker script app_base.ld.
gcc generates the executable in elf which contains a lot of information and is quite large for a mcu. So this elf file is
converted into TINF, which can be easily loaded onto a mcu.
The current example app blinky in apps/blinky turns on the green LED while the kernel turns on the red LED on the STM32F429i-DISC1 board. But should be able to port to any arm cortex m board.
To learn more about GOT and PIC refer the Acknowledgements, they do a much better job of explaining the concepts.
Mainly to learn about the GOT, PIC, memory layout of mcu, elf format, and a lot more. Gained a lot of knowledge from this project.
This example is for the STM32F429i DISC1 board. But should be portable to any other mcu.
cd kernel
mkdir build
cd build
cmake .. -DBOARD=stm32f429i_disc1 -DUSERLIB=1
make userlib
A folder called userlib will be created. This folder will contain everything required to build the app.
It contains the header and archive files.
Copy this folder to the apps folder.
cp -R userlib ../../apps/
This app turns on LED1. There is some extra code in the example to purposely populate the data and bss sections
And also to verify if the GOT is copied properly. ie global variable access.
cd apps/blinky
mkdir build/
cd build
cmake ..
make
This will create blinky.elf file. This file now needs to be converted into TINF.
python3 ../../../elf2tinf/elf2tinf.py --major 1 --minor 0 blinky.elf blinky
This will generate 2 files, the blinky.tinf and blinky_tinf.h
blink.tinf is in a binary format which can be loaded over uart, ble, usb, etc.
blinky_tinf.h is the same binary data in the form of a header file, so the app can be tested easily, by
compiling into the kernel, without implementing the actual transfer of the binary to the mcu.
More details about this tool is in the README.md in folder elf2tinf
Copy the blinky_tinf file to the kernel include folder.
cp -rf blinky_tinf.h ../../../kernel/include
cd kernel/build
Build the kernel, this is the code that will actually load the app and run it
cmake .. -DBOARD=stm32f429i_disc1 -DUSERLIB=0
make clean
Need to clean the build files of the userlib.
make
This will generate the main.elf file which needs to be loaded onto the board.
This can vary depending on your method. but if zephyr flash method works for you then simply
make flash
User documentation is in header files
To go deeper see the source files
Online docs: https://rgujju.github.io/Dynamic_App_Loading/html/index.html
-DBOARD
param given to cmake to match your boardHave a look at the diff here
Allocate space in the userspace thread stack for GOT, bss and data section of dynamic app. Right now I am just not resetting the thread stack to 0 or 0xaaaaaaaa while entering userspace so that it does not overwrite these sections.
The thread stack is statically allocated due to limiations in zephyr heap allocator. The current heap implementation cannot allocate stack which is aligned properly according to MPU
Do not compile syscall APIs as static inline while building userlib, and set CONFIG_USERLIB=1
and __ZEPHYR_USER__=1
CONFIG_USERLIB=1
is used to remove the static inline
from the syscall declaration so that the API is exported.__ZEPHYR_USER__=1
is used to compile only the userspace version of the syscallextern int8_t z_impl_some_api(uint8_t a, uint8_t b);
#ifndef CONFIG_USERLIB
static inline
#endif
int8_t some_api(uint8_t a, uint8_t b)
{
#ifdef CONFIG_USERSPACE
if (z_syscall_trap()) {
return (int8_t) arch_syscall_invoke2(*(uintptr_t *)&Led_Num, *(uintptr_t *)&Led_State, K_SYSCALL_SETLED);
}
#endif
compiler_barrier();
return z_impl_SetLed(Led_Num, Led_State);
}
#ifndef CONFIG_USERLIB .. #endif
module_name.c
#include <zephyr.h>
#include <device.h>
#include "<module_name>.h"
#ifndef CONFIG_USERLIB
int8_t z_impl_some_api(uint8_t a, uint8_t b){
...
}
int8_t z_vrfy_some_api(uint8_t a, uint8_t b){
}
#include <syscalls/some_api_mrsh.c>
#endif /*CONFIG_USERLIB*/
module_name.h
#ifndef CONFIG_USERLIB
_syscall int8_t some_api(uint8_t a, uint8_t b);
#endif
# The source for jumping to the syscall will
get included from here
#include <syscalls/module_name.h>
#include <syscalls/file_name.h>
along with dependencies to include the APIs from that file.Following resources helped me: