SVC 구현기

Nitroblue 1·2025년 9월 11일

What is ABI?

  • Application Binary Interface의 축약어.
    두 개 이상의 바이너리(기계어) 소프트웨어 모듈이 서로 통신하기 위한 저수준의 인터페이스 표준. API가 소스 코드 수준에서 소프트웨어 구성 요소를 정의하는 반면, ABI는 기계어 수준에서 함수 호출 규약, 데이터 형식, 메모리 레이아웃 등을 정의하여 런타임 호환성을 보장한다.

SVC layer

이 코드를 통해 어떤 플로우를 설계했는가?

  • 전체적인 Flow
    앱 코드 → Client의 sys_* 호출 → svc_call() → svc 0 → SVCall 트램펄린 → svcall_rust() → kernel_dispatch() → (보드 MMIO/커널 로직 수행) → 반환값을 예외 프레임 r0에 기록 → 예외 복귀 → svc_call() 반환 → 앱으로 복귀

  • 사용한 모듈들 : abi, svc_call, EXceptionFrame, global_asm!(SVCall 트램펄린), svcall_rust(러스트 핸들러), BOARD_PTR, register_kernel_board(커널 보드 포인터 등록), kernel_dispatch(커널 디스패처), Client(앱이 쓰는 Syscalls 구현)

1. ABI

pub mod abi {
	pub const NOW_MS: u8 = 1;
}
  • 시스템콜 종류들을 모아놓은 application binary interface이다. 추후 'BTN_PRESSED, GPIO_WRITE' 등의 기능들이 들어갈 예정.
  • r1~r3 : a0~a2, r12 : a3. r0 = call_id, u8로 관리하지만 실제로는 u32가 전달된다.

2. svc_call

#[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
}
  • 앱에서 함수처럼 시스템콜을 부르게 해주는 wrapper
  • inlateout("r0")로 결과를 받아온다.
  • nostack : 스택을 건드리지 않음(트램펄린이 스택 프레임을 직접 다루기 때문).

3. ExceptionFrame

#[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,
}
  • svc 진입 시에 하드웨어가 자동으로 쌓는 레지스터 묶음의 레이아웃.

4. SVCall 트램펄린

global_asm!(
    r#"
    .global SVCall
    .type   SVCall, %function
SVCall:
    tst     lr, #4
    ite     eq
    mrseq   r0, msp
    mrsne   r0, psp
    b       {svcrust}
"#,
    svcrust = sym crate::svc::svcall_rust,
);
  • MSP : Main Stack Pointer / PSP : Processor Stack Pointer
  • SVCall 트램펄린에서는 MSP/PSP중 어떤 스택이 쓰였는지 판별해서 프레임 포인터를 r0으로 넘기고, 러스트 핸들러로 점프한다.
  • lr의 EXC_RETURN bit2로 PSP/MSP 선택.
  • 어셈블리에서 스택 건드리지 않고 러스트 함수로 분기.

5. svcall_rust (러스트 핸들러)

#[no_mangle]
pub extern "C" fn svcall_rust(frame: &mut ExceptionFrame) {
    let call_id = (frame.r0 & 0xFF) as u8;
    let a0 = frame.r1; let a1 = frame.r2; let a2 = frame.r3; let a3 = frame.r12;
    let ret = unsafe { kernel_dispatch(call_id, a0, a1, a2, a3) };
    frame.r0 = ret; // 반환값을 r0에 써서 복귀
}
  • 프레임에서 인자 꺼내고 커널 디스패치 호출. 반환값을 r0에 기록.

6. BOARD_PTR, register_kernel_board (커널 보드 포인터 등록)

static mut BOARD_PTR: *mut crate::board::BoardSyscalls = core::ptr::null_mut();

pub unsafe fn register_kernel_board(p: *mut crate::board::BoardSyscalls) {
    BOARD_PTR = p;
}
  • 커널(SVC 핸들러)이 사용할 보드 객체를 알려줌.
  • 반드시 main()에서 보드 초기화 후 등록을 먼저 하고 SVC를 사용.

7. kernel_dispatch

unsafe fn kernel_dispatch(call_id: u8, a0: u32, a1: u32, a2: u32, a3: u32) -> u32 {
    let board = &mut *BOARD_PTR;
    match call_id {
        abi::NOW_MS => board.now_ms() as u32,
        // 앞으로:
        // abi::BTN_PRESSED => if board.user_button_pressed() { 1 } else { 0 },
        // abi::GPIO_WRITE  => { /* a0: pin, a1: high */; board.gpio_write(...); 0 }
        // abi::SLEEP_MS    => { board.sleep_ms(a0); 0 }
        _ => 0xFFFF_FFFF,
    }
}
  • 실제 kernel에서 동작을 수행하는 공간. MMIO, 타이머, MPUU 등 privilege 작업은 전부 여기서 진행한다.

8. Client

pub struct Client { board: *mut crate::board::BoardSyscalls }

impl crate::os::Syscalls for Client {
    fn now_ms(&self) -> u64 { svc_call(abi::NOW_MS, 0,0,0,0) as u64 }

    // 점진 전환: 아직은 보드 직접 호출로 fallback
    fn sleep_ms(&mut self, ms: u32) { unsafe { (&mut *self.board).sleep_ms(ms) } }
    fn gpio_write(&mut self, pin: crate::os::GpioPin, high: bool) { ... }
    fn gpio_toggle(&mut self, pin: crate::os::GpioPin) { ... }
    fn user_button_pressed(&self) -> bool { ... }
}
  • 앱에서 시스템콜을 했을 때, 앱 입장에서 바라보는 시스템콜 인터페이스를 제공한다.

SVC layer full code

// ------------------------- SVC layer ------------------------
mod svc {
    use core::arch::{ asm, global_asm };

    use crate::os::Syscalls;

    // --------- ABI : call_id definitions ----------
    pub mod abi {
        pub const NOW_MS: u8 = 1;
    }

    // --------- 공용 SVC call wrapper ----------------
    #[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
    }

    #[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,
    }

    // Removed duplicate declaration of svcall_rust to avoid multiple definitions.

    global_asm!(
        r#"
        .global SVCall
        .type   SVCall, %function
    SVCall:
        tst     lr, #4
        ite     eq
        mrseq   r0, msp
        mrsne   r0, psp
        b       {svcrust}
    "#,
        svcrust = sym crate::svc::svcall_rust
    );

    #[unsafe(no_mangle)]
    extern "C" fn svcall_rust(frame: &mut ExceptionFrame) {
        let call_id = (frame.r0 & 0xFF) as u8;
        let a0 = frame.r1;
        let a1 = frame.r2;
        let a2 = frame.r3;
        let a3 = frame.r12;
        let ret = unsafe { kernel_dispatch(call_id, a0, a1, a2, a3) };
        frame.r0 = ret;
    }

    // --------- (4) 커널(Board) 접근 포인터 등록 ----------
    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 };
    }

    // --------- (5) 실제 디스패처: 지금은 NOW_MS만 처리 ----------
    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,
            _ => 0xFFFF_FFFF, // unknown
        }
    }

    // --------- (6) Syscalls용 SVC 클라이언트 래퍼 ----------
    // fallback을 위해 보드 포인터 보관 (raw pointer로 보관: 빌림 충돌 회피)
    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 _ }
        }
    }

    // 기존 os::Syscalls 트레이트를 이 클라이언트가 구현
    impl crate::os::Syscalls for Client {
        // 1) now_ms만 SVC로 넘겨 테스트
        fn now_ms(&self) -> u64 {
            svc_call(abi::NOW_MS, 0, 0, 0, 0) as u64
        }

        // 2) 나머지는 일단 보드 직접 호출로 fallback (점진 전환)
        fn sleep_ms(&mut self, ms: u32) {
            unsafe { (&mut *self.board).sleep_ms(ms) }
        }
        fn gpio_write(&mut self, pin: crate::os::GpioPin, high: bool) {
            unsafe { (&mut *self.board).gpio_write(pin, high) }
        }
        fn gpio_toggle(&mut self, pin: crate::os::GpioPin) {
            unsafe { (&mut *self.board).gpio_toggle(pin) }
        }
        fn user_button_pressed(&self) -> bool {
            unsafe { (&*self.board).user_button_pressed() }
        }
    }
}

0개의 댓글