U-Bootを使ってRasberry Piから自作エミュレータをOSレスで起動してみた

# Raspberry Piと本気で遊ぶ
たまたま達人出版会から出ていた「[BareMetalで遊ぶ Raspberry Pi](http://tatsu-zine.com/books/raspi-bm)」という本を見つけて、当然ジャケ買いしたわけです。で、パラパラと見ていたらU-Bootから起動できるみたいな事が書いてあったので。以前「[AZ/05M（あずにゃん）でU-Bootから起動する何かを作ろう](http://qiita.com/toyoshim/items/3a69cb5ba1642796a384)」という記事を投稿してるわけですが、こいつをちょろっといじるだけでRasPiでもいけるんじゃないかなー、と。

加えて、gyaboさんが非常に有用な記事「[RaspberryPiのVideoCore VI(GPU)を使ってOS無し(ベアメタル)からポリゴン出して遊ぶ](http://qiita.com/gyabo/items/f3a411a63d608d00b384)」を投稿されていて。BIOS時代と違って手軽にBare Metalで描画周りまで自由になる環境ってなかなかないんですよね。そこにRasPiのような数も出ていて、Bootstrapレベルからそこそこのデバッグ環境付きでコード書けて。さらにGPUもある程度自由に触れる。となると、これはかなり興味がそそられます。

というわけで、まずは取っ掛かりにU-Boot applicationを作成します。U-Boot applicationの嬉しいところは、ほぼほぼBare Metalなのですが、キーボード入出力など最低限の機能はU-Boot側のコードを経由して実現できる点です。

# 予備調査
## U-Boot
elinuxのページに[RPi U-Boot](http://elinux.org/RPi_U-Boot)というページがあって、何人かのブランチ含めて対応状況に関する詳しい説明がありました。

ざっくりまとめるとmainlineでだいたい大丈夫。このページの解説ではUSBがハイスピードのみ対応してるため、キーボードとかは動かない、って書いてあったんですが、2017/4/2現在のmasterではロー/フルスピードも動いてます。USB Ethernetも動くとか書いてあるけど、自分の使ってたEther付きUSB HUBのネットワークは認識されてなかった。オンボードなら認識するかもしれないので、あとで試します。

追記：Pi 1 Model B+のオンボードEtherは認識してました。DHCPでアドレス取ってTFTP bootしようとするとこまでは確認したので、たぶんネットワークブートはできそう。

# 対応までの道のり
## 概要
コードはほとんどいじらず、buildルールの変更だけでいけました。動作確認含めて30分もかかってないかも。

## Toolchain
U-Bootの説明ページとかはgcc-arm-linux-gnueabiを使ってるようですが、AZにゃん対応の時にgcc-arm-none-eabiを使ったので同じものを使います。

## Build設定
Pi Zero使ってたので、ターゲットCPUはARM1176JZF-S、ARMv6です。よって、以下のフラグを設定しています。

```
-marm
-march=armv6zk
-mcpu=arm1176jzf-s
```

それとは別にU-Boot applicationとして動作させるためには、次のような設定をU-Bootと揃える必要があります。一部の設定はU-Bootのstubとリンクする際に怒られるのでわかりやすいですが、何も言われずに起動後に落ちる原因となる子もいるので注意。以下のリストは安全側に倒してるので、問題ない子もまざってると思いますが。

```
-mno-thum-interwork
-mabi=aapcs-linux
-msoft-float
-ffunction-sections
-fdata-sections
-ffixed-r9
-ffreestanding
-fstack-usage
-fno-common
-fno-builtin
-fno-stack-protector
-fno-toplevel-reorder
```

このうち`fixed-r9`は最初の起動に失敗した原因でした。あずにゃん機は`fixed-r8`だったんだな。

## Buildしてみる
AZにゃん用のルールを元に少し書き換えた物を用意して、すぐにbuildは通りました。あとで解説するU-Bootからの起動方法に則って試した結果、最初は一行だけ文字を表示した後に画面が崩壊してハング。先程も書いたように`fixed-r*`の設定が違ったためにU-BootのAPIを呼び出した後にABI違うせいでレジスタ壊れて暴走、かな。

で、これだけ直したらモニタ部は起動したんだけど。次に少しハマったのがエミュレータ起動時のディスクイメージの読み込みの失敗。

```c
static char sd_fat = 0;
```

この子が0で初期化されてなかった。たぶんbssが0クリアされてない気がする。問題はこの子だけだったので、起動時チェックで必ず初期化するようにして回避しました。

# 起動方法
## Build
という事で、この成果物を取ってきて遊ぶ方法を書いておきます。Linux限定。

```shell-session
% sudo apt-get install gcc-arm-none-eabi  # for debian and variants
% git clone https://github.com/toyoshim/cp-mega88.git
% cd cp-mega88
% make uboot_raspi
```

RasPiで起動できるSDカードを用意して

- obj.uboot_rpi/cpmeta88.bin
- third_party/u-boot/u-boot.bin
- sdcard.img

をコピー。sdcard.imgはz80packあたりで手に入るCP/Mの起動ディスクイメージです。

で、config.txtを以下のように書き換える。

```:config.txt
kernel=u-biit.bin
```

このままのSDカードを使って起動した場合はU-Bootのpromptが出るので

```
fatload mmc 0:1 0x00100000 /cpmega88.bin
fatload mmc 0:1 0x00200000 /sdcard.img
go 0x00100000
```

でモニタが起動します。モニタ起動後は「b」で起動。「help」でモニタコマンドの一覧が表示されます。毎回このコマンドを打つのが面倒な場合はboot.scrを用意すると楽です。上記のコマンドをboot.scriptという名前でSDカードに保存し、SDカードのトップディレクトリで以下のコマンドを実行します。

```shell-session
% sudo apt-get install u-boot-tools
% mkimage -A arm -O linux -T script -C none -n boot.script -d boot.script boot.scr
```

これで起動時にboot.scrにバイナリとして保存されたboot.scriptの内容が実行されます。こんな感じ。

![001.jpg](https://qiita-image-store.s3.amazonaws.com/0/12843/91474374-e14d-0fa4-3570-95d2866ba69d.jpeg)

# 最後に
ライブラリに依存してないCコードって事で、Bare Metalチックな環境の実験に使われ続けてきたcpmega88。次に活躍するのはWebAssemblyかなー。Ras Pi Bare Metalはたぶん20年越しのプロジェクトasuraの落ち着き先としてもう少し遊ぶつもり。