Configuration

An RTOS kernel has many settings to adjust the performance for a particular system. Consider a low power system running at 72 MHz with little RAM (e.g. 32 kB). There is no need for memory allocation or structures to store hundreds of threads. However, on a high performance microcontroller running 480 MHz, external memory and lots of interface the additional code size from allocation is not an issue.

Design

The kernel configuration consists of parameterizing internals, enabled optional modules and providing information about the hardware. Bern RTOS heavily relies on memory protection and linker sections to place static variables into process memory. As the kernel does not depend on microcontroller hardware specifics, the user has to provide memory layout information in the configuration.

An RTOS in C is typically configured using a header file. The preprocessor symbols are global during the build process. For example number of priorities is defined in the configuration header file in the user application and directly applied to the kernel.

In Rust those global symbols do not exist. One solution is to use constant generics in structures. For example, we could then set the number of priorities to 8 in the user application static SCHED: Scheduler<8> = Scheduler::new();. At the moment, const generics can only take primitive types. For some parameters strong types are preferred. Another solution is to use environment variables in the build shell. This approach suffers from weak types even more.

There are many more solutions to this problem. For simplicity reason the kernel uses a cargo patch in the build process.

conf-deps
Figure: Kernel configuration dependencies.

The bern-kernel crate in the figure depends on the default bern-conf crate implementation. On the application, the bern-conf crate is implemented again and passed as a patch to the build system. Thus, there are no circular dependencies. More importantly, the build command is still cargo build and does not require any external tools.

In addition to detailed adjustments in the configuration crate, some modules can be disabled altogether. System logs and tracing are not necessary for every application. These features are optional and configurable in the cargo manifest.

Usage

The API documentation lists the kernel features to enable optional modules. Configuring the memory layout and kernel parameters are done in the configuration crate.

[project]/conf/conf.rs

    pub const1CONF: Conf<1> = Conf {
        kernel: Kernel {2
            priorities: 8, 
            memory_size: Byte::from_kB(2),
        },
    
        shared: Shared  {3
            size: Byte::from_kB(8),
        },
    
        memory_map: MemoryMap4{
            flash: Memory {
                link_name: "FLASH",
                start_address: 0x0800_0000,
                size: Byte::from_MB(1),
            },
            sram: Memory {
                link_name: "RAM",
                start_address: 0x2000_0000,
                size: Byte::from_kB(512),
            },
            peripheral: Memory {
                link_name: "",
                start_address: 0x4000_0000,
                size: Byte::from_MB(512),
            },
            additional:5[
                OptionalMemory {
                    memory_type: MemoryType::Ram,
                    location: MemoryLocation::External,
                    link_name: "XRAM",
                    start_address: 0xC000_0000,
                    size: Byte::from_MB(32),
                }
            ],
        },
    
        data_placement: DataPlacement {6
            kernel: "RAM",
            processes: "RAM",
            shared: "RAM"
        }
    };

Listing: Bern RTOS memory configuration.

The configuration is a constant structure 1 in the listing. The kernel can use those constants to define the sizes of the static variables. First up is the kernel configuration 2. Here, we set the number of priorities and the size of the kernel RAM region. Static variables in the kernel and heap are present in the that region.

The shared region 3 will be accessible by all threads and processes. Sharing data with all parts of the application is undesirable. However, some libraries contain static data which are not placed in any process memory.

The memory map configuration 4 requires three regions and can contain optional regions. Required are flash, RAM and peripheral regions. The kernel sets up default memory access based on those regions. If a microcontroller offers special RAM or if there are external flash, RAM, or memory-mapped peripherals, additional 5 memory regions can be defined.

Lastly, the user can select the memory component where data will be placed 6. Often, this will be set to RAM.