Current Code Flow (~09.12)

Nitroblue 1·2025년 9월 11일

현재 코드는 “앱 → SVC → 커널 → 보드(MMIO)” Flow.


0) 부팅~런루프 진입 (main)

  1. main()

    • 보드 인스턴스 생성: let mut board = board::BoardSyscalls::new(...)
    • 보드 초기화: unsafe { board.init(); }
    • 커널에 보드 포인터 등록: unsafe { svc::register_kernel_board(&mut board as *mut _) }
    • 앱이 볼 시스템콜 구현체를 SVC 클라이언트로 선택:
      let mut syscalls = unsafe { svc::Client::new(&mut board) }
    • OS 실행: os::Os::new(...).run(AppCall::SwitchOnButton{...})

이 시점부터 앱이 호출하는 Syscalls 메서드는 모두 SVC를 통해 커널로 넘어갈 준비가 되어 있음.


1) 앱/OS 레벨 (어디서 SVC를 호출하나?)

OS 스케줄링

  • os::Os::run(AppCall::SwitchOnButton{ primary, secondary })

    • 루프마다 버튼 상태를 읽어 앱 선택:

      if self.sys.user_button_pressed() { ... } else { ... }
      self.sys.sleep_ms(1);
    • 여기서 self.syssvc::Client → 아래 2)로 이어짐.

앱 로직

  • apps::HeartbeatApp::tick(sys)

    • 주기 체크: let now = sys.now_ms();SVC 호출
    • LED 토글: sys.gpio_write(...);(2회) → SVC 호출
    • sys.sleep_ms(1);SVC 호출
  • apps::LedSosApp::tick(sys)

    • sys.gpio_write(..., true)SVC
    • sys.sleep_ms(DOT/DASH/GAP/WORD)SVC
    • sys.gpio_write(..., false)SVC

결론: OS도 앱도 하드웨어 직접 접근 없음. 모든 I/O·타이밍은 Syscalls 경유 → 2)에서 SVC로 빠짐.


2) SVC 클라이언트 (앱 → 커널 경계)

svc::Clientos::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=a3
    • 반환값r0로 되돌려 받음.

3) SVC 트램펄린/핸들러 (커널)

트램펄린 (어셈) — SVCall

SVCall:
  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에 써서 복귀
  • 여기가 바로 커널 코드(Handler 모드, Privileged).
  • 카운터도 여기서 증가: SVC_COUNTER/NOW_COUNT/BTN_COUNT.

4) 커널 디스패처 → 보드 MMIO

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
}
  • 여기서 쓰는 boardregister_kernel_board()로 등록한 포인터(BOARD_PTR)를 역참조.
  • 하드웨어 접근은 전부 여기서만 수행됨. (커널에서 처리)

5) 보드 레벨 (MMIO)

board::BoardSyscalls가 실제 레지스터를 건드림:

  • gpio_writeBSRR에 SET/RESET 비트 쓰기
  • gpio_toggleODR 읽고 반전 → BSRR 쓰기
  • user_button_pressedIDR 읽기(PC13: 눌림=LOW)
  • sleep_msspin_delay(busy-wait)로 time_ms 누적
  • now_ms → 누적된 time_ms 읽기(현재는 sleep 기반)

6) 왕복 경로 요약 (예시 3개)

(a) 버튼 읽기(앱 선택)

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 분기

(b) LED 토글(Heartbeat)

App.tick → sys.gpio_write(Led1, on)
     → svc_call(GPIO_WRITE, pin=0, high=1) ──▶ 커널
     → board.gpio_write(...) → BSRR 쓰기 → 복귀
(한 번 더 Led2 동일)

(c) 슬립

App.tick → sys.sleep_ms(1)
     → svc_call(SLEEP_MS, ms=1) ──▶ 커널
     → board.sleep_ms(1) → busy-wait → 복귀

7) RTT 디버그(카운터)

  • 증가 지점: svcall_rust() 초입에서 SVC_COUNTER, call_id별로 NOW_COUNT, BTN_COUNT.
  • 읽기/출력: Heartbeat가 1초마다 svc::svc_stats() 호출해서 rprintln!.

8) 지금 상태의 권한 정리

  • SVC 핸들러(커널): Handler 모드 = 항상 Privileged.
  • 스레드(앱/OS): 현재 코드는 Thread 모드가 Privileged(아직 Unpriv로 안내린 상태).
    → 하지만 하드웨어는 전부 SVC를 통해서만 쓰고 있으니, 다음 단계로 PSP + Unprivileged 전환을 해도 코드 수정 거의 없이 넘어갈 준비가 끝난 상태.

요약

  • “커널로 넘어가는 순간” = svc_call(...)에서 asm!("svc 0") 실행될 때.
  • 넘어간 뒤의 진입점 = SVCall(어셈) → svcall_rust(러스트).
  • 진짜 MMIO는 **kernel_dispatchboard::***에서만 수행.

0개의 댓글