ラズパイ3でベアメタル - その1:何もしない無限ループプログラム

ラズパイ3を買ってみました。


今回はベアメタルプログラミングで最も簡単な「何もしない無限ループ」のプログラムを作ってみるところまでを、
自分の経験のメモとしてまとめてみました。


なお、Raspberry Pi 3で64bitベアメタル(bare metal)プログラミングを試してみる
本シリーズの目次はコチラです。

0. はじめに

ARMの64ビットCPUに触れてみようとRaspberry Pi 3を買ってみたのですが、
現状のRaspbianは32ビットのモードで動作するため(*1)、64ビットを試してみることができません。


LinuxなどのOS(というよりカーネル)を介さず、CPUが直接実行するコードを書く事を「ベアメタルプログラミング」と言うそう(*2)で、
64ビットCPUの勉強としてベアメタルプログラミングを試してみています。


(*1):
Raspberry Pi 3に実装されているBCM2837というSoC(複数のCPUや周辺機能が一緒になったチップ)に含まれている
Cortex-A53というCPU(のARMv8というアーキテクチャ)は32ビットモード(従来のARMv7アーキテクチャ)と64ビットモードの
どちらでも動くように作られています。


(*2):
Raspberry Piフォーラムの一覧にも「Programming」のセクションに「Bare metal」のフォーラムがあります。

1. ベアメタルプログラミングでは何を作るのか?

ベアメタルプログラミングではRaspberry Piに電源を入れて初めてCPUが解釈するコードから自分で作るわけですが、
単にCPUといってもBroadcom製BCM2837のSoCは、

  • Broadcom VideoCore IV
  • ARM Cortex-A53 (4 Core)

を内蔵しており、電源投入直後はVideoCoreというGPUが動作します。


ただし、VideoCoreの仕様やファームウェア等(bootcode.bin、fixup.dat、start.elf)のソースコードも非公開です。


そのため、一般的にRaspberry Piのベアメタルプログラミングでは、VideoCoreによりセットアップされ、実行が開始されるARM Core用のプログラムを作成します。
そして、VideoCoreによりRAMにロードされるARM Core用のバイナリがkernel*.imgというファイルで、ベアメタルプログラミングではこのバイナリが最終生成物です。

2. コンパイラを用意する

Ubuntu 14.04 (Trusty Tahr)はapt-getでインストールできるようです。


それ以外の環境の場合は、Linaroが公開しているGCCの実行バイナリを以下のURLからダウンロードして使用すると良いです。


例えば、私のx86_64のLinux環境(Debian Jessie)では「gcc-linaro-5.3-2016.02-x86_64_aarch64-linux-gnu.tar.xz」を使用しています。

3. まずは、中身空の無限ループだけのプログラムを書いてみる

C言語で書くと以下のようなイメージです。

int main(void)
{
	while (1) {
		/* 何もしない */
	}

	return 0;
}


ただし、C言語で書いたプログラムをCPUが実行するには、
C言語で書いたプログラムを実行するための準備をしなければならないので、
まずは、CPUが解釈する機械語命令と(基本的には)一対一で対応するアセンブラでプログラムを書きます。


アセンブラで書くと以下のとおりです。

loop:
	b	loop


1行目の「loop:」はラベルで、2行目の「b」はブランチ(分岐)という命令を表し、
オペランド(C言語で言う引数)で指定している「loop」ラベルへ分岐することを示しています。


なお、現在実行中の命令のアドレスはわざわざラベルを用意しなくても、
以下のように書けます。

	b	.

ビルド・実行

アセンブラソースコード「.S」という拡張子で保存し、
以下のようにビルド(アセンブラなのでアセンブル)できます。

[PC]$ aarch64-linux-gnu-as -o loop.img loop.S


5/10リリース時点のRaspbian含め、現状のVideoCoreファームウェアでは
ARM Coreの実行開始時、ARM Coreの4つのCore全てが動き始めてしまいます。


Linuxの場合はカーネル起動時にマルチコアの対応をよしなにやっているかと思うのですが、
ベアメタルプログラミング初期においてマルチコア対応をするのは大変なので、
シングルコアでARM Coreの実行を開始するように修正したファームウェアを以下からダウンロードして使用します。


firmware_armstub.zipの中にファームウェア一式が入っているので、
このZIPをダウンロード・展開し、RaspbianをインストールしたmicroSDFAT32パーティション
展開したファイルを全て上書きコピーすればファームウェアのアップデート完了です。

[PC]$ unzip firmware_armstub.zip
[PC]$ sudo mount /dev/mmcblk0p1 /mnt/tmp
[PC]$ sudo cp firmware_armstub/* /mnt/tmp/

なお、ベアメタルプログラミングにおいてはVideoCore用のファームウェアとARM用の実行バイナリさえあれば良いので、
microSDに単一のFAT32パーティションを作成して上記のZIPの中身をコピーしてもOKです。


また、このファームウェアではFAT32パーティションに「kernel8.img」という名前で配置しておけば、
このバイナリを64ビットモードでARM Coreに実行させてくれるので、
先ほどの「loop.img」を「kernel8.img」という名前でFAT32バーティションへコピーすればmicroSDの準備完了です。


あとは作成したmicroSDRaspberry Pi 3へ挿入し、電源を入れれば作成したプログラムが実行されます。
(といっても、「何もしない」プログラムなので見た目上は何も起こりませんが。)

QEMUで試してみる

なお、作成したバイナリはQEMU上で実行してみることもできます。


UbuntuDebianなどのAPTが使える環境では以下のコマンドでARM用QEMUをインストールし、

[PC]$ sudo apt-get install qemu-system-arm


以下のコマンドで実行できます。

[PC]$ qemu-system-aarch64 -cpu cortex-a57 -M virt -kernel kernel.img
  • 5/14現在、Debian JessieでインストールできるQEMUにはCortex-A53は未実装なためCortex-A57を使用しています
  • また、Raspberry Piのマシンタイプも未実装なので、ARMバーチャルマシンをマシンタイプとして選択しています
    • 今回はCPUとメモリ以外のデバイスは使用しないようなプログラムなので問題ありません

今できていること

GPIOとUART(Mini UART)の制御は簡単な方法で実装してみたものがあり、GitHubで公開しています。

ただし、ARMのGIC(Generic Interrupt Controller)をまだちゃんと理解できていないので、
割り込みは使用できていません。


今できているところまでは、忘れない内にまた記事としてまとめておきたいと思います。

ARMv8について

Cortex-A53で使用できる命令は、Cortex-A53のアーキテクチャであるARMv8のリファレンスマニュアルに記載されています。