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
-
Inline assembly:
- Procedure Call Standard for the ARM® Architecture
- Cortex-M3 Technical Reference Manual
- Deprecated Features in ARMv7-M
-
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
- UART
-
RTOS features:
- stacks
- system stack
- irq stack
- user stack
- 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
- stacks
-
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)
TODO:
- use SVC for syscalls
- arm user vs system modes
- user / system / irq ? and stacks
- thread mode vs handler mode
- Target triplet ?
thumbv7em-none-eabihf
, or maybethumbv7m-none-eabi
is enough ? understand why .data MYVAR is already initialized in QEMU-> QEMU loads the .data section from the ELF file to RAMunderstand 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)
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_name = "my_symbol"]
extern "C" fn my_function() {
// ...
}
#[link_section = ".kvars"]
static mut BAZ: u32 = 42;
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();
!!! 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;
}
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.
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();
}
If you want to watch a static rust variable, you need to use its full name, for example:
The full names can be found in the output of nm
: e.g. 2000000c 00000014 d demo::KERNEL
What is the purpose of PhantomData
in the following code ?
pub struct SCB {
_marker: PhantomData<*const ()>,
}
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,
}
}
}