bare metal という言葉を最近知って、おもしろそうだなあと思ったので Raspberry Pi 上で試してみた。bare metal とは OS レスで直接動かす (つまりリッチなマイコン的に使う) ことのようです。bare metal で動かすだけだと目標が低い感じなので、まず mruby を動かすところまでやろう、という感じです。

yamanekko さんというかたが既にやって日本語で書いているので、それを見てコピペしながらやってみつつという感じです。ちょいちょいうまくいかなかったのでかなりハマった……

ARM クロスコンパイル環境の構築

https://launchpad.net/gcc-arm-embedded にあるバイナリをインストールするのが一番良い。これを入れれば以下にあるような fpu 問題にも悩まない。

大変めんどう。ググるといろいろでてくるけど、今流行りの全部うまいことやってくれる感じのスクリプトがあったので試してみた。

https://github.com/jsnyder/arm-eabi-toolchain

git clone git@github.com:jsnyder/arm-eabi-toolchain.git
cd arm-eabi-toolchain
make install-cross

大変時間がかかる。Makefile を開いて、--enable-languages="c,c++" となってるところから c++ を消したほうがいいっぽい。使わないし…

入るライブラリ

  • arm-none-eabi-
    • gcc
    • binutils (objdump とか)
    • gdb
  • GMP
    • 数値演算ライブラリ
  • MPFR
    • 浮動小数点ライブラリ
  • MPC
    • MPFR ベースのさらに複雑な浮動小数点ライブラリ
  • newlib
    • 組み込み環境用のライブラリ。 libc とかを提供

少し古いバージョンが入る

FPU 問題

最初

arm-none-eabi-ld: error: main.elf uses VFP register arguments, /Users/cho45/arm-cs-tools/arm-none-eabi/lib//libc.a(lib_a-errno.o) does not

とか出て ld が通らなかった。

yamanekko さんのレポジトリにある Makefile だと fpu ライブラリへのパスが書いてあるけど、上のスクリプトでクロスコンパイル環境を構築してもでてこない……

gcc の configure オプションか何かなのかもしれないけど、時間がかかるしむずかしいので諦めた。

よくわからなすぎるのでググったらARM EABIでの浮動小数点演算というが参考になった。

結局 -mfloat-abi=soft にして完全にソフトウェア命令しか出さないようにしたら動くようになった。-mfloat-abi=soft=softfp だと浮動小数点演算が走ったタイミングで死んだりしてよくわからない。

オプションの種類は arm-none-eabi-gcc --target-help で見ることができる。-mfpu=vfp のところもいろいろ種類があるけどよくわからない。

未だ完全には解決してない。

リンカスクリプト

memmap というファイルになってる。面倒だった。間違えるといろいろよくわかないことになる。今回遭遇したのは

  • .ARM.exidx を定義してなかったせいでおこられる
    • C++ 用のセクションらしいけど 特定の関数を使おうとすると必要っぽい
    • 定義の順番がおかしいとできるバイナリがデカくなったり、動かなくなったりする
  • アラインメントがおかしかったせい?で newlib の mALLOc が無限ループする
    • 当然ヒープを実装せず使わないなら再現しないので簡単なLチカだと気付かない

newlib が要求してくるシステムコール類は syscalls.c でモック的に定義されている(コピペしたのものをクリーンアップした)。だいたいはどうでもいい関数だけど、_sbrk が大変重要で、これによってヒープが実現されている。

メモリ割当のマッピングとかはリンカスクリプトで定義したものを vectors.s (アセンブラ) で C 側から見れるようにして、それを使ってヒープとかの実装をしているみたい。

gdb でシミュレーションデバッグ

.gdbinit に書いてあるけど

file main.elf
target sim
load

とすることで、gdb 上でシミュレーションして動かすことができるので、周辺機器が絡まない、とりあえずのデバッグはこれでも十分できる。

ただ、一部の演算、確認したのは % (余剰)で変なエラーがでておかしい。

実際やった手順

mruby なしでLチカ

まず mruby なしでLチカができないとどうしようもないので、まずそこまでできるプロジェクトツリーを作って動くことを確認するまでやった。

https://github.com/cho45/raspberrypi-bare-metal

この過程だけでも上記の通りかなりハマった……

mruby ありでLチカ

次に mruby ありでLチカに挑戦した。yamanekko さんのmruby-rs-led を参考に、Linux の gpio 的な GPIO module gem を実装した。

GPIO のアドレスとかはデータシート記載のものを手動で変換せずにできるだけそのまま書いておきたいと思うので、そうしてる。バスアドレスというのと、物理アドレスというのがあって、データシートにはバスアドレスで書いてあって、しかし実際使うのは物理アドレスなので、変換 (固定値) が必要みたい。

これによって以下のように書けるように (完全にちゃんと動いているか微妙だけど) なった。Ruby にすると一気に簡単になった感じがして落差がウケます。ほぼ Linux 上で適当にラッパー書いて書くのと同じコードになった。うまくラップできれば主要コードは Ruby レベルでテストが書けてよさそう。

pin = 16

GPIO.direction(pin, :low)
bool = false
loop do
	bool = !bool
	GPIO.write(pin, bool)
	10000.times { }
end

しかしここまで動かすのも大変で、何時間もハマった。ここでハマったのは特にリンカスクリプトでアラインメントがおかしかったせいで、mrb_open() がそもそも返ってこないというのがつらかった。

gdb で追ってもなぜか newlib の中で無限ループにになるので意味不明だった。

そしてそれがようやくなおったと思っても mrb_open() の最中に浮動小数点演算もしていて、ここでハングするということが起きてつらかった。最終的には上記の通り、とりあえず常にソフトウェアにしてなんとか動かした。

sleep の実装を書いてないので Ruby 側でビジーループしてる。16ピンにしてるのは、Raspberry Pi の基板上に実装済みの LED だからで、デバッグ用に新規配線をしなくてすむからです。

sleep の実装

さすがにビジーループをRuby側でやるのはダサいので次に sleep を実装した。

system timer と arm timer というのがあるけど、今回は system timer を使った。arm timer はシステムの状態によってクロックが変わるみたい。system timer は独立したクロックを持ってるようだ。

ただ、肝心の system timer clock がよくわからなかった。試行錯誤したかんじだと、1Mhz ぐらいで動いてる。分周比設定とかがなくて謎。要追試

Cレベルではビジーループしてる。うまくCPUスリープさせたいけど、まだよくわかってない。

mruby でハマったところ

mruby ではあまりハマらなかった。mrbgem 作るのは大変簡単になっていておもてなし感がある。

ただ、build_conf.rb で gem を指定せずにビルドすると、あとから gem を指定しても、clean するまで反映されないのが罠ぽかった。深くおってないけど、DISABLE_GEMS が永続されてしまうぽい?

この件は再現させて1行パッチ送ったらマージされたのでたぶん大丈夫です。build_config.rb を書きかえたら .c ソースのリビルドが走るはず…

レポジトリ

これも習作だけど別レポジトリにおいてる

課題点

  • fpu の問題の解決
  • GPIO モジュールの拡張
    • TIMER や割込みなどを使えるように
    • ハードウェア I2C SPI の実装
    • bit banging での I2C 実装
  • JTAG を試す
  • ブートローダーを使うように
    • SDカードいちいち抜き差ししてるとつらいので

感想

mruby は夢が広がる。mruby で OS とか作ったらかっこよさそう。

  1. トップ
  2. tech
  3. mruby を Raspberry Pi 上で bare metal で動かすまで