3주 간의 개고생 끝에 결국 해냈다...!!!!!
- 이 코드는 STM32F446 보드 위에서 동작하는 Rust 기반 학습용 Mini RTOS로, Tock OS의 “tiered trust model”(core kernel–capsule–user app 분리 개념)과 FreeRTOS의 PendSV 기반 컨텍스트 스위칭 구조를 결합한 실험적 마이크로커널이다. board 모듈은 하드웨어 접근과 시스템 콜 인터페이스(SVC)를 담당해 커널의 특권 계층을 구현하고, capsules는 #![forbid(unsafe_code)]로 보호된 안전한 커널 확장 계층을 모사하며, sched는 PSP/MSP 분리와 PendSV 기반 태스크 스케줄링을 수행한다. 이를 통해 Rust의 안전성, Cortex-M의 하드웨어 레벨 태스크 관리, 그리고 보안 계층화된 OS 설계 원리를 단일 코드에서 실습할 수 있도록 설계된 최소형 운영체제 프레임워크이다.
다음 단계는 드디어 MPU를 사용한 각 어플리케이션별 스택메모리 공간의 분리이다.
이제부터 진짜 보안 설계 시작.
// mini_os_app_framework.rs
#![no_std]
#![no_main]
#![allow(dead_code)]
use cortex_m_rt::entry;
use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};
use stm32f4 as _; // Required for memory layout and vector table
// for debug
#[cortex_m_rt::exception]
unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
rprintln!("[FATAL] HardFault occurred at PC: 0x{:08x}", ef.pc());
loop {}
}
#[cortex_m_rt::exception]
unsafe fn UsageFault() -> ! {
rprintln!("[FATAL] UsageFault occurred");
loop {}
}
#[cortex_m_rt::exception]
unsafe fn MemoryManagement() -> ! {
rprintln!("[FATAL] MemoryManagement fault occurred");
loop {}
}
// for debug
const CYCLES_PER_MS_ESTIMATE: u32 = 16_000;
#[derive(Copy, Clone, Debug)]
pub enum GpioPin {
Led1,
}
pub trait Syscalls {
fn gpio_write(&mut self, pin: GpioPin, high: bool);
fn gpio_toggle(&mut self, pin: GpioPin);
fn sleep_ms(&mut self, ms: u32);
fn now_ms(&self) -> u64;
}
#[inline(always)]
fn gpio_pin_to_idx(pin: GpioPin) -> u32 {
match pin {
GpioPin::Led1 => 0,
}
}
// ───────────── BOARD LAYER ─────────────
mod board {
use core::ptr::{read_volatile, write_volatile};
use crate::{GpioPin, Syscalls};
use cortex_m::asm::nop;
const RCC_BASE: u32 = 0x4002_3800;
const RCC_AHB1ENR: *mut u32 = (RCC_BASE + 0x30) as *mut u32;
pub const GPIOA_BASE: u32 = 0x4002_0000;
pub const GPIOC_BASE: u32 = 0x4002_0800;
const MODER_OFF: u32 = 0x00;
const OTYPER_OFF: u32 = 0x04;
const PUPDR_OFF: u32 = 0x0C;
const IDR_OFF: u32 = 0x10;
const ODR_OFF: u32 = 0x14;
const BSRR_OFF: u32 = 0x18;
#[inline(always)]
const fn reg32(addr: u32) -> *mut u32 {
addr as *mut u32
}
unsafe fn gpio_enable_clock(port_base: u32) {
let bit = match port_base {
GPIOA_BASE => 0,
GPIOC_BASE => 2,
_ => unreachable!(),
};
let mut v = unsafe { read_volatile(RCC_AHB1ENR) };
v |= 1 << bit;
unsafe { write_volatile(RCC_AHB1ENR, v); }
for _ in 0..128 {
nop();
}
}
unsafe fn gpio_set_output(port_base: u32, pin: u8) {
let shift = (pin as u32) * 2;
let moder = reg32(port_base + MODER_OFF);
let mut v = unsafe { read_volatile(moder) };
v &= !(0b11 << shift);
v |= 0b01 << shift;
unsafe { write_volatile(moder, v) };
let otyper = reg32(port_base + OTYPER_OFF);
let mut v = unsafe { read_volatile(otyper) };
v &= !(1 << pin);
unsafe { write_volatile(otyper, v) };
let pupdr = reg32(port_base + PUPDR_OFF);
let mut v = unsafe { read_volatile(pupdr) };
v &= !(0b11 << shift);
unsafe { write_volatile(pupdr, v) };
}
unsafe fn gpio_set_input_pullup(port_base: u32, pin: u8) {
let shift = (pin as u32) * 2;
let moder = reg32(port_base + MODER_OFF);
let mut v = unsafe { read_volatile(moder) };
v &= !(0b11 << shift);
unsafe { write_volatile(moder, v) };
let pupdr = reg32(port_base + PUPDR_OFF);
let mut v = unsafe { read_volatile(pupdr) };
v &= !(0b11 << shift);
v |= 0b01 << shift;
unsafe { write_volatile(pupdr, v) };
}
unsafe fn gpio_write(port_base: u32, pin: u8, high: bool) {
let bsrr = reg32(port_base + BSRR_OFF);
let val = if high { 1u32 << pin } else { 1u32 << (pin + 16) };
unsafe { write_volatile(bsrr, val) };
}
unsafe fn gpio_toggle(port_base: u32, pin: u8) {
let odr = reg32(port_base + ODR_OFF);
let cur = unsafe { read_volatile(odr) };
unsafe { gpio_write(port_base, pin, ((cur >> pin) & 1) == 0); }
}
pub struct RawPin {
pub(crate) port_base: u32,
pub(crate) pin: u8,
}
impl RawPin {
pub const fn new(port_base: u32, pin: u8) -> Self {
Self { port_base, pin }
}
}
pub struct BoardSyscalls {
led1: RawPin,
btn: RawPin,
time_ms: u64,
cycles_per_ms: u32,
}
impl BoardSyscalls {
pub const fn new(led1: RawPin, btn: RawPin, cycles_per_ms: u32) -> Self {
Self {
led1,
btn,
time_ms: 0,
cycles_per_ms,
}
}
pub unsafe fn init(&mut self) {
unsafe { gpio_enable_clock(GPIOA_BASE); }
unsafe { gpio_enable_clock(GPIOC_BASE); }
unsafe { gpio_set_output(self.led1.port_base, self.led1.pin); }
unsafe { gpio_set_input_pullup(self.btn.port_base, self.btn.pin); }
}
fn spin_delay(&mut self, ms: u32) {
for _ in 0..ms {
for _ in 0..self.cycles_per_ms {
nop();
}
self.time_ms = self.time_ms.wrapping_add(1);
}
}
}
impl Syscalls for BoardSyscalls {
fn gpio_write(&mut self, pin: GpioPin, high: bool) {
unsafe {
match pin {
GpioPin::Led1 => gpio_write(self.led1.port_base, self.led1.pin, high),
}
}
}
fn gpio_toggle(&mut self, pin: GpioPin) {
unsafe {
match pin {
GpioPin::Led1 => gpio_toggle(self.led1.port_base, self.led1.pin),
}
}
}
fn sleep_ms(&mut self, ms: u32) {
self.spin_delay(ms);
}
fn now_ms(&self) -> u64 {
self.time_ms
}
}
pub struct GpioPriv {
pub(crate) port_base: u32,
pub(crate) pin: u8,
}
impl GpioPriv {
pub const unsafe fn new_privileged_const(port_base: u32, pin: u8) -> Self {
Self { port_base, pin }
}
#[inline]
pub fn write(&self, high: bool) {
unsafe { gpio_write(self.port_base, self.pin, high) };
}
#[inline]
pub fn toggle(&self) {
unsafe { gpio_toggle(self.port_base, self.pin) };
}
}
pub const GPIOA: u32 = GPIOA_BASE;
pub const GPIOC: u32 = GPIOC_BASE;
}
// ───────────── CAPSULES ─────────────
mod capsules {
#![forbid(unsafe_code)]
use crate::{board, GpioPin};
pub struct MuxGpio {
led1: &'static board::GpioPriv,
led2: &'static board::GpioPriv,
}
impl MuxGpio {
pub const fn new(led1: &'static board::GpioPriv, led2: &'static board::GpioPriv) -> Self {
Self { led1, led2 }
}
#[inline]
pub fn write(&self, pin: GpioPin, high: bool) {
match pin {
GpioPin::Led1 => self.led1.write(high),
}
}
pub fn toggle(&self, pin: GpioPin) {
match pin {
GpioPin::Led1 => self.led1.toggle(),
}
}
}
}
// ───────────── SVC ─────────────
mod svc {
use core::arch::{asm, global_asm};
use core::sync::atomic::{AtomicU32, Ordering};
use crate::{GpioPin, Syscalls};
pub mod abi {
pub const NOW_MS: u8 = 1;
pub const GPIO_WRITE: u8 = 2;
pub const GPIO_TOGGLE: u8 = 3;
pub const SLEEP_MS: u8 = 4;
}
#[inline(always)]
pub fn svc_call(call_id: u8, a0: u32, a1: u32, a2: u32, a3: u32) -> u32 {
let mut r0 = call_id as u32;
unsafe {
asm!(
"svc 0",
inlateout("r0") r0,
in("r1") a0,
in("r2") a1,
in("r3") a2,
in("r12") a3,
options(nostack)
);
}
r0
}
static SVC_COUNTER: AtomicU32 = AtomicU32::new(0);
static NOW_COUNT: AtomicU32 = AtomicU32::new(0);
pub fn svc_stats() -> (u32, u32) {
(
SVC_COUNTER.load(Ordering::Relaxed),
NOW_COUNT.load(Ordering::Relaxed),
)
}
#[repr(C)]
pub struct ExceptionFrame {
pub r0: u32,
pub r1: u32,
pub r2: u32,
pub r3: u32,
pub r12: u32,
pub lr: u32,
pub pc: u32,
pub xpsr: u32,
}
global_asm!(
r#"
.global SVCall
.type SVCall, %function
SVCall:
tst lr, #4
ite eq
mrseq r0, msp
mrsne r0, psp
b {svcrust}
"#,
svcrust = sym svcall_rust
);
extern "C" fn svcall_rust(frame: &mut ExceptionFrame) {
let call_id = (frame.r0 & 0xFF) as u8;
SVC_COUNTER.fetch_add(1, Ordering::Relaxed);
if call_id == abi::NOW_MS {
NOW_COUNT.fetch_add(1, Ordering::Relaxed);
}
let ret = unsafe {
kernel_dispatch(call_id, frame.r1, frame.r2, frame.r3, frame.r12)
};
frame.r0 = ret;
}
static mut BOARD_PTR: *mut crate::board::BoardSyscalls = core::ptr::null_mut();
pub unsafe fn register_kernel_board(p: *mut crate::board::BoardSyscalls) {
unsafe { BOARD_PTR = p; }
}
unsafe fn kernel_dispatch(call_id: u8, a0: u32, a1: u32, _a2: u32, _a3: u32) -> u32 {
let board = unsafe { &mut *BOARD_PTR };
match call_id {
abi::NOW_MS => board.now_ms() as u32,
abi::GPIO_WRITE => {
board.gpio_write(GpioPin::Led1, a1 != 0);
0
}
abi::GPIO_TOGGLE => {
board.gpio_toggle(GpioPin::Led1);
0
}
abi::SLEEP_MS => {
board.sleep_ms(a0);
0
}
_ => 0xFFFF_FFFF,
}
}
pub struct Client {
board: *mut crate::board::BoardSyscalls,
}
impl Client {
pub unsafe fn new(board: &mut crate::board::BoardSyscalls) -> Self {
Self { board: board as *mut _ }
}
}
impl Syscalls for Client {
fn now_ms(&self) -> u64 {
svc_call(abi::NOW_MS, 0, 0, 0, 0) as u64
}
fn sleep_ms(&mut self, ms: u32) {
let _ = svc_call(abi::SLEEP_MS, ms, 0, 0, 0);
}
fn gpio_write(&mut self, _pin: GpioPin, high: bool) {
let _ = svc_call(abi::GPIO_WRITE, 0, high as u32, 0, 0);
}
fn gpio_toggle(&mut self, _pin: GpioPin) {
let _ = svc_call(abi::GPIO_TOGGLE, 0, 0, 0, 0);
}
}
}
// ───────────── SCHEDULER & TASKS ─────────────
mod sched {
use cortex_m_rt::exception;
use crate::{task0_entry, task1_entry, task2_entry};
use rtt_target::rprintln;
pub const N_TASKS: usize = 3;
const TASK_STACK_WORDS: usize = 256;
const KERNEL_STACK_WORDS: usize = 512; // Kernel needs more stack for complex operations
#[derive(Copy, Clone, Debug)]
pub enum TaskState {
Ready,
Running,
Blocked,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Tcb {
pub sp: u32, // Process Stack Pointer
pub r4: u32, // Callee-saved registers
pub r5: u32,
pub r6: u32,
pub r7: u32,
pub r8: u32,
pub r9: u32,
pub r10: u32,
pub r11: u32,
pub control: u32, // CONTROL register value
pub state: TaskState,
}
#[repr(align(8))]
#[derive(Copy, Clone)]
struct TaskStack([u32; TASK_STACK_WORDS]);
#[repr(align(8))]
#[derive(Copy, Clone)]
struct KernelStack([u32; KERNEL_STACK_WORDS]);
static mut TCBS: [Tcb; N_TASKS] = [Tcb {
sp: 0,
r4: 0, r5: 0, r6: 0, r7: 0, r8: 0, r9: 0, r10: 0, r11: 0,
control: 0x02, // Use PSP for thread mode
state: TaskState::Ready
}; N_TASKS];
static mut TASK_STACKS: [TaskStack; N_TASKS] = [TaskStack([0; TASK_STACK_WORDS]); N_TASKS];
static mut KERNEL_STACK: KernelStack = KernelStack([0; KERNEL_STACK_WORDS]);
static mut CURR: usize = 0;
const ICSR: *mut u32 = 0xE000_ED04 as *mut u32;
const SHPR3: *mut u32 = 0xE000_ED20 as *mut u32;
// Initialize task stack and TCB for new context switching approach
fn init_task_stack_and_tcb(stack: &mut [u32], task_fn: usize, tcb: &mut Tcb) {
let len = stack.len();
// Validate task function address
if (task_fn as u32) < 0x08000000 || (task_fn as u32) >= 0x08100000 {
rprintln!("[FATAL] Invalid task function: 0x{:08x}", task_fn);
loop {}
}
// Stack layout: only hardware context (r0, r1, r2, r3, r12, lr, pc, xpsr) - 8 words from top
let sp = unsafe { stack.as_ptr().add(len - 8) as u32 };
// Initialize hardware context for exception return
let hw_frame = unsafe { core::slice::from_raw_parts_mut(sp as *mut u32, 8) };
hw_frame[0] = 0x00000000; // r0
hw_frame[1] = 0x01010101; // r1
hw_frame[2] = 0x02020202; // r2
hw_frame[3] = 0x03030303; // r3
hw_frame[4] = 0x12121212; // r12
hw_frame[5] = 0xFFFFFFFE; // LR (exception return value)
hw_frame[6] = task_fn as u32 | 1; // PC with Thumb bit
hw_frame[7] = 0x01000000; // xPSR with Thumb state
// Initialize TCB with software context (callee-saved registers)
tcb.sp = sp; // PSP points to hardware frame
tcb.r4 = 0x44444444; // r4
tcb.r5 = 0x55555555; // r5
tcb.r6 = 0x66666666; // r6
tcb.r7 = 0x77777777; // r7
tcb.r8 = 0x88888888; // r8
tcb.r9 = 0x99999999; // r9
tcb.r10 = 0xAAAAAAAA; // r10
tcb.r11 = 0xBBBBBBBB; // r11
tcb.control = 0x02; // CONTROL: Use PSP for thread mode
tcb.state = TaskState::Ready;
rprintln!("[STACK] Task SP: 0x{:08x}, PC: 0x{:08x}", sp, hw_frame[6]);
}
#[unsafe(no_mangle)]
extern "C" fn task_return_trap() -> ! {
rprintln!("[FATAL] Task returned unexpectedly - this should never happen");
loop {
unsafe {
core::ptr::write_volatile(ICSR, 1 << 28);
// Memory barrier
core::arch::asm!("dsb", "isb", options(nomem, nostack));
// Wait a bit before retriggering
for _ in 0..1000 {
core::arch::asm!("nop", options(nomem, nostack));
}
}
}
}
pub unsafe fn init_kernel_and_tasks() {
unsafe {
// Initialize kernel stack - MSP will continue to use this
let _kernel_stack_ptr = core::ptr::addr_of_mut!(KERNEL_STACK.0);
// MSP should already be pointing to a valid kernel stack
// We don't change MSP here - it stays as the kernel/interrupt stack
// Initialize Task 0
let task0_addr = task0_entry as usize;
rprintln!("[SCHED] Task 0 entry point: 0x{:08x}", task0_addr);
init_task_stack_and_tcb(&mut TASK_STACKS[0].0, task0_addr, &mut TCBS[0]);
// Memory barrier and small delay before next task
core::arch::asm!("dsb", "isb", options(nomem, nostack));
for _ in 0..1000 {
core::arch::asm!("nop", options(nomem, nostack));
}
// Initialize Task 1
let task1_addr = task1_entry as usize;
rprintln!("[SCHED] Task 1 entry point: 0x{:08x}", task1_addr);
init_task_stack_and_tcb(&mut TASK_STACKS[1].0, task1_addr, &mut TCBS[1]);
// Initialize Task 2
let task2_addr = task2_entry as usize;
rprintln!("[SCHED] Task 2 entry point: 0x{:08x}", task2_addr);
init_task_stack_and_tcb(&mut TASK_STACKS[2].0, task2_addr, &mut TCBS[2]);
CURR = 0;
rprintln!("[SCHED] All tasks initialized");
}
}
pub unsafe fn init_systick_1s() {
unsafe {
// Initialize BASEPRI to 0 (no masking)
core::arch::asm!("mov r0, #0", "msr basepri, r0", out("r0") _, options(nomem, nostack));
// Set up SysTick registers
let syst_csr = 0xE000_E010 as *mut u32;
let syst_rvr = 0xE000_E014 as *mut u32;
let syst_cvr = 0xE000_E018 as *mut u32;
// Configure SysTick: 1 second intervals
core::ptr::write_volatile(syst_rvr, 15999999); // 1s at 16MHz
core::ptr::write_volatile(syst_cvr, 0); // Clear current value
core::ptr::write_volatile(syst_csr, (1 << 2) | 1); // Enable counting but no interrupt initially
rprintln!("[SYSTICK] Configured for 1 second intervals");
// Memory barrier
core::arch::asm!("dsb", "isb", options(nomem, nostack));
}
}
pub unsafe fn enable_systick_interrupt() {
unsafe {
let syst_csr = 0xE000_E010 as *mut u32;
// Enable both counting and interrupt
core::ptr::write_volatile(syst_csr, (1 << 2) | (1 << 1) | 1);
// Memory barrier
core::arch::asm!("dsb", "isb", options(nomem, nostack));
}
}
pub fn start() -> ! {
rprintln!("[SCHED] Starting...");
unsafe {
let mut scb = cortex_m::Peripherals::take().unwrap().SCB;
scb.set_priority(cortex_m::peripheral::scb::SystemHandler::PendSV, 255);
scb.set_priority(cortex_m::peripheral::scb::SystemHandler::SysTick, 128);
init_systick_1s();
enable_systick_interrupt();
}
// Kernel idle loop - Wait For Interrupt (CPU sleeps until interrupt)
loop {
cortex_m::asm::wfi();
}
}
static mut FIRST_SWITCH: bool = true;
static mut NEXT_TASK_PSP: u32 = 0;
/*
// Old PendSV - simple restart approach (disabled)
#[exception]
fn PendSV() {
unsafe {
cortex_m::peripheral::SCB::clear_pendsv();
if FIRST_SWITCH {
FIRST_SWITCH = false;
rprintln!("[PendSV] First switch to Task 0");
// Get Task 0's stack pointer - this points to control+lr+sw+hw context
// For first run, we need to skip to hardware context
let task0_sp = TCBS[0].sp;
let hw_context_sp = task0_sp + (2 + 10) * 4; // Skip control+lr + r2-r11
core::arch::asm!(
// Set PSP to Task 0's hardware exception frame
"msr psp, {psp}",
// Switch to thread mode using PSP
"mrs r1, control",
"orr r1, r1, #2", // Use PSP for thread mode
"msr control, r1",
"isb",
// Return to thread mode - hardware will restore exception frame
"mov lr, #0xFFFFFFFD",
"bx lr",
psp = in(reg) hw_context_sp,
options(noreturn)
);
} else {
// Normal context switching
let current_task = CURR;
let next_task = (current_task + 1) % N_TASKS;
rprintln!("[PendSV] Switch: Task {} -> Task {}", current_task, next_task);
CURR = next_task;
// Always use initial stack for simplicity - tasks restart fresh ... [TODO]
let task_sp = TCBS[next_task].sp;
let hw_context_sp = task_sp + (2 + 10) * 4; // Skip to hardware context
core::arch::asm!(
// Set PSP to next task's hardware exception frame
"msr psp, {psp}",
// Switch to thread mode using PSP
"mrs r1, control",
"orr r1, r1, #2", // Use PSP for thread mode
"msr control, r1",
"isb",
// Return to thread mode - hardware will restore exception frame
"mov lr, #0xFFFFFFFD",
"bx lr",
psp = in(reg) hw_context_sp,
options(noreturn)
);
}
}
}
*/
// Context switching Rust helper functions - returns r4_ptr, sets PSP in global
extern "C" fn pend_sv_switch_rust() -> *mut u32 {
unsafe {
cortex_m::peripheral::SCB::clear_pendsv();
if FIRST_SWITCH {
FIRST_SWITCH = false;
// rprintln!("[🚀 START] Task 0");
// Mark task 0 as running
TCBS[0].state = TaskState::Running;
// Set PSP in global and return r4 pointer
NEXT_TASK_PSP = TCBS[0].sp;
return core::ptr::addr_of_mut!(TCBS[0].r4);
} else {
// Normal context switching
let current_task = CURR;
let next_task = (current_task + 1) % N_TASKS;
// rprintln!("[🔄 SWITCH] Task {} → Task {}", current_task, next_task);
// Update task states
TCBS[current_task].state = TaskState::Ready;
TCBS[next_task].state = TaskState::Running;
CURR = next_task;
// Set PSP in global and return r4 pointer
NEXT_TASK_PSP = TCBS[next_task].sp;
return core::ptr::addr_of_mut!(TCBS[next_task].r4);
}
}
}
extern "C" fn save_current_context_rust(psp: *mut u32, r4: u32, r5: u32, r6: u32, r7: u32, r8: u32, r9: u32, r10: u32, r11: u32) {
unsafe {
if CURR < N_TASKS { // Safety check
let current_task = CURR;
// Save current PSP and software context to TCB
TCBS[current_task].sp = psp as u32;
TCBS[current_task].r4 = r4;
TCBS[current_task].r5 = r5;
TCBS[current_task].r6 = r6;
TCBS[current_task].r7 = r7;
TCBS[current_task].r8 = r8;
TCBS[current_task].r9 = r9;
TCBS[current_task].r10 = r10;
TCBS[current_task].r11 = r11;
// Show which task was running and a simple progress indicator
// match current_task {
// 0 => rprintln!("[TASK0] 🔵 Accumulator task was running"),
// 1 => rprintln!("[TASK1] 🟡 Fibonacci task was running"),
// 2 => rprintln!("[TASK2] 🟣 Sum task was running"),
// _ => {}
// }
}
}
}
use core::arch::global_asm;
global_asm!(
r#"
.global PendSV
.type PendSV, %function
PendSV:
@ PendSV always runs in privileged mode using MSP (kernel stack)
@ This preserves kernel context automatically
mrs r0, psp
cbz r0, first_switch
normal_switch:
@ Normal task-to-task switch
@ Save current task's software context (r4-r11) to TCB
@ Pass PSP and all callee-saved registers to save function
push {{lr}}
mov r1, r4 @ r1 = r4
mov r2, r5 @ r2 = r5
mov r3, r6 @ r3 = r6
push {{r7-r11}} @ push r7-r11 onto stack for function call
bl {save_context_fn}
add sp, sp, #20 @ clean up stack (5 registers * 4 bytes)
pop {{lr}}
@ Call scheduler to get next task's context pointer
push {{lr}}
bl {switch_fn}
@ r0 = r4_ptr
mov r2, r0 @ Save r4_ptr in r2
pop {{lr}}
@ Load PSP from global variable
ldr r1, ={next_psp}
ldr r1, [r1]
@ Load new task's context from r2 (r4 pointer)
ldmia r2, {{r4-r11}}
@ Set PSP from r1
msr psp, r1
@ Return to thread mode
bx lr
first_switch:
@ Initial switch from kernel to first task
@ PSP is 0, so we're switching from MSP (kernel) to PSP (task)
@ Call scheduler to get first task's context
push {{lr}}
bl {switch_fn}
@ r0 = r4_ptr
mov r2, r0 @ Save r4_ptr in r2
pop {{lr}}
@ Load PSP from global variable
ldr r1, ={next_psp}
ldr r1, [r1]
@ Load task's software context from r2 (r4 pointer)
ldmia r2, {{r4-r11}}
@ Set PSP from r1
msr psp, r1
@ Switch to thread mode using PSP
mrs r1, CONTROL
orr r1, r1, #2 @ Use PSP for thread mode
msr CONTROL, r1
isb @ Instruction barrier for CONTROL changes
@ Ensure BASEPRI is cleared for tasks
mov r2, #0
msr basepri, r2
@ Set return to thread mode with PSP (EXC_RETURN = 0xFFFFFFFD)
movw lr, #0xFFFD
movt lr, #0xFFFF
bx lr
"#,
switch_fn = sym pend_sv_switch_rust,
save_context_fn = sym save_current_context_rust,
next_psp = sym NEXT_TASK_PSP
);
#[exception]
fn SysTick() {
// Trigger PendSV every 2 seconds
cortex_m::peripheral::SCB::set_pendsv();
}
}
// ───────────── TASKS ─────────────
// Global task state variables for reporting
static mut TASK0_COUNTER: u32 = 1000;
static mut TASK0_ACCUMULATOR: u32 = 0;
static mut TASK1_COUNTER: u32 = 2000;
static mut TASK1_FIB_A: u32 = 0;
static mut TASK1_FIB_B: u32 = 1;
static mut TASK1_FIB_COUNT: u32 = 0;
static mut TASK2_COUNTER: u32 = 3000;
static mut TASK2_SUM: u32 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn task0_entry() -> ! {
rprintln!("[TASK0] Entry - context switching validation started!");
// Turn on LED using syscalls to indicate task is running
syscalls().gpio_write(GpioPin::Led1, true);
loop {
unsafe {
TASK0_COUNTER = TASK0_COUNTER.wrapping_add(1);
TASK0_ACCUMULATOR = TASK0_ACCUMULATOR.wrapping_add(TASK0_COUNTER * 7);
// Show progress with LED patterns (fast feedback)
if TASK0_COUNTER % 5000 == 0 {
// LED toggle to show task 0 is working
syscalls().gpio_toggle(GpioPin::Led1);
}
// Frequent output for quick feedback
if TASK0_COUNTER % 500 == 0 {
cortex_m::interrupt::disable();
let c = core::ptr::read_volatile(core::ptr::addr_of!(TASK0_COUNTER));
let a = core::ptr::read_volatile(core::ptr::addr_of!(TASK0_ACCUMULATOR));
rprintln!("[DEBUG] Task0 - Counter: {}, Accum: 0x{:08x}", c, a);
cortex_m::interrupt::enable();
}
}
// Simple delay
for _ in 0..1000 {
cortex_m::asm::nop();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn task1_entry() -> ! {
rprintln!("[TASK1] Entry - fibonacci sequence calculator started!");
loop {
unsafe {
TASK1_COUNTER = TASK1_COUNTER.wrapping_add(1);
// Calculate fibonacci sequence every 100 iterations
if TASK1_COUNTER % 100 == 0 {
let fib_next = TASK1_FIB_A.wrapping_add(TASK1_FIB_B);
TASK1_FIB_A = TASK1_FIB_B;
TASK1_FIB_B = fib_next;
TASK1_FIB_COUNT = TASK1_FIB_COUNT.wrapping_add(1);
// Quick fibonacci milestones (every 5 calculations)
if TASK1_FIB_COUNT % 5 == 0 {
cortex_m::interrupt::disable();
let count = core::ptr::read_volatile(core::ptr::addr_of!(TASK1_FIB_COUNT));
let fib = core::ptr::read_volatile(core::ptr::addr_of!(TASK1_FIB_B));
rprintln!("[DEBUG] Task1 - Fib#{}: {}", count, fib);
cortex_m::interrupt::enable();
}
}
}
// Simple delay
for _ in 0..1000 {
cortex_m::asm::nop();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn task2_entry() -> ! {
rprintln!("[TASK2] Entry - prime number finder started!");
loop {
unsafe {
TASK2_COUNTER = TASK2_COUNTER.wrapping_add(1);
TASK2_SUM = TASK2_SUM.wrapping_add(TASK2_COUNTER);
// Frequent output for quick feedback
if TASK2_COUNTER % 500 == 0 {
cortex_m::interrupt::disable();
let c = core::ptr::read_volatile(core::ptr::addr_of!(TASK2_COUNTER));
let s = core::ptr::read_volatile(core::ptr::addr_of!(TASK2_SUM));
rprintln!("[DEBUG] Task2 - Counter: {}, Sum: {}", c, s);
cortex_m::interrupt::enable();
}
}
// Simple delay
for _ in 0..1000 {
cortex_m::asm::nop();
}
}
}
// ───────────── MAIN ENTRY ─────────────
static mut BOARD: Option<board::BoardSyscalls> = None;
static mut SYSCALL_CLIENT: Option<svc::Client> = None;
static mut SYSCALLS_PTR: *mut svc::Client = core::ptr::null_mut();
#[inline(always)]
fn syscalls() -> &'static mut svc::Client {
unsafe {
if SYSCALLS_PTR.is_null() {
// Hang instead of using rprintln
loop {}
}
&mut *SYSCALLS_PTR
}
}
// LED debugging functions removed - using RTT instead
#[entry]
fn main() -> ! {
rtt_init_print!();
// RTT 초기화 확인을 위한 지연
for _ in 0..100000 {
cortex_m::asm::nop();
}
rprintln!("[mini-os] Booting");
unsafe {
// ───── Initialize static BOARD instance ─────
BOARD = Some(board::BoardSyscalls::new(
board::RawPin::new(board::GPIOA, 5),
board::RawPin::new(board::GPIOC, 13),
CYCLES_PER_MS_ESTIMATE,
));
// Extract raw pointer to BOARD
let board_option_ptr = core::ptr::addr_of_mut!(BOARD);
let board_ptr: *mut board::BoardSyscalls = (*board_option_ptr).as_mut().unwrap() as *mut _;
// Call .init() via pointer deref
(*board_ptr).init();
// Register with SVC
svc::register_kernel_board(board_ptr);
// ───── Create syscall client instance and assign to global ─────
SYSCALL_CLIENT = Some(svc::Client::new(&mut *board_ptr));
let client_option_ptr = core::ptr::addr_of_mut!(SYSCALL_CLIENT);
SYSCALLS_PTR = (*client_option_ptr).as_mut().unwrap() as *mut _;
}
rprintln!("[MAIN] Board initialization complete");
rprintln!("[MAIN] Initializing OS...");
unsafe {
sched::init_kernel_and_tasks();
}
sched::start();
}