RP2040(Raspberry Pi Pico) - Rustでインライン・アセンブラ(ARM/Thumb2)
Written on 2024-06-02
RustのコードからARM(thuumb2)のインライン・アセンブラを扱う方法は、ズバリの情報が少なかった。この記事が一助となれば嬉しい。 日本語の場合はRust by Exampleが一次情報となるだろう。
bootloaderのサブ記事だが、独立させておく。
bootloaderからアプリケーションのコードにジャンプする
前編でも書いたように、ldmia
やmsr
などの特殊な命令を使うので、インライン・アセンブラ必須。
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-m
のinline-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());