Skip to content

This learning project is an attempt to build a simple/naive RTOS for ARM Cortex-M microcontrollers in Rust.

Notifications You must be signed in to change notification settings

lucasdietrich/arm-rtos-rs

Repository files navigation

Rust ARM RTOS playground

This personal learning project is an attempt to build a simple/naive RTOS for ARM Cortex-M microcontrollers in Rust. Targeted microcontrollers are the mps2_an385 (Cortex M3) platform and the stm32f429zi MCU (Cortex M4). I've already worked on an RTOS for AVR 8 bits microcontrollers, written in C: https://github.com/lucasdietrich/AVRTOS

Ressources

Datasheets:

Desired features

  • Architecture: (thumbv7em-none-eabihf), devices:

    • mps2_an385 (ARM Cortex-M3 )
    • stm32f4xx (ARM Cortex-M4)
  • Cortex M3/M4 initialization

    • RAM initialization
    • Vector table
    • Reset handler
    • PendSV
      • Configure lowest priority (0b111)
    • SVCall
      • Configure lowest priority (0b111)
    • Systick
      • Configure highest priority (0b000)
    • Other interrupts
  • Peripherals:

    • UART
      • mps2_an385
      • stm32f4xx
  • RTOS features:

    • stacks
      • system stack
      • user stack
      • irq stack
    • MSP/PSP
    • thread switch (without FPU support)
    • cooperative scheduling
    • preemptive scheduling
    • sleep
    • mutex
    • semaphore
    • minimal drivers support for UART and GPIO
    • syscalls:
      • printf
      • sleep
      • fork
      • mutex
      • semaphore
      • memory allocation
    • std library (allocator, collections, etc.)
      • rust
      • C
  • Minimal process: load an application from an elf file and run it

    • parse elf file
    • toolchain for build the application (C/Rust + linker script + relocation? + syscalls)

Questions/ideas/problems

TODO:

  • understand why .data MYVAR is already initialized in QEMU -> QEMU loads the .data section from the ELF file to RAM
  • understand why .data .bss appears in the ELF file -> QEMU loads the .bss section from the ELF file to RAM
  • Add the noinit section to the linker script
  • If symbol gets wiped out of the elf, gdb won't find it, we need to force the symbol to be kept in the elf file -> how to ? (e.g. _thread_switch)
  • Proper Systick/FCPU and FREQ_SYS_TICK handling

Notes

Rust toolchain

Tested with the following toolchains:

  • nightly-2024-10-01
  • nightly-2024-07-08

To install a new toolchain:

rustup toolchain add nightly-2024-10-01 --profile minimal

Static and const

const FOO: u32 = 42; // Const is a compile-time constant
static BAR: u32 = 42; // Static is a runtime constant
static mut BAZ: u32 = 42; // Static mutable 

Export symbols

Disable name mangling for a function:

#[export_name = "switch_to_user"]
fn switch_to_user() {
    // ...
}

#[export_name = "my_symbol"]
extern "C" fn my_function() {
    // ...
}

Links to a section

#[link_section = ".kvars"]
static mut BAZ: u32 = 42;

Make static variable extern

In order to export the symbol of a static variable, it must be declared with #[used]: The no_mangle attribute make sure the symbol name is not mangled by the compiler (e.g. demo::entry::z_current -> z_current)

#[used]
#[no_mangle]
pub static mut z_current: *mut Thread = core::ptr::null_mut();

Rename symbol

!!! warning "TODO" What is bellow is probably wrong

link_name must only be used on statics and functions that are in an extern block.

extern "C" {
    #[link_name = "z_current"]
    static mut z_current: *mut Thread;
}

Write ASM in rust code

Following inline assembly code is equivalent to the rust code:

use use core::arch::global_asm;
global_asm!(
    "
    .section .text, \"ax\"
    .global _pendsv
    .thumb_func
_pendsv:
    push {{r7, lr}}
    mov	r7, sp
    nop
    pop	{{r7, pc}}
    "
);

extern "C" {
    pub fn _pendsv();
}

Pure rust:

use use core::arch::asm;
#[no_mangle]
pub unsafe extern "C" fn _pendsv() {
    asm!("nop");
}

It's currently impossible to write naked functions in Rust, see rust-lang/rust#90957 for support for #[naked] functions.

Static initialization

A static variable can be initialized using a const function:

pub struct Kernel;

impl Kernel {
    pub const fn init() -> Kernel {
        Kernel {}
    }
}

fn main() {
    static mut KERNEL: Kernel = Kernel::init();
}

cortex-debug: Watch variables

If you want to watch a static rust variable, you need to use its full name, for example:

pics/cortex-debug-watch-rust-static.png

The full names can be found in the output of nm: e.g. 2000000c 00000014 d demo::KERNEL

PhantomData of non generic (TODO)

What is the purpose of PhantomData in the following code ?

pub struct SCB {
    _marker: PhantomData<*const ()>,
}

Force inlining

Feel free to help the compiler to inline a function by using the #[inline(always)] attribute:

impl<D: CsDomain> Cs<D> {
    #[inline(always)]
    /* This is the only method to obtain a critical session object */
    pub unsafe fn new() -> Self {
        Cs {
            domain: PhantomData,
        }
    }
}

Mark as uninit

Set variable in the .noinit section:

#[link_section = ".noinit"]
static mut THREAD_STACK1: u32 = 0;

Define a KernelSpecs trait

It would be great to have:

pub trait KernelSpecs {
    const FREQ_SYS_TICK: u32 = 100;
    const KOBJS: usize = 32;
}

// CPU: CPU variant
pub struct Kernel<'a, CPU: CpuVariant, Specs: KernelSpecs>
where
    [(); Specs::KOBJS]:,
    [(); Specs::FREQ_SYS_TICK as usize]:,
{
    tasks: sl::List<'a, Thread<'a, CPU>, Runqueue>,

    // systick
    systick: SysTick<{ Specs::FREQ_SYS_TICK }>,

    // Ticks counter: period: P (ms)
    ticks: u64,

    idle: Thread<'a, CPU>,
    // Idle thread

    // Kernel objects (Sync) for synchronization
    kobj: [Option<Box<dyn KernelObjectTrait<'a, CPU> + 'a>>; Specs::KOBJS],
}

However the feature is not well supported today, it needs #![feature(generic_const_exprs)] This is discussed here: rust-lang/rust#76560

About

This learning project is an attempt to build a simple/naive RTOS for ARM Cortex-M microcontrollers in Rust.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages