RustでArm Cortex-Mプログラミングをする 2018

この記事は自作OS Advent Calendar 2018およびRust その2 Advent Calendar 2018の記事です。

ベアメタルプログラミングといえばC言語であったが、C言語でプログラミングするのはつらい、ということでその代わりとなる言語としてRustが注目されてきている。
Rust+Armのベアメタルプログラミングに関しては去年のRustアドベントカレンダーにも記事があったほか、自分も何件かすでにブログを書いているが、今年はRust Embeded Working Groupが発足し、資料も充実してきた(参考:This Year in Embedded Rust

Rustでベアメタル

Rust Embeddedチームがすでにドキュメントをつくっているので(執筆中の章も存在するが)、これに従えば誰でもCortex-Mのベアメタルプログラミングできる
The Embedded Rust Book
ベースとなっているのはjaparic氏のブログなので、それの正式版と思ってもらえば良い。
チュートリアルはSTM32F3DISCOVERYをハードウェアと用いていて、秋月電子通商などで日本でも2000円程度で手に入る。
以前、QEMUで実行する方法もブログにしたが、タイマーなどの一部ペリフェラルが動かない他、やはりLEDが手元で光っている方が楽しいと思うので、がっつりとやりたいのなら実機の購入をおすすめする
一応、後述する通り他のcortex-mボードを用いても十分に開発できると思われる。現に僕はSTM社製の他のボードを使っている。

cortex-m-quickstart

では実際にArm Cortex-Mでのベアメタルプログラミングの話に入ろうと思うのだが、
ベアメタルプログラミングするための手順だけであればThe Embedded Rust Bookがよくまとまっているので、それを読め、で終了してしまう。

なので今回はThe Embedded Rust Bookでも用いられているcortex-m-quickstartからサンプルコードを抜粋して大体なにをしているかを見ていくことにする。

hello world

example/hello.rsを見てみよう。いわゆるhello worldである。
hprintln関数はセミホスティングを利用した出力を利用している。セミホスティングとはSVC命令などを利用することでデバッガに対してメッセージを送る機能である(参考:https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/dui0471/f/semihosting/what-is-semihosting)。
なので、UARTなどを利用したものでないため、本来ならデバッガをボードにつなぐ必要がある。が、DISCOVERYボードは基板にデバッガも内蔵しているので簡単に利用できる。

この#[entry]というアトリビュートがついたものが実質的なmain関数となる。ブートコードはどこにあるか、というとこのcortex-m-quickstartが依存しているライブラリの一つ、cortex-m-rtが担っている。
このライブラリはビルド時のリンカスクリプトの生成もしていて、このアトリビュートをつけた関数を展開し、ブートのための処理を行った後呼び出す、という流れになっている。
大まかな仕組みとしてはライブラリに含まれるlink.x.inというスクリプトをベースにbuild.rsがスタックサイズのチェックや必要に応じて他のライブラリのリンカスクリプトを読み込んだりしている。
その他、例外発生時のハンドラの登録用マクロも用意されている。
詳しい仕組みはドキュメントを参照。

このテンプレートが依存しているもう一つのライブラリであるcortex-mライブラリはアセンブラ命令を呼び出すためのラッパやシステムレジスタの抽象化レイヤなどがふくまれている。
インラインのアセンブラはまだ安定版ではないため、対応していない場合、予めコンパイルしてあるアセンブラを関数をextern Cで呼び出している
その際のArmのバージョンごとの違いはbuild.rsが指定されたターゲットを見てどのバイナリをリンクするかで吸収している。

panic

example/panic.rsはpanicを発生させるだけのコードである。no_std環境でもpanicが発生した際の処理を実装しておかなければならない。
これは最近安定化されたfeatureのひとつでもある。
このサンプルコードではpanicハンドラを実装したクレートを読み込むことで動作を決定している。

allocator

example/allocator.rsは自前で動的メモリ確保を実装することでno_std環境下でも動的配列を利用する例である。
基本的に組込みで動的メモリ確保は行わないほうがいいのだが、どうしても必要というケースは出てくる。
これも最近安定化されたfeatureのひとつでもある。
外部クレートで実装されたallocであるが、中身は空き領域をリスト形式で持っておき、要求に応じて空き領域リストを探索して返す、というシンプルなものになっている。
ただし、stdに含まれるvecは使えないので、allocクレート内のvecを使ってあげる必要がある。こちらはまだunstableなfeature。 allocクレートの中にはBoxやArcなど、所有権周りの問題を解決するのにおなじみの構造体も含まれている。

device

マイコンのペリフェラルを使う例。stm32f103xxというクレートを使っているが、これはチップのMMIOのマッピングを記述したSVDファイルからsvd2rustを使って自動生成されたものである。
そのため、他のタイプのボードを使ってもSVDファイルさえ手に入れれば割と簡単に再現できる。
svd2rustはペリフェラルからの割り込みハンドラをリンクするためのcortex-m-rt用のリンカスクリプトもつくっている。
これはsvd2rustの生成したライブラリがCARGO_FEATURE_DEVICEというフラグをオンにすることで、device.xをcortex-m-rtが持つリンカスクリプトをインクルードさせている。

その他の資料

Armではないが、RISC-V向けの記事が今年の自作OS Advent Calendarに上がってる
RustでRISC-V OS自作!はじめの一歩

また、以前も紹介したが、別のアプローチとしてTockという組込みOSが存在しており、そちらのデザインはなかなか工夫されている。
Tock
ただし、こちらは専用ボード向けの実装しかないため、始めるにはハードルが高かったため、自分はまだ手が出ていない。

また、Cortex-Mではないが、Rasberry Pi 3でのチュートリアルも提供されている
Bare Metal Rust Programming on Raspberry Pi 3