RP2040(Raspberry Pi Pico) - Rustでインライン・アセンブラ(ARM/Thumb2)

Written on 2024-06-02

RustのコードからARM(thuumb2)のインライン・アセンブラを扱う方法は、ズバリの情報が少なかった。この記事が一助となれば嬉しい。 日本語の場合はRust by Exampleが一次情報となるだろう。

bootloaderのサブ記事だが、独立させておく。

bootloaderからアプリケーションのコードにジャンプする

前編でも書いたように、ldmiamsrなどの特殊な命令を使うので、インライン・アセンブラ必須。

    unsafe {
        asm!(
            "ldr r0, =0x10020100",
            "ldr r1, =0xe000ed08",
            "str r0, [r1]",
            "ldmia r0, ",
            "msr msp, r0",
            "bx r1",
        );
    };
  • インライン・アセンブラはunsafe{}で囲む。
  • 本体はasm!()で囲まれる。fmt!的な展開が行われるので、アセンブラとして{,}が必要なときは``とエスケープする。
  • 各行を"でくくって書き、コンマでつなげる。
  • 即値は、アセンブラと同様に=0x12345677と書く。前述のように、実際のコードでは、即値は別のアドレスに格納され、PC相対参照のコードが生成されるが、それはアセンブラが行う。

ROMルーチンのmemcpy44()を呼び出す

次の例は引数がある場合。

RP2040は内蔵ROMにサポート・ルーチンが内蔵されていて、それを呼び出すことができる(2.8.3.1. Bootrom Functions)。

次の例では、すこし効率が良いmemcpy44()を呼び出す。

fn memcpy44(to_addr: u32, from_addr: u32, size: u32) {
    //     ldr r0, =ROM_FN_TABLE
    //     ldrh r0, [r0]
    //     ldr r2, =ROM_TABLE_LOOKUP
    //     ldrh r2, [r2]

    //     // Query the bootrom function pointer
    //     ldr r1, =0x3443 // 'C','4' for _memcpy44
    //     blx r2

    //     //uint8_t *_memcpy44(uint32_t *dest, uint32_t *src, uint32_t n)
    //     mov r3, r0
    //     ldr r0, =DST
    //     ldr r1, =SRC
    //     ldr r2, =LEN
    //     blx r3
    unsafe {
        asm!(
            "ldr r0, =0x00000014",
            "ldrh r0, [r0]",
            "ldr r2, =0x00000018",
            "ldrh r2, [r2]",
            "ldr r1, =0x3443",
            "blx r2",
            "mov r3, r0",
            "mov r0, {0}",
            "mov r1, {1}",
            "mov r2, {2}",
            "blx r3",
            in(reg) to_addr,
            in(reg) from_addr,
            in(reg) size,
        );
    };
}
  • ROM_FUNC_TABLEのアドレスが0x0000_00014に格納されているので取り出す。r0に格納する。
  • rom_table_lookup()関数のアドレスが0x00000_0018に格納されているので取り出す。r2に格納する。
  • 探したい関数のコードをデータシートで見つける。memcpy44()の場合は0x3443なのでr1に格納する。覚えやすいようにC(0x43), 4(0x34)とアスキーコード的なニモニックが振られている。
  • rom_table_look()を呼ぶ。つまり、r2にジャンプする。第一引数(ROM_FUNC_TABLE)はr0に、第二引数(関数コード)はr1に格納されている。検索結果が戻り値のr0に格納されている。あとで使うのでr3にコピーしておく。
  • memcpy44()の第一引数(dst)をr0、第二引数(src)をr1、第三引数(size)をr2に格納する。r3(関数のアドレス)にジャンプすることでmemcpy44(dst,src,size)の関数コールが実行される。戻り値があればr0に格納される。

外部から、このインラインアセンブラに値を渡したいとき

  • in(reg) RUSTの変数名とすれば、インラインアセンブラが、そのRUST変数をレジスタに割り当てる。
  • そのレジスタは、順に{0}として、インライン・アセンブラ中で参照できる。fmt!()の変数展開のようなものだ。
  • in(reg)の場合は入力、out(reg)の場合は出力、inout(reg)の場合は入出力。レジスタの寿命がそのように考慮される。

システムレジスタを読む

こういったことをするのに、スタックポインタ(MSP)、プログラムカウンタ(PC)の値が重要になる。

通常であれば、インライン・アセンブラが必要になるが、cortex-mクレートが便利機能を提供してくれているので、それを使えば簡単だ。ただし、PCを読むには、cortex-minline-asmフィーチャーを有効にしておかなければならない。

[dependencies]
cortex-m = { version = "0.7", features = ["inline-asm"] }
    info!("MSP={:08x}", cortex_m::register::msp::read());
    info!("PC={:08x}", cortex_m::register::pc::read());