Raspberry Pi で簡単にネットワークブートができるようになったよ，という話

[Raspberry Pi Advent Calendar 2017](https://adventar.org/calendars/2075) 2日目です．

## 結論

SDカードに[`bootcode.bin`](https://github.com/raspberrypi/firmware/raw/next/boot/bootcode.bin) (49kBほど) というファイルを置いておくだけで，全バージョンのRaspberry Piで

* ネットワークブート
* USBストレージブート

ができます．

サーバの設定などは
[Network Boot Your Raspberry Pi - Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/net_tutorial.md)
を参考にしてください．

## 経緯

まずRaspberry Piのブートは，おおまかには3段階からなります．

* 1段目  
Raspberry PiのSoCにはROMが載っており，ここにファームウェアが書き込まれています．  
Raspberry Piに電源が投入されると，まずこのROM内のファームウェアがメモリに読み込まれます．  
読み込まれたコードはGPUにあるVPUというプロセッサによって実行されます．
* 2段目  
ROM内のファームウェアによってSDカードが検出され，  
SDカード内の設定ファイル (`config.txt` や `cmdline.txt`) と，  
ファームウェアがメモリにロードされます．  
このコードもVPUによって実行されます．
* 3段目  
SDカード内のファームウェアがSDカード内のLinuxカーネル等を検出し，メモリにロードします．  
また，同時にCPUの初期化も行われ，LinuxカーネルはCPUによって実行されます．

<br>
「Raspberry Piにネットワークブートの機能を追加したい」となったときに初めに考えられたのは，3段目でネットワークブートを行う方法です．
この方法は，例えばLinuxカーネルのNFS root機能を使うことで実現されます．
NFS rootはカーネルの引数に設定を追加するだけなので，比較的容易に行えますが，CPUを起動するためのファームウェアとLinuxカーネルはSDカードに入れなければなりません． 
これだと，ファームウェアとカーネルをアップデートするたびにSDカードへの書き込みが発生するので，SDカードの寿命が縮んでしまいます．
U-Bootを使う方法もありますが，結局SDカード上にファームウェアが必要なので，アップデートによるSDカードへの負荷は避けられません．

<br>
そこで考えられたのが，2段目でネットワークブートする方法です．
元々のRaspberry Piのファームウェアにはネットワークブート機能がなく，かつRaspberry Piのファームウェアはクローズドソースなので，2段目でネットワークブートするためには，別途VPU向けのファームウェアを書かなければなりません．
VPUの命令セットは非公開なのですが，実は[有志によって解析されており](https://github.com/hermanhermitage/videocoreiv/wiki/VideoCore-IV-Programmers-Manual)，これを基に[オープンソース版のファームウェアが開発されています](https://github.com/christinaa/rpi-open-firmware)．
こちらのファームウェアを使うことでネットワークブートすることは可能なようですが，公式のファームウェアの一部の機能しか対応していなかったり，Raspberry Piの一部のバージョンに対応していなかったりするなど，動作が不安定なところがあるようです．
また，この方法にも，結局アップデート時にSDカード上のファームウェアを書き換える必要があるという欠点があります．

<br>
そこで考えられたのが，1段目でネットワークブートする方法です．
実は，Raspberry Pi 3からROM内のファームウェアに変更が施されており，SDカード無しでネットワークブートやUSBストレージブートなどができるようになっています．
(参考: [Boot flow - Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/bootflow.md))
ROMの内容を変更するのはRaspberry PiおよびそのSoCを開発する組織にしかできないので，ROMのファームウェアは中の人である[Gordon Hollingworth](https://twitter.com/gsholling)を中心としたエンジニアによって開発されたようです．
この方法はSDカードさえ不要になるので画期的に思われましたが，ROM内のファームウェアにバグがあり，LAN内で他のマシンがbroadcast addressにpingし続けないとRaspberry Piがうまくネットワークを認識できないなど，ネットワークブートできない環境がありました．
OTP (one-time programmable) メモリのビットを立ててROMのファームウェアの挙動を制御するなどの対策が取られましたが，ROMの内容は後から変更できないため，バグを完全に取り除くことはできなかったようです．

<br>
ここでどうしようもなくなったので，公式のファームウェア (2段目) にネットワークブート機能が追加されました．
Raspberry Pi 3のROMに書いたコードを，バグを修正して移植したようです．

Raspberry Piを通常の方法で起動させる場合，SDカードには `bootcode.bin` や `fixup.dat`，`start.elf`，`config.txt`，`kernel.img` などのファイルが必要です．
しかし，ネットワークブート機能は `bootcode.bin` に実装されたので，SDカードに`bootcode.bin` だけを入れれば，ネットワークブートができるようになります．
この際，残りのファイルは `bootcode.bin` がTFTPサーバ経由で持ってきてくれます．
さらに，`bootcode.bin` はアーキテクチャ特有の処理しかしないので，動くバージョンがあれば，基本的にそれを使い続けて大丈夫です（多分）．
よって，**`bootcode.bin` 以外のファームウェアや設定ファイルやLinuxカーネル，rootfsなどは全てSDカードでなくサーバ側に置いておくだけで良い** のです．
これで，**全バージョンのRaspberry PiでネットワークブートとUSBストレージブートができるようになり**，アップデートによってSDカードに負荷がかかることもなくなりました．

<br>
サーバ側の設定などは
[Network Boot Your Raspberry Pi - Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/net_tutorial.md)
を参考にしてください．


## 最後に

私は単なるアルバイトで雇用に関わる権限はありませんが紹介だけさせていただきます．
[Idein Inc.では一緒に働ける人を常時募集しています](http://idein.jp/career)．