ネットワーク経由起動(TFTP)の概要
開発中はカーネルを書き換えるたびにストレージへ焼き直すのは手間です。U-Bootはネットワークから直接イメージを取得でき、tftpboot を使うとTFTPサーバ上のファイルを指定アドレスへ転送できます。事前に ipaddr(ボード側IP)と serverip(TFTPサーバIP)を環境変数で設定し、必要なら dhcp で自動取得します。転送したカーネルとDTBを bootm などで起動すれば、ストレージを書き換えずにカーネルを差し替えながら開発できます。ルートファイルシステムをNFSにすればさらに反復が速くなります。
組込みLinuxの開発では、カーネルやデバイスツリーを一日に何度も作り直します。そのたびに、できたイメージをSDカードやeMMCに書き込み直してボードに戻す、という作業を繰り返すのは大変な手間です。書き込みには時間がかかり、カードを抜き差しする物理的な操作も発生します。この反復を劇的に速くする手段が、U-Bootのネットワーク起動機能、とりわけTFTP(Trivial File Transfer Protocol)を使ったイメージ転送です(boot-tftp)。U-BootはEthernet経由でネットワーク上のサーバからファイルを直接メモリへ読み込めるため、ストレージを一切書き換えずに、最新のカーネルで起動できます。開発のテンポを上げるうえで、これを使えるかどうかは生産性に直結します。
TFTPとは何か
TFTPは、その名のとおり「ごく簡素なファイル転送プロトコル」です。普段のファイル転送で使うFTPと違い、ユーザ認証やディレクトリ操作といった機能を持たず、「指定したファイルを読む/書く」という最小限の機能だけを備えています。仕組みが単純なぶん実装が小さくて済むうえ、軽量なUDPという通信方式で動くため、U-Bootのような限られた環境のブートローダに組み込むのに向いています。開発時の使い方はこうです。まずホストPC(開発機)でTFTPサーバを動かし、ビルドしたカーネルやDTBをそのサーバの公開ディレクトリに置きます。次にボード側のU-Bootから、そのサーバ上のファイルをメモリへ転送する、という流れです。サーバとボードを同じネットワークにつないでおけば、あとはコマンド一つでイメージを取り込めます。
ネットワークの設定 ipaddr と serverip
TFTPを使う前に、ネットワークの設定を環境変数で済ませておく必要があります。最低限必要なのは二つで、ボード自身のIPアドレスを表す ipaddr と、TFTPサーバ(ホストPC)のIPアドレスを表す serverip です。たとえば setenv ipaddr 192.168.1.10、setenv serverip 192.168.1.1 のように設定します(boot-envvar)。固定IPを手で設定する代わりに、ネットワーク上のDHCPサーバから自動でアドレスを取得することもできます。その場合は dhcp コマンドを使い、これを実行するとボードはDHCPでIPアドレスを取得し、環境によってはサーバ情報も併せて受け取ります。開発環境にDHCPサーバがあるなら dhcp を、なければ ipaddr と serverip を手動設定する、と使い分けます。設定後、saveenv で保存しておけば、毎回設定し直す手間が省けます。
tftpboot でイメージを取り込む
ネットワークの準備ができたら、tftpboot コマンドでイメージをメモリへ転送します。書式は tftpboot ロード先アドレス ファイル名 で、たとえば tftpboot ${kernel_addr_r} zImage とすれば、サーバ上の zImage がメモリの kernel_addr_r へ転送されます。同様に tftpboot ${fdt_addr_r} board.dtb でデバイスツリーも取り込めます。転送中はコンソールに進捗を示す記号(# が並ぶ表示など)が出て、完了するとファイルサイズと転送先アドレスが報告されます。もし「T T T」のようなタイムアウトを示す表示が続いて転送が進まないときは、ボードとサーバが同じネットワークにつながっているか、ipaddr と serverip が正しいか、サーバ側でTFTPサービスが動いてファイアウォールに阻まれていないか、を順に確認します。ネットワーク起動のトラブルの大半は、この物理接続とアドレス設定のどこかに原因があります。カーネルとDTBの両方を tftpboot で読み込んだら、あとは前のトピックで見た起動コマンド(bootz ${kernel_addr_r} - ${fdt_addr_r} など)でLinuxを起動するだけです。ストレージには一切触れていないので、サーバ上のイメージを差し替えてボードを再起動すれば、何度でも最新版で起動できます。
bootcmd への組み込みとNFSの併用
この一連の手順を毎回手で打つのは面倒なので、開発中は bootcmd 自体をTFTP起動用に書き換えてしまうのが効率的です。setenv bootcmd 'tftpboot ${kernel_addr_r} zImage; tftpboot ${fdt_addr_r} board.dtb; bootz ${kernel_addr_r} - ${fdt_addr_r}' のように設定しておけば、ボードを再起動するだけで自動的にサーバから最新イメージを取得して起動します。このとき saveenv するかどうかは状況しだいで、開発機を固定で使うなら保存しておくと便利ですし、一時的な検証ならRAM上だけにとどめて再起動で元に戻せるようにしておく、という選び方もできます。printenv で現在の bootcmd を確認しながら、必要に応じて切り替えるとよいでしょう。
カーネルをTFTPで取り込めるようになると、次はルートファイルシステムもネットワーク越しに使いたくなります。そこで併用されるのがNFS(Network File System)です。カーネルへ渡す起動引数 bootargs に、ルートファイルシステムをNFS上に置く指定(root=/dev/nfs と、サーバ上のパスやボードのIP設定)を書いておくと、カーネルはストレージではなくネットワーク越しのディレクトリをルートとしてマウントします。こうすると、ホストPC上のディレクトリをそのままボードのルートファイルシステムとして使えるため、ファイルを書き換えればボード側に即座に反映され、アプリの修正と動作確認の反復が一気に速くなります。「カーネルはTFTP、ルートファイルシステムはNFS」という組み合わせは、組込みLinux開発における定番の高速イテレーション環境です。量産段階ではこれらをストレージからの起動に戻して固定しますが、開発中はネットワーク起動を最大限活用することで、書き込み待ちのない快適な開発サイクルを実現できます。