Architecture

bern_rtos_architecture
Figure: Bern RTOS architecture draft; red: high priority components, blue: low priority components, green: not part of the Bern RTOS

The architecture of the Bern RTOS in the figure has been drafted based on the requirements above and Zephyrs architecture. The right side shows the intended separation of privileges: Components in kernel mode have all privileges and have access to every resource, while components in thread mode will run with restricted permissions.

Hardware Abstraction

The most basic form of hardware access on a microcontroller are memory mapped registers. In Rust the community develops peripheral access crates (PAC) to manage registers. There are two PACs per microcontroller, one for the Core registers and peripherals (e.g. on ARM Cortex-M4F: registers, SysTick timer, Instrumentation Trace Macrocell, MPU) and one for the MCU peripherals (e.g. SPI, UART, timers).

Based on the MCU peripherals is the hardware abstraction layer. Here the memory mapped registers are embedded into functions of a common interface. This layer hides any memory addresses from the user and thus abstracts the hardware. The Bern RTOS will not implement the HAL, the user will have to provide it. But the system design should allow to either use the embedded-hal or a custom HAL. Allowing for a custom HAL is necessary because a company, that uses one microcontroller for lots of products, might want to implement a more feature rich but less generic HAL.

The intent for low level drivers is a set of support functions that are not yet device drivers, e.g. transmitting a block of memory over a bus. These functions are built on a generic HAL interface and should make device drivers more efficient.

Kernel

The kernel of the Bern RTOS consists of a set of modules. Thereby we can start with a minimal prototype and add features gradually.

Architecture Interface

The architecture interface defines the functions that have to ported in order for the Bern RTOS to run on another architecture.

Scheduler

Tasks will be scheduled priority based preemptively. For tasks with the same priority the user will be able to choose whether to enable time-slicing as this features adds overhead. In Zephyr, for example, tasks with priority greater than or equal to zero are time-sliced, tasks with negative priority are scheduled cooperatively.

IPC

As any OS, mutex, semaphores and message queues are planned for the Bern RTOS. For the mutex the aim is to include a resource within a mutex in the same way the Rust standard library does. For message passing we need to find a solution to efficiently pass data of different sizes. μC/OS-III uses memory blocks to pass large data from one task to another without copying. On systems with dynamic memory this might be a solution.

Log

Logging is an integral part of any embedded system. Any module will be able to log events. For efficient debugging the log module needs to provide filtering by event severity and origin. Logs should also support multiple back-ends, because during development a serial port suffices but the end-use application will some need non-volatile storage.

Resource Manager

The role of the resource manager is to assure that a task has the permission to access a resource. This also includes resource sharing, i.e. for buses. However, there is yet no model defined on how a task can get permission for a resource.

Kernel API

The kernel API separates all components that depend on the hardware from the rest of the system. It consists of several APIs for the individual parts of the kernel, e.g. for the scheduler, for IPC and for the resource manager.

The kernel API forms the barrier between thread and kernel mode. Meaning that the components above the kernel API have limited permissions and cannot propagate a critical error to the kernel. This is a precaution that should reduce kernel panics to a minimum.

System Services

System services provide extra functionality to the user but do not have the permissions the kernel has. These service include for example device drivers, file system and network stacks.

Device drivers are meant to be used for peripheral components that are not part of the MCU, e.g. accelerometer, Ethernet PHY or EEPROM. The drivers will mostly be written by the end user or the open-source community. The device drivers have been placed in the thread mode because they quite often contain bugs that can cause a kernel to panic.

The Rust community has already developed network stacks (e.g. smoltcp) and support to can access a file system (e.g. fat32). The goal here is not to redevelop these packages but to wrap them so that they work with the Bern RTOS. For example, Zephyr provides an API that allows the use of multiple file system implementations at the same time.