CMRX expects that the following functions are provided by architecture support layer. If creating new layer, these functions have to be implemented.
Similarly, some of kernel syscalls are directly implemented by the architecture support layer as there is no way for kernel to know how platform will implement given mechanisms. This covers mostly the RPC call / return syscalls and signal delivery.
To create a port of CMRX to the new architecture a few steps are needed. This guide roughly describes them in general terms and outlines items that have to be provided which are not immediately obvious from the portin layer API.
In the following text, several terms will be used:
Architecture - refers to the CPU family which determines most of target CPU functionality. In case of CMRX, ports are mainly done to support certain CPU architecture rather than specific platform (see next). Examples of architectures are: ARM (Cortex-M), RISC-V (RV32/64-E).
Platform - refers to specific subfamily of CPU. This subfamily may further determine presence of absence of certain CPU features. CMRX mostly don’t care about presence of features it does not directly support or require. Features, that are required by CMRX (especially memory protection) must be present, otherwise it is not possible to port CMRX to such architecture reliably. In case of ARM, such platforms might be Cortex-M0+, Cortex-M4 or Cortex-M4F.
Port - refers to specific vendor implementation of the platform. In case of CMRX, specifically for the ARM architecture, all ports are covered by generic port named “CMSIS”. This ports expects that your vendor’s SDK provides CMSIS-compatible headers. The CMRX build system supports creation of ports, but as long as there is technical solution available that does not require port creation, it should be avoided. In CMRX terminology port refers to the same thing as term “HAL” does.
uint32_t * os_thread_populate_stack(int stack_id, unsigned stack_size, entrypoint_t *entrypoint, void *data)Populates stack of new thread so that it can be executed with no other actions required. Returns the address where SP shall point to.
stack_id ID of stack to be populated
stack_size size of stack in 32-bit quantities
entrypoint address of thread entrypoint function
data address of data passed to the thread as its 1st argument Address to which the SP shall be set.
Returns Address to which the SP shall be set.
int os_process_create(Process_t process_id, const struct OS_process_definition_t *definition)Takes process definition and initializes MPU regions for process out of it.
process_id ID of process to be initialized
definition process definition. This is constructed at compile time using OS_APPLICATION macros E_OK if process was contructed properly, E_INVALID if process ID is already used or if process definition contains invalid section boundaries. E_OUT_OF_RANGE is returned if process ID requested is out of limits given by the size of process table.
Returns E_OK if process was contructed properly, E_INVALID if process ID is already used or if process definition contains invalid section boundaries. E_OUT_OF_RANGE is returned if process ID requested is out of limits given by the size of process table.
void os_boot_thread(Thread_t boot_thread)Used to actually start executing in thread mode just after the kernel has been initialized and is ready to start the first thread. This function has to perform CPU switch from privileged mode in which kernel runs into unprivileged mode in which threads are supposed to run. Thread passed to this function is in state ready to be executed by normal kernel thread switching mechanism on this platform.
boot_thread ID of thread that shall be started
void os_kernel_shutdown()Return to bare metal execution mode similar to one after CPU reset. This function should configure the CPU to continue execution in privileged mode not distinguishing between thread and kernel space. Once this mode is configured the function cmrx_shutdown_handler()
This is a point of no return. Code here is free to destroy any previous context.
Stop running the kernel.
This is platform-specific way of how to shutdown the kernel. In this case an interrupt frame is forged on stack that will resemble a frame returning back to the cmrx_shutdown_handler
void os_reset_cpu()This is architecture- (and possibly HAL-) specific way to reset the CPU. This function can be used before the kernel has been started or after it has been shut down. If there is no shutdown handler provided by the integrator then the default handler will call this function to reset the CPU automatically.
int os_set_syscall_return_value(Thread_t thread_id, int32_t retval)void os_ipi_sync_request()This is minimalistic implementation for inter-processor synchronization request for Cortex-M processors. This implementation does not assume presence of any inter-processor interrupt mechanism. It assumes that each core checks for inter-processor sync every now and then. This function will block until all online cores call os_ipi_sync_probe()
void os_ipi_sync_release()This function will release all cores which are waiting in os_ipi_sync_probe()os_ipi_sync_request()
void os_ipi_sync_probe()Each core should call this function from time to time. If any other core calls for inter-processor synchronization, then core calling this function will be blocked in the call until the core which requested the synchronization doesn’t release it.
void os_request_context_switch(bool activate)This function is called by the platform independent part of the kernel when cpu_context structure is filled with valid data and context switch shall happen. The implementation of this function should configure the hardware in a way that context switch will happen as soon as kernel finishes its work and is ready to return the CPU back to the userspace code.
unsigned coreid()Kernel calls this function if it is configured for SMP. This function is part of the porting layer but does not need to be implemented in port. If the architecture does not provide unified way of determining which core is the one currently executing this code, then the implementation of this method may be left to the user. ID of the currently running code
void os_smp_lock()This function starts the critical section within kernel. Actions that happen within critical section can ever only happen on one CPU core and other cores must not motify the same data. Big kernel lock is used to lock thread table, stack allocation table and sleepers table.
This function is part of porting layer but depends heavily on target CPU. It may be left to be implemented by the user. If this function is called, the code should make sure that if this function is called on any other core before os_smp_unlock()
void os_smp_unlock()This function ends the critical setion within kernel.
This function is part of the porting layer but depends heavily on target CPU. It may be left the be implemented by the user. Once this function is called the code must make sure that any other core potentially being blocked by parallel call to os_smp_lock()
void os_core_lock()It usually boils down to disabling interrupts. Calling this function does NOTos_smp_lock()
void os_core_unlock()Usually it boils down to enabling interrupts.
void os_core_sleep()Park current core This function is free to park current core in whatever way that will still allow the core to be woken up by external interrupts.
void os_memory_protection_start()int mpu_init_stack(int thread_id)Performs initialization of the MPU to enable the given thread to use the stack.
thread_id Thread stack has to be initialized for
int mpu_restore(const MPU_State *hosted_state, const MPU_State *parent_state)Loads MPU settings for default amount of regions from off-CPU buffer. This is suitable for store-resume during task switching.
hosted_state MPU state buffer for the current host process
parent_state MPU state buffer for the parent process
void os_memory_protection_stop()Disables memory protection unit so that no rules are enforced by the hardware. The CPU state after this call should resemble MPU state after reset.
int os_rpc_call(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)int os_rpc_return(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)This syscall has to return the control back to the code which called rpc_call
void os_thread_initialize_arch(struct OS_thread_t *thread)This routine is called when thread has been created. It has opportunity to perform architecture-specific default-initialization of the thread. To save some CPU cycles, you can define this function as an empty macro if it is not used. In that case the Arch_State_t
void os_init_arch(void)This can be used to perform system-wide initialization that has to be done once per whole system. Kernel guarantees that this function will be called exactly once
void os_init_core(unsigned core_id)This can be used to perform architecture specific initialization of CPU core before kernel starts executing on the core. Note that you shall not use this function to initialize memory protection unless some very specific steps have to be taken as there is API to initialize MPU elsewhere in the porting layer. This function will be called once per core managed by the kernel before kernel starts initialization of the kernel structures for that core.
unsigned static_init_thread_count()amount of threads that have to be statically initialized
const struct * static_init_thread_table()address of table containing details of statically initialized threads
unsigned static_init_process_count()amount of processes that have to be statically initialized
const struct * static_init_process_table()address of table containins details of statically initialized processes