2013年03月07日
U-Bootのデバッグ
先日のKMCセミナーでU-Bootの話をしました。その中から一部をピックアップして紹介します。
U-Bootのブートシーケンス
まずは古典的でシンプルなブートシーケンスです。電源投入するとCPUはNORフラッシュ上のU-Bootを実行します。U-BootはDRAMなど必要なハードウェアの初期化を行った後に、なんらかの方法でLinuxカーネルをDRAM上にロードし、制御を移します。Linuxカーネルをロードする元としては、NORフラッシュや、NANDフラッシュ、SDカード、あるいはtftp等のプロトコルでネットワーク経由でロードしたりします。
U-Bootのデバッグを行うには、まずはこの方法で試すのがよいでしょう。KZM-A9-GTボードにはこれが可能なようにNORフラッシュメモリを搭載しています。
最近のブートシーケンスはもっと複雑になっています。その理由はコスト削減のためにNORフラッシュを省略するようになったためです。価格と容量の比率でNORフラッシュよりもNANDフラッシュおよびそれを利用したSDカードやeMMCのほうがずっと安くなりました。最近の廉価な評価ボードではSDカードのスロットだけあって、フラッシュメモリを搭載しないものが増えています。
NORフラッシュはランダムアクセスが可能でプログラムをそこに置いて直接実行することができますが、NANDフラッシュはブロック単位でしかリードできないので、プログラムを直接実行することはできません。NANDフラッシュに格納したプログラムを実行するには、一度RAM上に転送し、RAM上でを実行することになります。通常メインメモリとしてはDRAMが使用されますが、DRAMは最初にDRAMコントローラを初期化しないと使用できません。また、初期化に必要なパラメータはDRAMの種類ごとに異なります。
これらの事情をふまえた上で最近のブートシーケンスを見ていきましょう。
電源投入すると、まずSoC内部のROMが実行されます。この時点で書き込み可能なメモリはSoC内部にあるSRAMだけです。SRAMは初期化が不要です。しかし容量はそれほど大きくありません。64KB程度です。SoC内部のROMはボード上に載っているDRAMの種類、容量等を知りません。これらはボード依存の情報です。そのためSoC内部のROMはNANDフラッシュやSDカード上に格納された「2番目のローダ(Secondary Program Loader: SPL)」をSRAM上にロードし、そこに制御を移します。SRAMはフル機能のU-Bootを格納するにはサイズが足りません。SPLはDRAMを初期化してから、U-BootをDRAM上にロードして、制御を移します。そしてU-BootがLinuxカーネルをDRAM上にロードして制御を移します。
このようにさまざまな制約をクリアするために多段階のローダを使う構造になっているわけです。
SPLはU-Bootのソースツリーからビルドします。最近のU-Bootでは "Falcon mode" という起動高速化のしくみとして、SPLから(フルのU-Bootをスキップして)直接Linuxカーネルを起動するしくみが追加されました。
デバッグの視点でのU-Bootの特徴
- シングルCPU
- シングルタスク
- 割り込み無し
- 仮想メモリを使用しない
- リロケーション
1.から4.まではデバッグが簡単になることなのですが、最後のリロケーションがデバッグするときのわかりにくい要因になります。デバッガでソースコードや変数を表示するときにはアドレスとソース行やシンボルとの対応付けを行う必要がありますが、これが途中から変わるということを意味します。
リロケーション
NORフラッシュ上で起動を開始したU-Bootは各部のクロックやDRAMなど基本的なハードウェアの初期化を行った後は、自分自身のプログラムをDRAM上に転送し、以降はDRAM上で動作します。
プログラム中に絶対アドレスが入っているところはリロケーション情報としてまとめられていて、プログラムをNORフラッシュからDRAM上にコピーした後にリロケーション情報を元にアドレスを更新します。
リロケーションを行う理由は2つあります。
- プログラムの実行を高速化するため。DRAMのアクセスは高速ですが、NORフラッシュはコストや構造の簡略化のために遅いバスにつながっていることが多いです。
- U-Boot自身の機能でNORフラッシュを更新できるようにするため。DRAM上で動いている状態ならば、自分自身が格納されていたNORフラッシュを消して書き直すことができます。
リロケーション前の状態では、data, bssセクションもNORフラッシュ上のアドレスにありため、書き換えることができません。つまりC言語の全てのグローバル変数、スタティック変数は書き換えることができません。SRAMに置かれたスタックエリアだけを使って注意深くプログラムします。
リロケーション後は全てのセクションがリードライト可能になります。U-Bootは自分自身のサイズを考慮して、DRAMの一番後ろに合わせてリロケーションを行います。これは前のほうに最大サイズの連続した空き領域をとるためです。デバッガでソース表示を行うためには、どれだけずらしたか(= relocation offset)をデバッガに教えてあげる必要がありますが、これが定数にならないのが面倒なところです。U-Bootのコードを修正したり、コンパイルオプションを変えるとU-Bootのサイズが変わるので、relocation offsetもそれに応じて変わることになります。