|
StateOS
|
The kernel does not depend on the hardware: everything that depends on the processor, the compiler or the device is in the port layer (port directory). This page describes what a port must provide; the existing ports are the best reference:
| port | description |
|---|---|
| port/cortexm | ARM Cortex-M (CMSIS), compilers armcc, clang, csmcc, gnucc, iarcc, devices STM32 |
| port/stm8 | STM8 (8-bit), compilers csmc, iarc, sdcc |
| port/x86 | PC (Linux, Windows): the system runs in a process of the host, for testing purposes |
A port consists of the files common to the processor and compiler (compiler) and the files specific to the device (device):
| file | contents |
|---|---|
| port/<arch>/compiler/<cc>/osdefs.h | definitions of the compiler (see Compiler definitions) |
| port/<arch>/compiler/<cc>/oscore.h | types, context of the task, critical section, inline functions of the core |
| port/<arch>/compiler/<cc>/oscore.c | context switch handler, core_tsk_flip (or oscore.s in assembler) |
| port/<arch>/compiler/<cc>/oslibc.c | optional: support of the C library (e.g. locks of newlib) |
| port/<arch>/device/<dev>/osport.h | frequencies and sizes of the timers, inline functions of the system timer |
| port/<arch>/device/<dev>/osport.c | port_sys_init, interrupt handlers of the system timer |
| port/<arch>/common/*.h | optional: headers common to all devices (e.g. osmpu.h) |
The build scripts select the port with the variables STATEOS_ARCH, STATEOS_COMPILER and STATEOS_DEVICE (make/<board>/makefile.<cc>, cmake/config-<board>-<cc>.cmake), add the directories to the include path and compile oscore.c, oscore.s, oslibc.c and osport.c if they exist.
The headers are included in this order: oskernel.h → oscore.h → osbase.h → osport.h → osconfig.h (configuration of the application, skipped with NOCONFIG) and osdefs.h. So osport.h can not use the types of the kernel (e.g. cnt_t), oscore.h can.
osdefs.h provides the definitions of the compiler used by the kernel. On Cortex-M most of them come from CMSIS (cmsis_compiler.h), so osdefs.h defines only __CONSTRUCTOR (if the compiler supports it); the other ports define all of them (see port/x86/compiler/gcc/osdefs.h):
| definition | required | description |
|---|---|---|
| __STATIC_INLINE | yes | static inline function |
| __NO_RETURN | yes | function that never returns |
| __COMPILER_BARRIER() | yes | compiler memory barrier (critical section) |
| __PACKED_STRUCT | yes | structure without padding (header of a message in the message queue) |
| __ALIGNED(x) | no | alignment of a variable to x bytes (task stacks with MPU, system heap); default: no alignment |
| __CONSTRUCTOR | no | function called before main: sys_init is called automatically and the macros that start an object at its definition are available (OS_TSK_START, OS_WRK_START, OS_TMR_START, OS_TMR_UNTIL, ...); without it the application calls sys_init at the beginning of main |
__MPU_USED come from the header of the device (CMSIS), the default value is 0.
The port sets the default values of the configuration; the application can change them in osconfig.h:
| definition | file | description |
|---|---|---|
| CPU_FREQUENCY | osport.h | frequency of the processor in Hz |
| OS_FREQUENCY | osport.h | frequency of the system timer in Hz (ticks per second) |
| OS_TIMER_SIZE | osport.h | bit size of the system time cnt_t: 16, 32 or 64 |
| OS_ROBIN | osport.h | frequency of the round-robin in Hz: 0 = cooperative mode; must not exceed OS_FREQUENCY |
| OS_STACK_SIZE | oscore.h | default size of the task stack in bytes |
| OS_IDLE_STACK | oscore.h | size of the idle task stack in bytes |
| OS_HEAP_SIZE | oscore.h | size of the system heap in bytes: 0 = the heap of the C library (malloc) |
| OS_MAIN_PRIO | oscore.h | priority of the main task |
| OS_LOCK_LEVEL | oscore.h | optional, specific to the port: interrupts masked by the critical section |
The port also defines HW_TIMER_SIZE (internal, it must not be set in osconfig.h): the bit size of the hardware timer in tick-less mode, or 0 in tick mode. The ports choose the tick-less mode for OS_FREQUENCY above 1000 Hz.
A port should reject the configuration that it does not support with #error, e.g. OS_ATOMICS on a core without atomic operations.
They are defined in oscore.h:
| definition | description |
|---|---|
| lck_t | state of the interrupts saved by port_get_lock |
| stk_t | unit of the task stack; its size is the alignment of the stack (e.g. uint64_t on Cortex-M) |
| ctx_t | context of the task saved on its stack by the context switch handler |
| _CTX_INIT(pc) | static initializer of ctx_t: the task starts the function pc (used for the idle task) |
| MAIN_TOP | optional: top of the stack of the main task (e.g. the symbol of the linker script); without it the kernel reserves a stack of OS_STACK_SIZE bytes for tsk_flip called in the main task |
The kernel places the context of a new task at the top of its stack and initializes it with port_ctx_init. The first context switch to the task loads this context, so the task starts core_tsk_loop (or core_tsk_exec with OS_TASK_EXIT) on its own stack.
The functions are usually __STATIC_INLINE in oscore.h or osport.h, port_sys_init and port_sys_time (when it is not inline) are in osport.c.
| function | description |
|---|---|
| port_get_lock() | return the current state of the interrupts |
| port_put_lock(lck) | restore the state of the interrupts |
| port_set_lock() | mask the interrupts that use the kernel (begin of the critical section) |
| port_clr_lock() | unmask the interrupts (end of the critical section) |
| port_isr_context() | true if called from an interrupt handler |
| port_get_sp() | current stack pointer of the task (stack checks in DEBUG mode) |
| port_ctx_init(ctx, pc) | initialize the context ctx of a new task, so that it starts the function pc |
| port_ctx_switch() | request the context switch: it is executed when the interrupts are unmasked (e.g. pending PendSV) |
| port_ctx_switchNow() | request the context switch and execute it now (called in thread mode) |
| port_ctx_reset() | restart the time slice of the round-robin (tick-less mode with OS_ROBIN), otherwise empty |
| port_sys_init() | configure the system timer, the interrupt of the context switch and the MPU; called once by sys_init |
| port_sys_wait() | wait for an interrupt in the idle task; called with the interrupts masked by port_set_lock, it must not miss an interrupt pending since then |
| port_sys_time() | tick-less mode: current system time read from the hardware timer |
| port_tmr_start(time) | tick-less mode: generate the timer interrupt when the hardware timer reaches time |
| port_tmr_stop() | tick-less mode: disable the timer interrupt set by port_tmr_start |
| port_tmr_force() | tick-less mode: generate the timer interrupt now |
| port_mpu_* | optional: memory protection (__MPU_USED), see port/cortexm/common/osmpu.h |
| function | caller |
|---|---|
| core_tsk_switch(sp) | context switch handler: it saves the context of the current task on its stack, passes the stack pointer to core_tsk_switch and loads the context from the returned stack pointer of the next task |
| core_sys_tick() | tick mode: interrupt of the system timer with frequency OS_FREQUENCY; tick-less mode with HW_TIMER_SIZE < OS_TIMER_SIZE: overflow of the hardware timer |
| core_tmr_handler() | tick-less mode: interrupt generated by port_tmr_start or port_tmr_force |
| core_ctx_switch() | tick-less mode with OS_ROBIN: interrupt of the round-robin timer with frequency OS_ROBIN |
| core_tsk_loop(), core_tsk_exec() | core_tsk_flip |
core_tsk_flip(sp) is implemented by the port (usually in assembler): it sets the stack pointer to sp and jumps to core_tsk_loop (core_tsk_exec with OS_TASK_EXIT).
In tick-less mode the hardware timer counts with frequency OS_FREQUENCY and the compare interrupt (port_tmr_start) calls core_tmr_handler.