What is ABI?
- Application Binary Interface의 축약어.
두 개 이상의 바이너리(기계어) 소프트웨어 모듈이 서로 통신하기 위한 저수준의 인터페이스 표준. API가 소스 코드 수준에서 소프트웨어 구성 요소를 정의하는 반면, ABI는 기계어 수준에서 함수 호출 규약, 데이터 형식, 메모리 레이아웃 등을 정의하여 런타임 호환성을 보장한다.
전체적인 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 구현)
pub mod abi {
pub const NOW_MS: u8 = 1;
}
#[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,
}
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,
);
lr의 EXC_RETURN bit2로 PSP/MSP 선택.#[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에 써서 복귀
}
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;
}
main()에서 보드 초기화 후 등록을 먼저 하고 SVC를 사용.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,
}
}
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 ------------------------
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() }
}
}
}