~9.8 study 3

Nitroblue 1·2025년 9월 8일

코드 설명

  • Apps 총 2개 : Hearteat(default), SOS signal
    • Heartbeat : blink constantly
    • SOS signal : when push button(B1), blinks following SOS patterns, [..--.. ]
  • Call flow
앱 호출(ex. HeartbeatApp.tick)
→ sys.gpio_write(GpioPin::Led1, on)	// trait 호출
→ BoardSyscalls::gpio_write(...)	// vtable로 실제 구현
→ gpio_write(...)	// BSRR register write
→ write_volatile(...)

만약 진짜 시스템콜 느낌을 내려면 svc 트랩과 핸들러를 써서 커널로 들어가게 해야 한다. Tock OS가 그 방식. 'svc': Supervisor Call

Study : system call?

  • sys는 'OS가 제공하는 시스템 인터페이스에 대한 핸들'이고, 앱은 sys.gpio_write(...) 처럼 간접 호출만 할 수 있도록 해주는 중간다리 역할. 따라서 유저는 레지스터 주소를 알 수 없고, 접근도 할 수 없다.

  • 생각해보니 컴퓨터 조직론에서 배웠었다. 시스템콜이란 '일반 OS에서 유저 프로세스가 커널 서비스(파일, 네트워크, 타이머, GPIO 등)를 요청할 때 쓰는 공식 진입점이라는데, 컴조에서는 뭔가 중요한 interrupt가 발생했을 때 진행중인 instruction을 잠깐 중단하고 해당 문제를 해결하기 위해 하는 행위가 시스템콜이라고 배웠던 것 같다. 다 연결이 되는구나 :)

  • 실제로 이번에 작성한 코드에서는 하드웨어 보호를 하는 게 아니라 소프트웨어 인터페이스 레벨에서 Syscalls라는 중간 가드를 둬서 간접 호출만 허용한 케이스이다. 실제 레지스터 접근은 보드 계층 안에서만 가능하도록.

// mini_os_app_framework.rs (Button-controlled app switching, STM32F446)
// - Board: STM32F446 (e.g., Nucleo-F446RE)
// - LEDs: Map BOTH logical LEDs to PA5 (LD2) so everything is visible.
// - Button: Use user button B1 on PC13 with pull-up; pressed = LOW.
// - Clock: assume HSI 16 MHz; adjust CYCLES_PER_MS_ESTIMATE as needed.
#![no_std]
#![no_main]
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(non_snake_case)]


use cortex_m_rt::entry;
use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};

// ------------------------- OS Core -------------------------
mod os {
    #[derive(Copy, Clone, Debug)]
    pub enum GpioPin { Led1, Led2 }

    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;
        fn user_button_pressed(&self) -> bool; // ← Board input exposed as a syscall
    }

    pub trait App {
        fn name(&self) -> &'static str;
        fn init(&mut self, _sys: &mut dyn Syscalls) {}
        fn tick(&mut self, sys: &mut dyn Syscalls);
    }

    pub enum AppCall<'a> {
        ByName(&'a str),
        ByIndex(usize),
        All,
        /// Run `primary` app while button is released; run `secondary` while pressed.
        SwitchOnButton { primary: usize, secondary: usize },
    }

    pub struct Os<'a> {
        apps: &'a mut [&'a mut dyn App],
        sys: &'a mut dyn Syscalls,
        started: bool,
    }

    impl<'a> Os<'a> {
        pub fn new(apps: &'a mut [&'a mut dyn App], sys: &'a mut dyn Syscalls) -> Self {
            Self { apps, sys, started: false }
        }
        pub fn run(&'a mut self, call: AppCall<'a>) -> ! {
            // One-time init for all apps
            if !self.started { for a in self.apps.iter_mut() { a.init(self.sys); } self.started = true; }

            assert!(!self.apps.is_empty(), "no apps to run");

            match call {
                AppCall::ByIndex(mut i) => {
                    i %= self.apps.len();
                    loop { self.apps[i].tick(self.sys); }
                }
                AppCall::ByName(name) => {
                    let mut idx = 0usize;
                    for (i, a) in self.apps.iter().enumerate() { if a.name() == name { idx = i; break; } }
                    loop { self.apps[idx].tick(self.sys); }
                }
                AppCall::All => {
                    loop { for a in self.apps.iter_mut() { a.tick(self.sys); } }
                }
                AppCall::SwitchOnButton { mut primary, mut secondary } => {
                    let len = self.apps.len();
                    primary %= len; secondary %= len;
                    loop {
                        if self.sys.user_button_pressed() {
                            self.apps[secondary].tick(self.sys);
                        } else {
                            self.apps[primary].tick(self.sys);
                        }
                        self.sys.sleep_ms(1); // debounce / cooperative yield
                    }
                }
            }
        }
    }
}

// ------------------------- Apps ----------------------------
mod apps {
    use super::os::{App, GpioPin, Syscalls};

    /// Heartbeat: steady blink on PA5 (both Led1/Led2 mapped) to show liveness.
    pub struct HeartbeatApp { last: u64, on: bool, period_ms: u32 }
    impl HeartbeatApp { pub const fn new(period_ms: u32) -> Self { Self { last: 0, on: false, period_ms } } }
    impl App for HeartbeatApp {
        fn name(&self) -> &'static str { "heartbeat" }
        fn tick(&mut self, sys: &mut dyn Syscalls) {
            let now = sys.now_ms();
            if now.wrapping_sub(self.last) >= self.period_ms as u64 {
                self.on = !self.on;
                sys.gpio_write(GpioPin::Led1, self.on);
                sys.gpio_write(GpioPin::Led2, self.on);
                self.last = now;
            }
            sys.sleep_ms(1);
        }
    }

    /// SOS pattern on PA5: ··· ––– ···, repeats
    pub struct LedSosApp;
    impl LedSosApp { pub const fn new() -> Self { Self } }
    impl App for LedSosApp {
        fn name(&self) -> &'static str { "led_sos" }
        fn tick(&mut self, sys: &mut dyn Syscalls) {
            const DOT: u32 = 2; const DASH: u32 = 6; const GAP: u32 = 2; const WORD: u32 = 7;
            let mut pulse = |dur: u32| {
                sys.gpio_write(GpioPin::Led2, true);  sys.sleep_ms(dur);
                sys.gpio_write(GpioPin::Led2, false); sys.sleep_ms(GAP);
            };
            for _ in 0..2 { pulse(DOT); }
            for _ in 0..2 { pulse(DASH); }
            for _ in 0..2 { pulse(DOT); }
            sys.sleep_ms(WORD);
        }
    }

}

// -------------- Board layer: STM32F446 raw registers ---------
mod board {
    use core::ptr::{read_volatile, write_volatile};
    use super::os::{GpioPin, Syscalls};
    use cortex_m::asm::nop;

    // --- RCC base (STM32F4xx) ---
    const RCC_BASE: u32 = 0x4002_3800;
    const RCC_AHB1ENR: *mut u32 = (RCC_BASE + 0x30) as *mut u32; // GPIOxEN bits

    // --- GPIO base ---
    pub const GPIOA_BASE: u32 = 0x4002_0000;
    pub const GPIOC_BASE: u32 = 0x4002_0800;

    // Offsets (only what we use)
    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) {
        // AHB1ENR: bit0=GPIOA, bit2=GPIOC
        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) {
        // MODER: 01 = output
        let moder = reg32(port_base + MODER_OFF);
        let mut v = unsafe { read_volatile(moder) };
        let shift = (pin as u32) * 2;
        v &= !(0b11 << shift);
        v |=  0b01 << shift;
        unsafe { write_volatile(moder, v) };

        // OTYPER: push-pull
        let otyper = reg32(port_base + OTYPER_OFF);
        let mut v = unsafe { read_volatile(otyper) };
        v &= !(1 << pin);
        unsafe { write_volatile(otyper, v) };

        // PUPDR: no pull
        let pupdr = reg32(port_base + PUPDR_OFF);
        let mut v = unsafe { read_volatile(pupdr) };
        let shift2 = (pin as u32) * 2;
        v &= !(0b11 << shift2);
        unsafe { write_volatile(pupdr, v) };
    }

    unsafe fn gpio_set_input_pullup(port_base: u32, pin: u8) {
        // MODER: 00 = input
        let moder = reg32(port_base + MODER_OFF);
        let mut v = unsafe { read_volatile(moder) };
        let shift = (pin as u32) * 2;
        v &= !(0b11 << shift);
        unsafe { write_volatile(moder, v) };

        // PUPDR: 01 = pull-up
        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) };
        let high = ((cur >> pin) & 1) == 0;
        unsafe { gpio_write(port_base, pin, high) };
    }

    unsafe fn gpio_read_input(port_base: u32, pin: u8) -> bool {
        let idr = reg32(port_base + IDR_OFF);
        let v = unsafe { read_volatile(idr) };
        ((v >> 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, // PA5
        led2: RawPin, // PA5
        btn:  RawPin, // PC13
        time_ms: u64,
        cycles_per_ms: u32,
    }

    impl BoardSyscalls {
        pub const fn new(led1: RawPin, led2: RawPin, btn: RawPin, cycles_per_ms: u32) -> Self {
            Self { led1, led2, btn, time_ms: 0, cycles_per_ms }
        }

        pub unsafe fn init(&mut self) {
            unsafe {
                gpio_enable_clock(GPIOA_BASE);
                gpio_enable_clock(GPIOC_BASE);
                gpio_set_output(self.led1.port_base, self.led1.pin);
                gpio_set_output(self.led2.port_base, self.led2.pin);
                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),
                    GpioPin::Led2 => gpio_write(self.led2.port_base, self.led2.pin, high),
                }
            }
        }
        fn gpio_toggle(&mut self, pin: GpioPin) {
            unsafe {
                match pin {
                    GpioPin::Led1 => gpio_toggle(self.led1.port_base, self.led1.pin),
                    GpioPin::Led2 => gpio_toggle(self.led2.port_base, self.led2.pin),
                }
            }
        }
        fn sleep_ms(&mut self, ms: u32) { self.spin_delay(ms); }
        fn now_ms(&self) -> u64 { self.time_ms }
        fn user_button_pressed(&self) -> bool {
            unsafe {
                // B1 on Nucleo-F446RE (PC13): pull-up. Pressed => level LOW.
                let high = gpio_read_input(self.btn.port_base, self.btn.pin);
                !high
            }
        }
    }

    pub const GPIOA: u32 = GPIOA_BASE;
    pub const GPIOC: u32 = GPIOC_BASE;
}

// --------------------------- main ---------------------------
const CYCLES_PER_MS_ESTIMATE: u32 = 16_000; // HSI 16 MHz (tune if needed)

#[entry]
fn main() -> ! {
    rtt_init_print!();
    rprintln!("[mini-os] booting (button-controlled switching)");

    // Map both logical LEDs to PA5 for visibility; button is PC13.
    let mut syscalls = board::BoardSyscalls::new(
        board::RawPin::new(board::GPIOA, 5),  // Led1 → PA5
        board::RawPin::new(board::GPIOA, 5),  // Led2 → PA5
        board::RawPin::new(board::GPIOC, 13), // Btn  → PC13
        CYCLES_PER_MS_ESTIMATE,
    );

    unsafe { syscalls.init(); }
    rprintln!("GPIO ready: PA5 output, PC13 input-pullup");

    // Two apps: 0 = heartbeat, 1 = SOS
    let mut app_beat = apps::HeartbeatApp::new(3);
    let mut app_sos  = apps::LedSosApp::new();
    let mut app_list: [&mut dyn os::App; 2] = [ &mut app_beat, &mut app_sos ];

    let mut kernel = os::Os::new(&mut app_list, &mut syscalls);
    // Button not pressed => heartbeat; pressed => SOS
    kernel.run(os::AppCall::SwitchOnButton { primary: 0, secondary: 1 })
}

0개의 댓글