🎉

ARM Cortex-R向けRustのサンプル実装について調査してみる

に公開

はじめに

たまたまいい資料が見つかったので(参考資料)を見つけましたのでそれについて内容をまとめてみました。

前提事項

実装に関しては以下の18c2337の実装をもとに記事を作成しました。

https://github.com/rust-embedded/aarch32

参考資料

概要

Ferrous Systemsによればミッションクリティカルかつセーフティクリティカルなリアルタイムシステム用にARM Cortex-Mの実装を参考に実装したそうです。明示的には記載されていませんが[
safety-critical-rust-consortium]([
safety-critical-rust-consortium] "https://github.com/rustfoundation/safety-critical-rust-consortium")の活動ともリンクしている可能性が高いです。

リポジトリの構成

リポジトリの構成としては以下のようになっています。

- .cargo
- .github
- .vscode
- aarch32-cpu … キャッシュやMMU、割り込みの操作などの実装を内包、いわゆるHAL層、Cortex-Mでいうところのcortex-mクレート
- aarch32-rt-macros … aarch32-rtの実装をもとに作成したマクロを内包
- aarch32-rt … ベクターテーブル等の実装あり、Cortex-Mでいうところのcortex-m-rtクレート
- arm-targets
- examples - サンプルアプリ
- .gitattributes
- .gitignore
- Cargo.toml
- LICENSE-APACHE
- LICENSE-MIT
- README.md
- justfile
- tests.sh

対応環境

'examples'を除いている見ると現状はMSP3-AN536とversatileabというボードをターゲットとしておりQEMU上でも動作可能であるようです。今回はMSP2-AN506の実装を深掘りしていこうと思います。

実装

エントリーポイントとベクターテーブル

リンカスクリプト(aarch32-rt/link.x)を見たところ、エントリーポイントはstartとなっているようです。

aarch32-rt/link.x

INCLUDE memory.x

ENTRY(_start);
EXTERN(_vector_table);
EXTERN(_start);
EXTERN(_default_handler);

~(中略)~

/* Weak aliases for ASM default handlers */
PROVIDE(_start                      = _default_start);
PROVIDE(_asm_undefined_handler      = _asm_default_undefined_handler);
PROVIDE(_asm_svc_handler            = _asm_default_svc_handler);
PROVIDE(_asm_prefetch_abort_handler = _asm_default_prefetch_abort_handler);
PROVIDE(_asm_data_abort_handler     = _asm_default_data_abort_handler);
PROVIDE(_asm_irq_handler            = _asm_default_irq_handler);
PROVIDE(_asm_fiq_handler            = _asm_default_fiq_handler);

/* Weak aliases for C default handlers */
PROVIDE(_undefined_handler      = _default_handler);
PROVIDE(_svc_handler            = _default_handler);
PROVIDE(_prefetch_abort_handler = _default_handler);
PROVIDE(_data_abort_handler     = _default_handler);
PROVIDE(_irq_handler            = _default_handler);
/* There is no default C-language FIQ handler */

そこから検索するとベクターテーブルもみつかりました。

aarch32-rt/src/lib.rs

// The Interrupt Vector Table, and some default assembly-language handler.
// Needs to be aligned to 5bits/2^5 to be stored correctly in VBAR
#[cfg(target_arch = "arm")]
core::arch::global_asm!(
    r#"
    .section .vector_table,"ax",%progbits
    .arm
    .global _vector_table
    .type _vector_table, %function
    .align 5
    _vector_table:
        ldr     pc, =_start
        ldr     pc, =_asm_undefined_handler
        ldr     pc, =_asm_svc_handler
        ldr     pc, =_asm_prefetch_abort_handler
        ldr     pc, =_asm_data_abort_handler
        nop
        ldr     pc, =_asm_irq_handler
        ldr     pc, =_asm_fiq_handler
    .size _vector_table, . - _vector_table
    "#
);

スタートアップコード

スタートアップコードはベクターテーブルと同じaarch32-rt/src/lib.rs内にあります。この中でEL2としての設定、EL1の設定を終えkmainなるシンボル(おそらくEL1のエントリーポイント、kernel main)にジャンプしています。

aarch32-rt/src/lib.rs


// Start-up code for Armv8-R.
//
// There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we
// always enable it.
//
// We boot into EL2, set up a stack pointer, and run `kmain` in EL1.
#[cfg(arm_architecture = "v8-r")]
core::arch::global_asm!(
    r#"
    // Work around https://github.com/rust-lang/rust/issues/127269
    .fpu vfp2

    .section .text.default_start
    .arm
    .global _default_start
    .type _default_start, %function
    _default_start:
        // Are we in EL2? If not, skip the EL2 setup portion
        mrs     r0, cpsr
        and     r0, r0, 0x1F
        cmp     r0, {cpsr_mode_hyp}
        bne     1f
        // Set stack pointer
        ldr     sp, =_hyp_stack
        // Set the HVBAR (for EL2) to _vector_table
        ldr     r1, =_vector_table
        mcr     p15, 4, r1, c12, c0, 0
        // Configure HACTLR to let us enter EL1
        mrc     p15, 4, r1, c1, c0, 1
        mov     r2, {hactlr_bits}
        orr     r1, r1, r2
        mcr     p15, 4, r1, c1, c0, 1
        // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked
        mov		r1, {sys_mode}
        msr		spsr_hyp, r1
        adr		r1, 1f
        msr		elr_hyp, r1
        dsb
        isb
        eret
    1:
        // Set up stacks.
        bl      _stack_setup_preallocated
        // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on
        // Armv7-R because that only supports 'low' (default) or 'high'.
        ldr     r0, =_vector_table
        mcr     p15, 0, r0, c12, c0, 0
        // Init .data and .bss
        bl      _init_segments
        "#,
        fpu_enable!(),
        r#"
        // Zero all registers before calling kmain
        mov     r0, 0
        mov     r1, 0
        mov     r2, 0
        mov     r3, 0
        mov     r4, 0
        mov     r5, 0
        mov     r6, 0
        mov     r7, 0
        mov     r8, 0
        mov     r9, 0
        mov     r10, 0
        mov     r11, 0
        mov     r12, 0
        // Jump to application
        bl      kmain
        // In case the application returns, loop forever
        b       .
    .size _default_start, . - _default_start
    "#,
    cpsr_mode_hyp = const ProcessorMode::Hyp as u8,
    hactlr_bits = const {
        Hactlr::new_with_raw_value(0)
            .with_cpuactlr(true)
            .with_cdbgdci(true)
            .with_flashifregionr(true)
            .with_periphpregionr(true)
            .with_qosr(true)
            .with_bustimeoutr(true)
            .with_intmonr(true)
            .with_err(true)
            .with_testr1(true)
            .raw_value()
    },
    sys_mode = const {
        Cpsr::new_with_raw_value(0)
            .with_mode(ProcessorMode::Sys)
            .with_i(true)
            .with_f(true)
            .raw_value()
    }
);

kmain自体はユーザが作成することを期待しています。

aarch32-rt/src/lib.rs


//! ## C-Compatible Functions
//!
//! ### Main Function
//!
//! The symbol `kmain` should be an `extern "C"` function. It is called in SYS
//! mode after all the global variables have been initialised. There is no
//! default - this function is mandatory.
//!
//! ```rust
//! #[unsafe(no_mangle)]
//! extern "C" fn kmain() -> ! {
//!     loop { }
//! }
//! ```
//!
//! You can also create a 'kmain' function by using the `#[entry]` attribute on
//! a normal Rust function. The function will be renamed in such a way that the
//! start-up assembly code can find it, but normal Rust code cannot. Therefore
//! you can be assured that the function will only be called once (unless someone
//! resorts to `unsafe` Rust to import the `kmain` symbol as an `extern "C" fn`).


サンプルアプリ

examples/mps3-an536/src/bin内に多数あります。今回は一例としてexamples/mps3-an536/src/bin/el2_hello.rsについてみていきたいと思います。

このソースはEL2で実行するためのサンプルでありおそらくハイパーバイザーを作成することを意図してその設定の仕方などを記載していると思われます。

先ほど説明した通り、すでにスタートアップルーチン自体は存在していましたがこのサンプルではEL2の特殊な設定が必要なため、startをオーバーライドして定義しています。ここからkmainにジャンプしてkmainからリターンしたら無限ループに入る実装となっています。

examples/mps3-an536/src/bin/el2_hello.rs

// Provide a custom `_start` function that sets us up in EL2 mode, with a
// stack.
//
// Unlike the default routine, it does not initialise any other stacks, or
// switch to EL1 mode.
core::arch::global_asm!(
    r#"
    // Work around https://github.com/rust-lang/rust/issues/127269
    .fpu vfp3-d16

    .section .text.start

    .global _start
    .type _start, %function
    _start:
        // Set stack pointer
        ldr     sp, =_hyp_stack
        // Set the HVBAR (for EL2) to _vector_table
        ldr     r1, =_vector_table
        mcr     p15, 4, r1, c12, c0, 0
        // Configure HACTLR to let us enter EL1
        mrc     p15, 4, r1, c1, c0, 1
        mov     r2, {hactlr_bits}
        orr     r1, r1, r2
        mcr     p15, 4, r1, c1, c0, 1
        // Init .data and .bss
        bl      _init_segments
        // Allow VFP coprocessor access
        mrc     p15, 0, r0, c1, c0, 2
        orr     r0, r0, #0xF00000
        mcr     p15, 0, r0, c1, c0, 2
        // Enable VFP
        mov     r0, #0x40000000
        vmsr    fpexc, r0
        // Zero all registers before calling kmain
        mov     r0, 0
        mov     r1, 0
        mov     r2, 0
        mov     r3, 0
        mov     r4, 0
        mov     r5, 0
        mov     r6, 0
        mov     r7, 0
        mov     r8, 0
        mov     r9, 0
        mov     r10, 0
        mov     r11, 0
        mov     r12, 0
        // Jump to application
        bl      kmain
        // In case the application returns, loop forever
        b       .
    .size _start, . - _start
    "#,
    hactlr_bits = const {
        Hactlr::new_with_raw_value(0)
            .with_cpuactlr(true)
            .with_cdbgdci(true)
            .with_flashifregionr(true)
            .with_periphpregionr(true)
            .with_qosr(true)
            .with_bustimeoutr(true)
            .with_intmonr(true)
            .with_err(true)
            .with_testr1(true)
            .raw_value()
    },
);

Discussion