현재 코드는 “앱 → SVC → 커널 → 보드(MMIO)” Flow.
main()
let mut board = board::BoardSyscalls::new(...)unsafe { board.init(); }unsafe { svc::register_kernel_board(&mut board as *mut _) }let mut syscalls = unsafe { svc::Client::new(&mut board) }os::Os::new(...).run(AppCall::SwitchOnButton{...})이 시점부터 앱이 호출하는
Syscalls메서드는 모두 SVC를 통해 커널로 넘어갈 준비가 되어 있음.
os::Os::run(AppCall::SwitchOnButton{ primary, secondary })
루프마다 버튼 상태를 읽어 앱 선택:
if self.sys.user_button_pressed() { ... } else { ... }
self.sys.sleep_ms(1);
여기서 self.sys는 svc::Client → 아래 2)로 이어짐.
apps::HeartbeatApp::tick(sys)
let now = sys.now_ms(); → SVC 호출sys.gpio_write(...);(2회) → SVC 호출sys.sleep_ms(1); → SVC 호출apps::LedSosApp::tick(sys)
sys.gpio_write(..., true) → SVCsys.sleep_ms(DOT/DASH/GAP/WORD) → SVCsys.gpio_write(..., false) → SVC결론: OS도 앱도 하드웨어 직접 접근 없음. 모든 I/O·타이밍은
Syscalls경유 → 2)에서 SVC로 빠짐.
svc::Client가 os::Syscalls를 구현:
impl Syscalls for Client {
fn now_ms(&self)->u64 { svc_call(abi::NOW_MS, 0,0,0,0) as u64 } // → SVC
fn user_button_pressed(&self)->bool { svc_call(abi::BTN_PRESSED,0,0,0,0)!=0 } // → SVC
fn gpio_write(&mut self, pin, high){ svc_call(abi::GPIO_WRITE, p, h,0,0); } // → SVC
fn gpio_toggle(&mut self, pin) { svc_call(abi::GPIO_TOGGLE, p,0,0,0); } // → SVC
fn sleep_ms(&mut self, ms: u32) { svc_call(abi::SLEEP_MS, ms,0,0,0); } // → SVC
}
여기서 커널로 진입이 일어나는 지점:
svc_call(...) ──▶ asm!("svc 0")
ABI 약속:
r0=call_id (예: NOW_MS=1, BTN_PRESSED=2, GPIO_WRITE=3, GPIO_TOGGLE=4, SLEEP_MS=5)r1..r3=a0..a2, r12=a3r0로 되돌려 받음.SVCallSVCall:
tst lr,#4 ; MSP/PSP 판별
mrseq r0,msp
mrsne r0,psp ; r0 = 예외 프레임 포인터
b svcall_rust ; → 러스트 핸들러
svc::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 = kernel_dispatch(call_id, a0, a1, a2, a3);
frame.r0 = ret; // 반환값을 r0에 써서 복귀
SVC_COUNTER/NOW_COUNT/BTN_COUNT.svc::kernel_dispatch(call_id, a0..a3):
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 => { board.gpio_write(pin_enum, a1 != 0); 0 }
abi::GPIO_TOGGLE => { board.gpio_toggle(pin_enum); 0 }
abi::SLEEP_MS => { board.sleep_ms(a0); 0 }
_ => 0xFFFF_FFFF
}
board는 register_kernel_board()로 등록한 포인터(BOARD_PTR)를 역참조.board::BoardSyscalls가 실제 레지스터를 건드림:
gpio_write → BSRR에 SET/RESET 비트 쓰기gpio_toggle → ODR 읽고 반전 → BSRR 쓰기user_button_pressed → IDR 읽기(PC13: 눌림=LOW)sleep_ms → spin_delay(busy-wait)로 time_ms 누적now_ms → 누적된 time_ms 읽기(현재는 sleep 기반)Os.run → sys.user_button_pressed()
→ svc_call(BTN_PRESSED) ──▶ SVCall → svcall_rust → kernel_dispatch
→ board.user_button_pressed() (IDR 읽기)
→ 반환값 r0로 복귀 → Client.bool 변환 → Os.run 분기
App.tick → sys.gpio_write(Led1, on)
→ svc_call(GPIO_WRITE, pin=0, high=1) ──▶ 커널
→ board.gpio_write(...) → BSRR 쓰기 → 복귀
(한 번 더 Led2 동일)
App.tick → sys.sleep_ms(1)
→ svc_call(SLEEP_MS, ms=1) ──▶ 커널
→ board.sleep_ms(1) → busy-wait → 복귀
svcall_rust() 초입에서 SVC_COUNTER, call_id별로 NOW_COUNT, BTN_COUNT.svc::svc_stats() 호출해서 rprintln!.svc_call(...)에서 asm!("svc 0") 실행될 때.SVCall(어셈) → svcall_rust(러스트).kernel_dispatch → board::***에서만 수행.