とあるエンジニアの備忘log
2014年12月26日金曜日
Linux の NAND の Bad Block 管理まとめ
Linux の MTD/NAND framework で、Bad Block の管理がどうなっているか、まとめてみた。 ### Bad Block と Bad Block Mark について ### NAND チップは、出荷時にある一定割合まで、不良ブロックを含んでいいことになっている。 ベンダーは、不良ブロックには、そのことがわかるように Bad Block Mark (以下 BBMと略す)というマークを付けて出荷する。 ベンダーによって若干仕様にブレがあるが、だいたい各ブロックの先頭ページの冗長領域 (以下 OOB = out of band と略す)の先頭に `0xFF` ではない値が書かれる。 また、消去と書き込みを繰り返すうちに、正常ブロックが不良化することもある。 その時にも BBM をつける。 当然ながら、BBM がついているブロックは使ってはいけない。 また、消去すると BBM も消えてしまうので、消去もしてはいけない(これ非常に重要)。 そのブロックが不良かどうかを調べるために、OOB の BBM を読みにいくと確実なのだが、かなり効率が悪いので、実際には Bad Block Table (以下 BBT と略す)を作って管理している。 各ブロックが不良かどうかを調べるには、理屈的には 3 つの方法があることになる。 - 各ブロックの OOB にある BBM を読む。もっとも効率が悪い。 - NAND Flash 上に構築される BBT (以下 flash based BBT と記す) を読む。 - Linux のメモリ上に構築される BBT (memory BBT と記す) を読む。 memory BBT が1次キャッシュで、 flash based BBT が2次キャッシュみたいなイメージで捉えてもいいかもしれない。 ### flash based BBT について ### flash based BBT は最初の一回だけ構築すればよく、その後は Bad Block が増えるごとに更新されていく。 BBT がどこに置かれるかについては、何パターンかあって、ドライバから指定できる。 チップの先頭に置くか、最後尾に置くかで 2通りある。 (`NAND_BBT_LASTBLOCK` フラグで決まる) NAND パッケージ上では 1つに見えても、内部的には複数のシリコンダイで構成されている場合があり、その場合には、全ダイ分の BBT を一箇所にまとめて置くか、ダイごとにテーブルを分けるかでも 2 通りある。 (`NAND_BBT_PERCHIP` フラグで決まる) トータル 2 x 2 = 4 通りの配置方法があり(もっと言うと任意のブロック上に BBT を置く `NAND_BBT_ABSPAGE` というフラグもある)、しかも BBT にも、メインとミラーがあるので結構ややこしい。 BBT はダイごとにテーブルを分けて、最後尾に置くのが普通なので、2 ダイ構成の NAND Flash の場合、通常以下の様な感じで BBT が置かれる。 |---------------------| | | | | | | | chip0 Data Area | | | | | |---------------------| | chip0 BBT | |---------------------| | | | | | | | chip1 Data Area | | | | | |---------------------| | chip1 BBT | |---------------------| ### memory BBT について ### memory BBT は Linux の起動時(正確に言うと NAND ドライバが `module_init` されたとき)に毎回作る。 (ただし `NAND_SKIP_BBTSCAN` フラグがセットされていると memory BBT は作成しない。) ややこしいので、フローチャートで説明すると以下のようになる。 |------------------------| | NAND_SKIP_BBTSCAN flag |---(ON)---> 何もしない |------------------------| | (OFF) | |----------------------| | NAND_BBT_NO_OOB flag |---(OFF)---> 全ブロック巡回して BBM を読み出し、 memory BBT を構築 |----------------------| | (ON) | |--------------------------| | flash based BBT exists ? |---(YES)---> flash based BBT を読みだして memory BBT を構築 |--------------------------| | (NO) | 全ブロック巡回して BBM を読み、memory BBT を構築して flash based BBT に書き込む。 実際に、とあるブロックがBad Block かどうかを問い合わせる場合には |---------------------| | memory BBT exists ? |---(YES)---> memory BBT を読み出す |---------------------| | (NO) | そのブロックの OOB の BBM を読む memory BBT は、各ブロックにつき 2bit の情報を持っている。 - `BBT_BLOCK_GOOD` (`0x00`): 正常ブロック - `BBT_BLOCK_WORN` (`0x01`): 使用しているうちに、正常ブロックから不良ブロックに変化したもの - `BBT_BLOCK_RESERVED` (`0x2`): flash based BBT が置かれているブロック - `BBT_BLOCK_FACTORY_BAD` (`0x3`): 最初から不良ブロックだったもの ### 実際の関数実装 (BBT 構築編) ### `nand_scan_tail()` 関数は (`NAND_SKIP_BBTSCAN` フラグがセットされていなければ) `chip->scan_bbt` を呼び出して、 memory BBT を作成する。 以下は関数呼び出しの概略 chip>scan_bbt (= nand_default_bbt: overridable) |-- nand_create_badblock_pattern `-- nand_scan_bbt |-- nand_memory_bbt | `-- create_bbt | |-- scan_block_fast | `-- bbt_mark_entry |-- verify_bbt_descr |-- read_abs_bbts | `-- scan_read |-- search_read_bbts | `-- search_bbt | `-- scan_read |-- check_create | |-- create_bbt | |-- read_abs_bbt | | `-- read_bbt | `-- write_bbt | |-- nand_erase_nand | `-- scan_write_bbt |-- mark_bbt_region `-- nand_update_bbt 各関数の詳細 int nand_default_bbt(struct mtd_info *mtd) `chip->scan_bbt` のデフォルト。 `NAND_BBT_USE_FLASH` フラグが立っていれば、flash based BBT を記述する BBT Descriptor (BBT をどこに置くとか、ID文字列とか、BBT の仕様を記述したもの)を メイン (`chip->bbt_td`) とミラー(`chip->bbt_md`)と 2つ分セットする。 `nand_scan_bbt` を呼び出して、BBT を scan する。 static int nand_create_badblock_pattern(struct nand_chip *this) 各ブロックの OOB の BBM を scan するときの、仕様を記述した BBT Descriptor を `chip->badblock_pattern` にセットする。 int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) `chip->bbt_td` がセットされていなければ(`NAND_BBT_USE_FLASH` がオフの時)、 `nand_memory_bbt` を呼び出して、各ブロックの OOB から BBM を読みだす。 セットされていれば、 flash based BBT が存在するか確認し、あればそれを使う。 static inline int nand_memory_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) `create_bbt` の Wrapper。 static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) `chip->badblock_pattern` に基づいて 各ブロックの OOB にアクセスして BBM を読みだし、 memory BBT を作る。 通常は、各ブロックの先頭ページの OOB だけを見るが、 `NAND_BBT_SCAN2NDPAGE` フラグが立っていると、2ページ目も見る。 `NAND_BBT_SCANLASTPAGE` フラグが立っていると、ブロックの最終ページを見る。 OOB に BBM が付いているブロックには、 memory BBT 上では `BBT_BLOCK_FACTORY_BAD` とマークされる。 static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd, loff_t offs, uint8_t *buf, int numpages) `offs` 位置(ページ先頭を指している)から `numpages` ページ分の OOB を調べ、1 つでも BBM が付いていたら `1` を、 正常ブロックなら `0` を、その他のエラーならば、負の値を返す。 `buf` は temporary buffer で、 OOB を格納するのに十分な長さがないといけない。 static inline void bbt_mark_entry(struct nand_chip *chip, int block, uint8_t mark) memory BBT の先頭から `block` ブロック目のエントリーに対して、 `mark` をつける。 static void verify_bbt_descr(struct mtd_info *mtd, struct nand_bbt_descr *bd) BBT descriptor table `bd` の sanity check static void read_abs_bbts(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md) BBT Descriptor に `NAND_BBT_ABSPAGE` フラグが立っているときのみ呼び出される。 `NAND_BBT_VERSION` フラグが立っているときは、 `td->pages[0]` に BBT 位置が指定されているので、 そのページから BBT のバージョンを読みだして、 `td->version[0]` にセットする。 `md` が存在すれば、ミラーに対しても同じことをする。 static int scan_read(struct mtd_info *mtd, uint8_t *buf, loff_t offs, size_t len, struct nand_bbt_descr *td) BBT マーカーを含む領域を `buf` に読み出す。 `td` に `NAND_BBT_NO_OOB` フラグが立っているときは、 `offs` から マーカーの長さ (4 byte) さらにバージョン情報がある場合は、もう 1 byte 読み出す。 `NAND_BBT_NO_OOB` フラグが立っていない場合は、`offs` 位置の1ページ分(MAIN + OOB)を読み出す。 static void search_read_bbts(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md) BBT Descriptor に `NAND_BBT_ABSPAGE` フラグが立っていないときのみ呼び出される。 `search_bbt` の wrapper。 `td` (メイン)、および `md` (ミラー)が存在すれば `md` についても `search_bbt` を呼び出す。 static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td) flash based BBT を探し、見つかると `td->pages[i]` に BBT の先頭のページ番号を格納する。 `NAND_BBT_VERSION` フラグが立っていれば `td->version[i]` にバージョン番号をつめる。 `NAND_BBT_PERCHIP` フラグが立っていれば、チップ単位で BBT を探す。 `NAND_BBT_LASTBLOCK` フラグが立っていれば、最終ブロックから前方に向かって、立っていなければ、先頭ブロックから後方に向かって探す。 int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) `buf` に BBT の マーカー (通常は `'B'`, `'b'`, `'t'`, `'0'` の4文字, ミラーは `'1'`, `'t'`, `'b'`, `'B'` の 4文字) が含まれているかを調べる。 含まれていれば `0` 含まれていなければ `-1` を返す。 static int check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd) flash based BBT の読み出し、更新、新規作成をする。 `td`, `md` ともにまだ存在しない場合は、`create_bbt` を呼び出して、各ブロックの OOB から BBM を読みだして memory BBT を作る。 ただし、 `NAND_BBT_CREATE` フラグが立っていない もしくは `NAND_BBT_CREATE_EMPTY` フラグが立っていない場合は、 memory BBT は全ブロック `BBT_BLOCK_GOOD` になる。 少なくともどちらか存在する場合は、より新しいバージョンを持つ方の flash based BBT を読みだして memory BBT を作る。 int read_abs_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, int chip) flash based BBT を読み出し、memory BBT へ書き込む。 static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num, struct nand_bbt_descr *td, int offs) NAND の `page` ページ位置の flash based BBT を読み出し、memory BBT の `offs` 位置から `num` エントリー分を書き込む。 `NAND_BBT_NO_OOB` フラグが立っている場合は、 先頭ページの最初は BBTマーカーとバージョン情報なので、その部分はスキップする。 `NAND_BBT_NO_OOB` フラグが立っていない場合は、先頭バイトから読む。 flash based BBT の各エントリーのビット数は td->options で指定される。 All 1 が `BBT_BLOCK_GOOD` に対応。 `td->reserved_block_code` に `0` 以外が指定されているときは、 `BBT_BLOCK_RESERVED` に対応。 All 0 が `BBT_BLOCK_FACTORY_BAD`、 それ以外は `BBT_BLOCK_WORN` に対応する。 static int write_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md, int chipsel) memory BBT を flash based BBT へ書き込む。 `NAND_BBT_LASTBLOCK` フラグが立っていない場合は、先頭ブロックから後方に向かって、 立っている場合は、最終ブロックから前方に向かって、bad block ではなく、ミラーでも使われていないブロックを探す。 見つかると、そのブロックを一旦消去し、 `scan_write_bbt` を呼び出して memory BBT の内容を書き込む。 void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) BBT が置かれているブロックに `BBT_BLOCK_RESERVED` マークをつける。 `NAND_BBT_ABSPAGE` フラグが立っていない場合は、 `td->maxblocks` ブロック分、マークをつける。 基本的に memory BBT のみの更新だが、 `td->reserved_block_code` に `0` 以外が指定されていると、 `nand_update_bbt` を呼び出して flash based BBT も更新する。 static int nand_update_bbt(struct mtd_info *mtd, loff_t offs) flash based BBT のバージョンをインクリメントし、 `write_bbt` を呼び出して、更新する。 `chip->md` が存在すれば、ミラーに対しても同じことをする。 `NAND_BBT_PERCHIP` フラグが立っている場合は、 `offs` 位置のブロックが属する BBT のみが更新される。 ### 実際の関数実装 (Bad Block 問い合わせ編) ### mtd_block_isbad `-- mtd->_block_isbad (=nand_block_isbad) `-- nand_block_checkbad |-- chip->block_bad (=nand_block_bad:overridable) `-- nand_isbad_bbt 各関数詳細 int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs) MTD layer の bad block 判定関数。 オフセット位置 `ofs` のブロックが bad block なら `1`、正常ブロックなら `0`、エラーの場合は負の値を返す。 `mtd->_block_isbad` がなければ 0 を返し、存在すれば呼び出す。 int nand_block_isbad(struct mtd_info *mtd, loff_t offs) `mtd_block_isbad` の NAND layer 実装。`nand_block_checkbad` の wrapper。 int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, int allowbbt) memory BBT があればそれを読み、なければ `chip->block_bad` をコールして、直接 NAND chip の OOB を読む。 static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) OOB に直接アクセスして BBM を読み、 `0xFF` でなければ 1 を返す。 (TODO: `NAND_CMD_READOOB` のような低レベルコマンドだけでなく、 `chip->ecc.read_oob` に対応すべき??) int nand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) memory BBT にアクセスして Bad Block なら `1` を、正常ブロックなら `0` を返す。 `allowbbt` が `0` でないならば、 `BBT_BLOCK_RESERVED` (BBT を格納しているブロック) は正常ブロックとして扱い、 `allowbbt` が `1` ならば、 `BBT_BLOCK_RESERVED` は不良ブロックとして扱う。 ### 実際の関数実装 (BBMをつけるとき) ### mtd_block_markbad `-- mtd->_block_markbad (=nand_block_markbad) |-- nand_block_isbad `-- nand_block_markbad_lowlevel |-- nand_erase_nand |-- chip->block_markbad (=nand_default_block_markbad) `-- nand_markbad_bbt |-- bbt_mark_entry `-- nand_update_bbt `-- write_bbt 各関数詳細 int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs) MTD layer 関数。`ofs` の位置のブロックに BBM をつける。 static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) `mtd_block_markbad` の NAND layer 実装。 `nand_block_isbad` を呼び出して、`ofs` が bad block か調べ、すでに bad block なら何もしない。 bad block でないなら `nand_block_markbad_lowlevel` を呼び出して、マークをつける。 static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs) `NAND_BBT_NO_OOB_BBM` フラグが立っていない場合は、いったんそのブロックを消去し、OOB 領域に BBM をつける。 さらに `chip->bbt` が存在すれば `nand_markbad_bbt` を呼び出して、memory BBT と flash based BBT を更新する。 (`chip->bbt` が存在しなくても flash based BBT は更新すべきでは?バグ??) int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) `ofs` 位置のブロックの先頭ページ の OOB に BBM (0x00) をつける。 `NAND_BBT_SCAN2NDPAGE` フラグが立っていると、2ページ目にも BBM をつける。 ただし、`NAND_BBT_SCANLASTPAGE` フラグが立っている場合は、ブロック内最終ページに BBM をつける。 int nand_markbad_bbt(struct mtd_info *mtd, loff_t offs) `bbt_mark_entry` を呼び出して memory BBT に `BBT_BLOCK_WORN` マークをつける。 `NAND_BBT_USE_FLASH` フラグが立っている場合には、`nand_update_bbt` を呼び出して、flash based BBT も更新する。
2014年11月20日木曜日
Google からの贈り物
先月のある日、 Google の Open Source Programs Office というところからメールが来まして。 読んでみると、 オープンソースで優れた仕事をしている人を表彰していて、あなたの仕事がそれに値すると認められました。 ついては、記念品と賞金を送りたい。 というような内容でした。 「お金あげます」という類のスパムは頻繁に来るのですが(特にオープンソースに参加しているとよくスパムが来る)、金額がまっとうな額な上に、 メールの内容が、自分のやったきたことに合致するので、本物のようです。 とりあえず、住所等をフォームから送信して、待つこと数週間。 本当に来ました! 予想外にでかい FeDex の箱を開けると、中から出てきたのは、、、 [![Bahnhof](http://4.bp.blogspot.com/-jHvUJsHxOnQ/VGy9y9GB_tI/AAAAAAAANHU/aobPS-5vNX0/s320/google_blanket.jpg)](http://4.bp.blogspot.com/-jHvUJsHxOnQ/VGy9y9GB_tI/AAAAAAAANHU/aobPS-5vNX0/s1600/google_blanket.jpg) 暖かそうな、ブランケットのようです。 あまりにも実用的な品なので、かえって拍子抜けしてしまいました。(笑) プリペイドのクレジットカードも送られてきました。 賞金があらかじめチャージされていて、その分だけ使えるようです。 ただただ、自分の趣味でパッチを送り続けてきたのですが、このように認めてもらえるのは嬉しいものです。 このブランケットを使って、もっとパッチ送ってこい、という暗黙のメッセージなんでしょうか? これからの季節、夜な夜なキーボードに向かっていると体が冷えるので、ありががたく使わせていただきます。
2014年10月24日金曜日
ELCE 2014 & U-Boot Mini Summit in Düsseldorf
[昨年](/2013/10/elce-2013-u-boot-mini-summit-in.html)に引き続き、今年も [Embedded Linux Conference Europe](http://events.linuxfoundation.org/events/embedded-linux-conference-europe) と [U-Boot Mini Summit](http://www.denx.de/wiki/U-Boot/MiniSummitELCE2014) に行ってきました。 今年の開催地はドイツのデュッセルドルフでした。 最寄り駅。 [![Bahnhof](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvU2vUZ2dgEpz-wuYUUsTvIumWtBxsELBegJ4y75WZvcHSZzTirnBWhdycpE8coBahgIKOAn7QWILl7GxXzM5YwR0PzjEJdQcvl10kM0GG6URs7p9g7Icdqc1w9B5yuEtBmX88MEGnPYk/s320/bahnhof.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvU2vUZ2dgEpz-wuYUUsTvIumWtBxsELBegJ4y75WZvcHSZzTirnBWhdycpE8coBahgIKOAn7QWILl7GxXzM5YwR0PzjEJdQcvl10kM0GG6URs7p9g7Icdqc1w9B5yuEtBmX88MEGnPYk/s1600/bahnhof.jpg) 会場までの道。のどかです。 [![Strasse](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUQH8TMoT8LSkCRlhMiuVHgahULjnx-gKpoS8dtJI12ruY8YBLQ5BnyBlt1XLzbmBGagEdcHgcL3co6xvnkkmcfdxjfpSqPI0FLQU4p9hgUWB0sCjzn3MwV3K07XUmXCaimjm2rKXL-8Q/s320/strasse.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUQH8TMoT8LSkCRlhMiuVHgahULjnx-gKpoS8dtJI12ruY8YBLQ5BnyBlt1XLzbmBGagEdcHgcL3co6xvnkkmcfdxjfpSqPI0FLQU4p9hgUWB0sCjzn3MwV3K07XUmXCaimjm2rKXL-8Q/s1600/strasse.jpg) 会場の Congress Centre Düsseldorf [![CCD](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZQts4lTwctbF1jByHv61IZVZ87B_yfJLE9qBeXYFHwoxMX5s-7avzL4zSL2OUSrAhf93TH7ebQmPpKhgQA8PTlmgOyEQ_lzxA7NoV1GqQTxWqluXG-0GqXHuhp2ptgtqD-AHo9Wx3TOo/s320/ccd.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZQts4lTwctbF1jByHv61IZVZ87B_yfJLE9qBeXYFHwoxMX5s-7avzL4zSL2OUSrAhf93TH7ebQmPpKhgQA8PTlmgOyEQ_lzxA7NoV1GqQTxWqluXG-0GqXHuhp2ptgtqD-AHo9Wx3TOo/s1600/ccd.jpg) [![Willkommen](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDtLsiM2sHt1SFff9dpk1v5BCLJZ3zGcRNubdaywjsXJrrNh2kQ-USOFtg3ZrmeD41vB_wKk3veNz8zkU3h2Voe6du3Q6C3qEi871JjeZGkf9PGejCAMu8Jy1A0UzBx1x8L1cgAExe5rQ/s320/willkommen.jp)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDtLsiM2sHt1SFff9dpk1v5BCLJZ3zGcRNubdaywjsXJrrNh2kQ-USOFtg3ZrmeD41vB_wKk3veNz8zkU3h2Voe6du3Q6C3qEi871JjeZGkf9PGejCAMu8Jy1A0UzBx1x8L1cgAExe5rQ/s1600/willkommen.jpg) メインのホール。もうすぐ始まります。 [![Haupthalle](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPKVf935Z5oz8ohCp0Ig1mSVs1tmbX9UHbkbzrNqLv8vF6qPK-snusFuaMCdpFTkuDA9ne_DXG3pA2QhQeen3MUJErtMSoWUsUZe6nlrI5l0NR3WbWpbeAWlntbFixASSHdLq4TgQaMjg/s320/haupthalle.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPKVf935Z5oz8ohCp0Ig1mSVs1tmbX9UHbkbzrNqLv8vF6qPK-snusFuaMCdpFTkuDA9ne_DXG3pA2QhQeen3MUJErtMSoWUsUZe6nlrI5l0NR3WbWpbeAWlntbFixASSHdLq4TgQaMjg/s1600/haupthalle.jpg) こちらは [U-Boot Mini Summit 2014](http://www.denx.de/wiki/U-Boot/MiniSummitELCE2014) の会場。14:30 からは自分のプレゼンです。ドキドキ。。 [![U-Boot Mini Summit](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilphpMhijvWI1raCipv8tB-qRiKrUWPjPUUy1l3GR60uJvJcqv6vLRbxwYAQtyTE7_yRAKBTI9MGhh2NLpDsdxni9wROcq3ShU1ybPXyYGHWYNN3_Ik9KeZoe5MLmfMmLo6AbNYXwTzs8/s320/uboot_summit2014.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilphpMhijvWI1raCipv8tB-qRiKrUWPjPUUy1l3GR60uJvJcqv6vLRbxwYAQtyTE7_yRAKBTI9MGhh2NLpDsdxni9wROcq3ShU1ybPXyYGHWYNN3_Ik9KeZoe5MLmfMmLo6AbNYXwTzs8/s1600/uboot_summit2014.jpg) [ELCE2014](http://events.linuxfoundation.org/events/embedded-linux-conference-europe) の ARCHIVE ページにプレゼンしているところが載っていた。(なんだかビミョーな表情) [![U-Boot Mini Summit](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0_rAhLrcAWGYxbXr97UfwmSbRe6YI8F_bjpN-OpWFNlIqnbKaa-GUwerqOZa5iqmLOO9VTYl6pv6r7bdZZ6czZOAt_qfaJAbthklSZ2lVlEkx5QLhBLTwXZDkfJXQydGoNE8AUmmxcUc/s320/my_talk.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0_rAhLrcAWGYxbXr97UfwmSbRe6YI8F_bjpN-OpWFNlIqnbKaa-GUwerqOZa5iqmLOO9VTYl6pv6r7bdZZ6czZOAt_qfaJAbthklSZ2lVlEkx5QLhBLTwXZDkfJXQydGoNE8AUmmxcUc/s1600/my_talk.jpg) プレゼンもなんとか無事に終えて、ホッ。 スライドは[こちら](http://www.denx.de/wiki/U-Boot/MiniSummitELCE2014)からダウンロード可能。 最後に集合写真。 [![Group Shot](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_T4UxgK5D21XjTxkrxJGOraU5P1ZBbAq55Qzmem4M7N6oWHSsDpSvxUtMyedKhZ3QscJxBrgwRap_0-fTzISARcMzDrqXE2MF-jK0UrXGVdiQi2NLWiySSIYcksWFIUY1_JfxmMiY290/s320/U-Boot-Mini-Summit-2014.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoos58upKdKkAZoaaFFLL_TJH7W_K7ajNKR-h4_mVvQ_Uy7aogS9ZV1FT6cqifT1rMdcn2KnA6ob6cvv_qVvdPJgpfuN84mx0YirUey0MzEionCMmt8HMHACp4Pmhc9sW8o5qkUBfkFQw/s1600/U-Boot-Mini-Summit-2014.jpg) 夜は Füchschen というビアレストランで飲み会。「地球の歩き方」に載っていたので、結構有名なお店なんでしょうか。 [![Beer Summit](http://3.bp.blogspot.com/-24yopBmKYuA/VEJ99dSgiLI/AAAAAAAAMTE/VqQMGbOw-zM/s320/uboot_beer_summit2014.jp)](http://3.bp.blogspot.com/-24yopBmKYuA/VEJ99dSgiLI/AAAAAAAAMTE/VqQMGbOw-zM/s1600/uboot_beer_summit2014.jpg) 3 日目のステージには Linus が登場。 [![Linus](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMpV0FxTfzMAhF0O2t21PpAnE8foXjLOxNLcx3NSCQSBuypENbIuoL0tWsQPOapKcVldQF59ATgSr9emaNxroRsAMzmuSJXgT9BC4XNd0RYyP5cDAfYa61QVODq9s364i2KxVIKch0xVI/s320/linus_elce2014.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMpV0FxTfzMAhF0O2t21PpAnE8foXjLOxNLcx3NSCQSBuypENbIuoL0tWsQPOapKcVldQF59ATgSr9emaNxroRsAMzmuSJXgT9BC4XNd0RYyP5cDAfYa61QVODq9s364i2KxVIKch0xVI/s1600/linus_elce2014.jpg) 去年(左)は「XL サイズしか残ってません!」と言われてしまったが、今年(右)はちゃんと MサイズのTシャツがもらえました。 [![T Shirt](http://3.bp.blogspot.com/-5v0pDM_PuEE/VEKFiIfyqeI/AAAAAAAAMTw/SWAGetnc7sY/s320/elce_shirts.jpg)](http://3.bp.blogspot.com/-5v0pDM_PuEE/VEKFiIfyqeI/AAAAAAAAMTw/SWAGetnc7sY/s1600/elce_shirts.jpg) U-Boot の発展に貢献したということで、 DENX から素敵な贈り物を頂きました。一応 USBメモリとしても使えます。 [![Gift from DENX](http://1.bp.blogspot.com/-wqV3CcFKuPM/VEWo94kbeQI/AAAAAAAAMUo/eZGI-hKrcYo/s320/denx_usb_memory.jpg)](http://1.bp.blogspot.com/-wqV3CcFKuPM/VEWo94kbeQI/AAAAAAAAMUo/eZGI-hKrcYo/s1600/denx_usb_memory.jpg)
2014年7月15日火曜日
U-Boot と Linux Kernel のメインラインで Zynq を動かす 2014年7月版
本日 U-Boot 2014.07 がリリースされました。 [前回](/2014/05/u-boot-linux-kernel-zynq-201404.html)から U-Boot の方のやり方が少し変わったので、今回もまとめておきます。 具体的には、 v2014.04 で動かなくなっていた DTB を使った U-Boot のブートシーケンスが動くようになっていたり、 煩雑な工程のいくつかが不要になっています。 使用するのは、 - U-Boot 2014.07 - Linux Kernel 3.15 - Zynq ZC706 ボード とします。 ### STEP0: DTC (Device Tree Compiler) の準備 ##### Input Files Required - None ##### Output Files Produced - `dtc`: Device Tree Compiler ##### Task Description U-Boot を Device Tree 付きでビルドするには version 1.4 以降の DTC が必要になる。 ディストリビューションに標準で用意されている DTC では不足するかもしれないのでバージョンを確認。 $ dtc -v Version: DTC 1.3.0 バージョンが 1.4.0 よりも古い場合は、自分でビルドします。 $ git clone git://git.kernel.org/pub/scm/utils/dtc/dtc.git でソースを取ってきて $ make $ make install でビルドとインストールができる。 `$(HOME)/bin` の下に `dtc` が入るので PATH を通しておく。 ### STEP1: U-Boot のビルド ##### Input Files Required - `dtc` - ARM Cross Compiler - `ps7_init.c`: ISE / Vivado で "Export Hardware for SDK" を実行すると出力される - `ps7_init.h`: ISE / Vivado で "Export Hardware for SDK" を実行すると出力される ##### Output Files Produced - `u-boot-dtb.bin`: U-Boot の RAWバイナリと u-boot をコンフィグレーションする DTB を連結したもの - `u-boot-dtb.img`: `u-boot-dtb.bin` に uImage ヘッダーをつけたもの - `spl/u-boot-spl.bin`: U-Boot SPLの RAWバイナリ - `tools/mkimage`: U-Boot で扱うイメージを生成するツール。 ##### Task Description $ git clone git://git.denx.de/u-boot.git $ cd u-boot $ git checkout v2014.07 でソース取得して、v2014.07 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) あとで、Kernel を TFTP でダウンロードしたいので、`include/configs/zynq-common.h` を開いて、適当なところに #define CONFIG_IPADDR 192.168.11.2 #define CONFIG_SERVERIP 192.168.11.1 #define CONFIG_ETHADDR 00:0a:35:00:01:22 の3行を足す。 `CONFIG_IPADDR` は Zynqボードに割り振る IPアドレス、 `CONFIG_SERVERIP` は TFTP サーバーのアドレスに合わせて下さい。 MACアドレスは、(他のネットワーク機器と被らなければ)適当でいい。 TFTP サーバーがなくても、動かすことはできるので、ない人は上記はスキップして下さい。 さらに、 `ps7_init.c`, `ps7_init.h` を U-Boot の `board/xilinx/zynq` ディレクトリにコピーする。 このファイルが、 FSBL の代わりをするための、肝になるファイルです。 あとは $ make zynq_zc70x_config $ make CROSS_COMPILE=arm-linux-gnueabi- DEVICE_TREE=zynq-zc706 のようにして、コンフィグレーションとビルドをする。 もしくは $ make zynq_zc70x_config all CROSS_COMPILE=arm-linux-gnueabi- DEVICE_TREE=zynq-zc706 のように 1行で、コンフィグレーションとビルドを同時にすることもできる。 `ps7_init.c` と `ps7_init.h` が warning を出しますが、気にしなくてもOK。 気になる人は、関数のプロトタイプの引数部に `void` を足してください。 ### STEP2: Linux Kernel のビルド ##### Input Files Required - ARM Cross Compiler ##### Output Files Produced - `arch/arm/boot/zImage`: Kernel Image - `arch/arm/boot/dts/zynq-zc706.dtb`: Kernel をコンフィグレーションする DTB (Device Tree Blob) (従来、 U-Boot から Kernel を起動するときは `arch/arm/boot/uImage` を使っていたが、これは使わない。) ##### Task Description $ git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git $ cd linux $ git checkout v3.15 でソース取得して、v3.15 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) 以下のようにして ARMv7 Multi な設定にする。 $ make ARCH=arm multi_v7_defconfig ここで $ make ARCH=arm menuconfig をして、少々設定をいじる。 Device Drivers ---> Block devices ---> [*] RAM block device support (16384) Default RAM disk size (kbytes) のようにたどり、 `RAM block device support` にチェックを入れ、 `Default RAM disk size` を `16384` に設定する。 もう一つ Device Drivers ---> Character devices ---> [ ] Legacy (BSD) PTY support のようにたどり、`Legacy (BSD) PTY support` のチェックを外す。 あとは $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- でビルド。 ### STEP3: Ramdisk のダウンロード ##### Input Files Required - None ##### Output Files Produced - `arm_ramdisk.image.gz`: Kernel がマウントする init ramdisk (を gzip 圧縮したもの) ##### Task Description [こちら](http://www.wiki.xilinx.com/Build+and+Modify+a+Rootfs) から arm_ramdisk.image.gz をダウンロードする。 ### STEP4: ITB (Image Tree Blob) の生成 ##### Input Files Required - `linux/arch/arm/boot/zImage` - `arm_ramdisk.image.gz` - `linux/arch/arm/boot/dts/zynq-zc706.dtb` - `u-boot/tools/mkimage` カレントディレクトリから見て、上記の配置になっているとする。 ##### Output Files Produced - `fit.itb`: U-Boot から Kernel を起動するためのイメージ ##### Task Description Kernel Image, Ramdisk, DTB (Device Tree Blob) を一つにまとめた ITB というのを作ります。 ITB を作るには、 ITS(Image Tree Source) を記述して、 `mkimage` に食わせます。 以下の内容を `fit.its` というファイルに記述する。 /dts-v1/; / { description = "Kernel, ramdisk and FDT blob"; #address-cells = <1>; images { kernel@1 { description = "Linux Kernel 3.15 configured with multi_v7_defconfig"; data = /incbin/("linux/arch/arm/boot/zImage"); type = "kernel"; arch = "arm"; os = "linux"; compression = "none"; load = <0x00008000>; entry = <0x00008000>; hash@1 { algo = "md5"; }; }; ramdisk@1 { description = "Ramdisk for Zynq"; data = /incbin/("arm_ramdisk.image.gz"); type = "ramdisk"; arch = "arm"; os = "linux"; compression = "gzip"; load = <0x00000000>; entry = <0x00000000>; hash@1 { algo = "sha1"; }; }; fdt@1 { description = "FDT for ZC706"; data = /incbin/("linux/arch/arm/boot/dts/zynq-zc706.dtb"); type = "flat_dt"; arch = "arm"; compression = "none"; hash@1 { algo = "crc32"; }; }; }; configurations { default = "config@1"; config@1 { description = "Zynq ZC706 Configuration"; kernel = "kernel@1"; ramdisk = "ramdisk@1"; fdt = "fdt@1"; }; }; }; あとは以下のようにすれば、`fit.itb` ができる。 $ u-boot/tools/mkimage -f fit.its fit.itb ### STEP5: JTAG (Slave Boot) から U-Boot と Linux を起動する ##### Input Files Required - `u-boot-dtb.bin`: STEP1 で作成したもの - `fit.itb`: STEP4 で作成したもの - `xmd`: ISE / Vivado のインストールディレクトリに入っている - `ps7_init.tcl`: ISE / Vivado から "Export Hardware for SDK" を実行すると出力される - `stub.tcl`: Xilinx のページからダウンロードできる `ug873-design-files.zip` の中に入っている - `fpga.bit`: ISE / Vivado で生成した FPGA bit file (Optional) ##### Task Description `fit.itb` を TFTP の公開ディレクトリに置く。(TFTP 環境のない人はスキップして下さい) Zynq ボードのブートモードの選択スイッチを JTAG に合わせて電源入れる。JTAG でボードと接続し、XMD を開く。 $ xmd XMD のプロンプトから以下を実行する。(FPGA は必要なければダウンロードしなくても良い) XMD% connect arm hw ;# Open JTAG connection XMD% rst -slcr ;# Reset the whole system XMD% fpga -f fpga.bit ;# Download FPGA bit file (Optional) XMD% source ps7_init.tcl XMD% ps7_init ;# Initialize DDR, IO pins, etc. XMD% ps7_post_config ;# Enable level shifter XMD% source stub.tcl ;# start CPU1 XMD% targets 64 ;# connect to CPU0 XMD% dow -data u-boot-dtb.bin 0x04000000 ;# Download u-boot to address 0x04000000 XMD% con 0x04000000 ;# start CPU0 from address 0x04000000 なお、毎回これを打ち込むのも面倒ですので、 `foo.tcl` に書いておきましょう。 XMD% source foo.tcl で XMD から読み込むか、シェルから $ xmd -tcl foo.tcl とすればよいです。 U-Boot のプロンプトが出た後、放っておくと、自動的に TFTPサーバーから `fit.itb` をダウンロードして、 Linux が起動する。 TFTP サーバーがない場合は、`con 0x04000000` の前に XMD% dow -data fit.itb 0x02000000 とすれば、 JTAG 経由で `fit.itb` をダウンロードできるので(時間かかりますが、、) あとは U-Boot のプロンプトから > bootm 2000000 と入力して Linux を起動させる。 ### STEP6: SDカード用のブートイメージを作成する ##### Input Files Required - `u-boot/spl/u-boot-spl.bin`: STEP1 で作成したもの ##### Output Files Produced - `boot.bin` ##### Task Description `bootgen` を使わずに `boot.bin` を作成する方法を紹介します。 u-boot-xlnx のコードを取ってきます。 git clone git://github.com/Xilinx/u-boot-xlnx.git `tools` ディレクトリの下に `zynq-boot-bin.py` という Python スクリプトが 入っているので、これを `~/bin` かどこか適当な PATH にコピーする。 あとは zynq-boot-bin.py -o boot.bin -u u-boot/spl/u-boot-spl.bin とすれば、 `boot.bin` ができます。 BIF ファイルを記述しなくてもいい分、こちらの方が簡単だと思います。 ### Step7: SDカードから U-Boot と Linux Kernel を起動する ##### Input Files Required - `boot.bin`: STEP6 または STEP6B で作成したもの - `u-boot-dtb.img`: STEP1 で作成したもの - `fit.itb`: STEP4 で作成したもの ##### Task Description FAT でフォーマットしたSDカードに `boot.bin`, `u-boot-dtb.img`, `fit.itb` をコピー。 SDカードを Zynq ボードに挿し、ブートモードの選択スイッチを SD カードに合わせて電源入れる。 ### 補足 U-Boot の起動時に以下の Warning メッセージが出ると思いますが、気にしなくていいです。 Warning: Your board does not use generic board. Please read doc/README.generic-board and take action. Boards not upgraded by the late 2014 may break or be removed. これは開発者向けの情報です。次回のリリースでは修正されているはず。 消したい人は `include/configs/zynq-common.h` に #define CONFIG_SYS_GENERIC_BOARD を足す。
2014年6月5日木曜日
Linux で Merge Window の開き方が変わった
LKML (Linux Kernel Mailing List) を watch していて、 まだ Linux 3.15 がリリースされていないのに、 やたらと Pull Request が飛んでいるなあ、と思っていたらすでに Merge Window が開いていた。 Linus のこのアナウンスメールです。 [https://lkml.org/lkml/2014/6/1/264](https://lkml.org/lkml/2014/6/1/264) 要約するとこういう話。(MW = Merge Window) ----------------------------------------------------------------------------------- 先週 rc7 をリリースして、本来は今日 3.15 をリリースするつもりだったけど、 ちょっとした修正があって、目論見に反して rc8 をリリースすることになってしまった。 rc8 をリリースしたくなかった本当の理由というのは、娘の学校が 2週間後に終わるので、 それに合わせてバカンスに出かけようとしていたため。 (今日 3.15 をリリースして、 MW を開いていたら、 MW が閉じるのと同時にバカンスに 出発できる、という目論見だった。) ネットがあるから、バカンス中に MW はできるが、できればやりたくない。 かといって、 3.15 をリリースするには、もう少し時間を置く必要がある。 そういうわけで、新しい試みとして、リリースの1週間前に MW を開いてみよう。 リリースの最後の1週間は、おかしなことが起こっていないかの確認期間みたいなものだから、 次のリリースの MW と重なっていたとしてもうまくいうだろう。 MW の最初の1週間は、 3.16 用の変更は 'next' branch へ Pull することになるが、 Subsystem のメンテナーはすでによく branch を使いこなしているから、混乱する人もいないだろう。 ----------------------------------------------------------------------------------- 補足入れつつ、かなり雰囲気で訳してしまったが、だいたいこんな感じだと思います。 図にするとこういうことだと思う。 #### これまでのやり方 #### Mainline には 'master' branch のみがある。 3.x のリリースから 2週間 Merge Window が開いた後、 rc1 リリースがされる。 ----o----------------o---------------------------------o----------------o---(master)---> 3.14-rc8 3.14 3.15-rc1 3.15-rc2 |---- 1 week ----|------------- 2 week ------------|---- 1 week ----| |<-------- [Merge Window] ------->| #### 今回のやり方 #### 3.15-rc8 から 'next' branch が分岐し、新しい機能はこちらにマージされる。 一方、 3.15 のリリースに向けた最後の修正は 'master' branch の方に入る。 3.15 のリリース後に、'next' branch は 'master' branch へとマージされ、 さらにもう 1週間の MW が続いたあと、 3.16-rc1 がリリースされる。 -----(next)------o--------------o----------------o---(master)---> / / 3.16-rc1 3.16-rc2 ----o----(master)----o 3.15-rc8 3.15 |---- 1 week ----|---- 1 week ----|---- 1 week ----| |<-------- [Merge Window] ------->|
2014年5月28日水曜日
Device Tree アクセス関数まとめ (DTC & U-Boot)
Device Tree Compiler や U-Boot に入っている Device Tree アクセス関数をまとめました。 ### `_fdt_nodename_eq` (fdt_ro.c:17) static int _fdt_nodename_eq(const void *fdt, int offset, const char *s, int len) DTB `fdt` の DT structure の先頭から `offset` の位置の文字列(`0` または `@` で終端されている)と 文字列 `s` (長さ `len`) を比較し、 一致すれば `1` を、一致しなければ `0` を返す。 ### `fdt_string` (fdt_ro.c:78) const char *fdt_string(const void *fdt, int stroffset) DTB `fdt` の DT strings の先頭から `stroffset` の位置の文字列へのポインタを返す。 ### `_fdt_string_eq` (fdt_ro.c:83) static int _fdt_string_eq(const void *fdt, int stroffset, const char *s, int len) DTB `fdt` の DT strings の先頭から `stroffset` の位置の文字列と、 文字列 `s` (長さ `len`) を比較する。 長さも、文字列も一致すれば `1` を、それ以外は `0` を返す。 `strcmp` と論理が逆なので注意する。 ### `_nextprop` (fdt_ro.c:108) static int _nextprop(const void *fdt, int offset) DTB `fdt` の DT structure の先頭から `offset` の位置のタグを調べ、プロパティならば `offset` を返す。 それ以外は負のエラーコードを返す。 ### `fdt_subnode_offset_namelen` (fdt_ro.c:132) int fdt_subnode_offset_namelen(const void *fdt, int offset, const char *name, int namelen) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない)のノードから見て、 1つ下の階層のサブノードのうち、ノード名 `name` (長さ `namelen`) のものを探す。 見つかれば、そのサブノードのオフセット位置を返し、見つからなかったり、エラーの場合は負のエラーコードを返す。 ### `fdt_subnode_offset` (fdt_ro.c:151) int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭をさしていないといけない)のノードから見て、 1つ下の階層のサブノードのうち、ノード名 `name` のものを探す。 見つかれば、そのサブノードのオフセット位置を返し、見つからなかったり、エラーの場合は負のエラーコードを返す。 ### `fdt_path_offset` (fdt_ro.c:157) int fdt_path_offset(const void *fdt, const char *path) DTB `fdt` から `path` のパスのノードを探し、そのオフセット位置を返す。 見つからない場合や、エラーの場合は負のエラーコードを返す。 `path` が `'/'` で始まらない場合はエイリアスとみなされる。 ### `fdt_get_name` (fdt_ro.c:201) const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) DTB `fdt` の DT structure の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない) のノードの名前を返す。 `len` に `NULL` 以外が与えられていると、ノード名の長さを詰めて返す。 エラーの場合は、`NULL` を返し、 `len` には負のエラーコードを詰める。 ### `fdt_first_property_offset` (fdt_ro.c:221) int fdt_first_property_offset(const void *fdt, int nodeoffset) DTB `fdt` の DT structure の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない) の最初のプロパティ位置のオフセットを返す。 見つからないときや、エラーの場合は負のエラーコードを返す。 ### `fdt_get_property_by_offset` (fdt_ro.c:239) const struct fdt_property *fdt_get_property_by_offset(const void *fdt, int offset, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置がプロパティ先頭かを確認し、 プロパティならば、そのポインタを返し、 `lenp` (に `NULL` 以外が与えられていれば)にプロパティ値の長さを詰める。 プロパティでなければ、`NULL` を返し、`lenp` には エラーコードを詰める。 ### `fdt_get_property_namelen` (fdt_ro.c:260) const struct fdt_property *fdt_get_property_namelen(const void *fdt, int offset, const char *name, int namelen, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ名が `name` (長さ `namelen`)のものを探し、見つかれば、そのプロパティへのポインタを返し、 `lenp` (に`NULL` 以外が与えられていれば)にプロパティ値の長さを詰める。 見つからない場合やエラーの場合は、`NULL` を返し、`lenp` にエラーコードを詰める。 ### `fdt_get_property` (fdt_ro.c:284) const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, const char *name, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ名が `name` のものを探し、見つかれば、そのプロパティへのポインタを返し、 `lenp` (に`NULL` 以外が与えられていれば)にプロパティ値の長さを詰める。 見つからない場合やエラーの場合は、`NULL` を返し、`lenp` にエラーコードを詰める。 ### `fdt_getprop_namelen` (fdt_ro.c:251) const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, const char *name, int namelen, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ名が `name` (長さ `namelen`)のものを探し、見つかれば、そのプロパティの値へのポインタを返し、 `lenp` (に`NULL` 以外が与えられていれば)にプロパティ値の長さを詰める。 見つからない場合やエラーの場合は、`NULL` を返し、`lenp` にエラーコードを詰める ### `fdt_getprop_by_offset` (fdt_ro.c:265) const void *fdt_getprop_by_offset(const void *fdt, int offset, const char **namep, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置 (プロパティ先頭を指していないといけない) のプロパティの値へのポインタを返す。 `namep` に `NULL` 以外が与えられていれば、プロパティ名を詰め、 `lenp` に `NULL` が与えられていれば、プロパティ値の長さを詰める。 エラーのときは、 `NULL` を返し、 `lenp` に (`NULL`以外が与えられていれば、エラーコードを詰める) ### `fdt_getprop` (fdt_ro.c:276) const void *fdt_getprop(const void *fdt, int nodeoffset, const char *name, int *lenp) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ名が `name` のものを探し、見つかれば、そのプロパティの値へのポインタを返し、 `lenp` (に`NULL` 以外が与えられていれば)にプロパティ値の長さを詰める。 見つからない場合やエラーの場合は、`NULL` を返し、`lenp` にエラーコードを詰める ### `fdt_get_phandle (fdt_ro.c:282)` uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ "phandle" の値を返す。 "phandle" が見つからない場合は、 "linux,phandle" の値を返す。 見つからない場合は `0` を返す。 ### `fdt_get_alias_namelen` (fdt_ro.c:299) const char *fdt_get_alias_namelen(const void *fdt, const char *name, int namelen) ノード `name` の別名(エイリアス)を文字列として返す。 エイリアスは `"/aliases"` ノードに格納されているので、そのノードからプロパティ名 `name` (長さ `namelen`) のものを探し、そのプロパティ値を返す。 見つからない場合は `NULL` を返す。 ### `fdt_supernode_atdepth_offset` (fdt_ro.c:368) int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, int supernodedepth, int *nodedepth) DTB `fdt` の DT structure の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない)のノード の親(直接の親とは限らない)のうち、 `supernodedepth` の深さのもののオフセットを返す。 見つからない場合は、負のエラーコードを返す。 `nodedepth` に `NULL` 以外が与えられていれば、自身の階層の深さを詰める。 ### `fdt_node_depth` (fdt_ro.c:404) int fdt_node_depth(const void *fdt, int nodeoffset) DTB `fdt` の DT structure の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない)のノード の深さを返す。 エラーの場合は、負のエラーコードを返す。 ### `int fdt_parent_offset(const void *fdt, int nodeoffset)` (fdt_ro.c:416) int fdt_parent_offset(const void *fdt, int nodeoffset) DTB `fdt` の DT structure の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない)のノード の直接の親ノードのオフセット位置を返す。 エラーの場合は、負のエラーコードを返す。 ### `fdt_node_offset_by_prop_value` (fdt_ro.c:425) int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, const char *propname, const void *propval, int proplen) DTB `fdt` の DT structure 領域の `startoffset` 位置から検索開始し、 プロパティ名 `propname` と プロパティ値 `propval` の組み合わせを持つノードが見つかると そのノードへのオフセットを返す。 見つからない場合やエラーの場合は、負のエラーコードを返す。 ### `fdt_node_offset_by_phandle` (fdt_ro.c:452) int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) DTB `fdt` の DT structure の先頭から探索し、phandle `phadle` を持つノードを返す。 見つからない場合や、エラーの場合は、府のエラー番号を返す。nodeoffset` 位置 (ノード先頭を指していないといけない)のノード の直接の親ノードのオフセット位置を返す。 ### `fdt_stringlist_contains` (rdt_ro.c:477) int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) 1つまたはそれ以上の文字列の連結 `strlist` (長さ `listlen`) 中に、文字列 `str` が含まれていれば `1`を、それ以外は `0` を返す。 ### `fdt_node_check_compatible` (rdt_ro.c:570) int fdt_node_check_compatible(const void *fdt, int nodeoffset, const char *compatible) DTB `fdt` の DT structure の先頭から `offset` 位置 (ノード先頭を指していないといけない) のノードから `"compatible"` という名前のプロパティを探し、その値中に文字列 `compatible` があれば(compatible であれば)、`0` を返す。 compatible でない場合は、 `1` を返す。 ノードが `"compatible"` というプロパティを持たない場合や、その他エラーの場合は、負のエラーコードを返す。 ### `fdt_node_offset_by_compatible` (fdt_ro.c:585) int fdt_node_offset_by_compatible(const void *fdt, int startoffset, const char *compatible) DTB `fdt` の DT structure 領域の `startoffset` 位置から検索開始し、 `compatible` に対して compatible なノードを返す。 見つからなかったり、エラーの場合は、負のエラーコードを返す。 ### `fdt_offset_ptr` (fdt.c:77) const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) DTB `fdt` の DT structure の先頭から `offset` バイト目の位置のポインタを返す。 DT structure 領域をオーバーランしないかチェックするため、アクセスする長さを `len` に与える。 ### `fdt_next_tag` (fdt.c:93) uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) DTB `fdt` の DT structure の先頭から `startoffset` バイト目にあるタグを返す。 戻り値は `FDT_BEGIN_NODE`, `FDT_END_NODE`, `FDT_PROP`, `FDT_END`, `FDT_NOP`。 `FDT_END` は DT structure の最後に達したか、エラーを意味する。 次のタグの位置を `nextoffset` に詰める。エラー終了の場合は `nextoffset` にエラーコードを詰める。 ### `_fdt_check_node_offset` (fdt.c:143) int _fdt_check_node_offset(const void *fdt, int offset) DTB `fdt` の DT structure の先頭から `offset` バイト目がノードの先頭かをチェックする。 ノード先頭であれば、次のタグの `offset` 値を返し、それ以外は `-FDT_ERR_BADOFFSET` を返す。 ### `_fdt_check_prop_offset` (fdt.c:152) int _fdt_check_prop_offset(const void *fdt, int offset) DTB `fdt` の DT structure の先頭から `offset` バイト目がプロパティの先頭かをチェックする。 プロパティの先頭であれば、次のタグの `offset` 値を返し、それ以外は `-FDT_ERR_BADOFFSET` を返す。 ### `fdt_next_node` (fdt.c:120) int fdt_next_node(const void *fdt, int offset, int *depth) DTB `fdt` の DT structure の先頭から `offset` バイト目 (ノードを指していないといけない)から探索を開始し、次のノードを探す。 見つかれば、そのノードのオフセット位置を返す。 `depth` に `NULL` 以外が与えられていれば、階層を降りたり昇ったりするたびに、 `depth` を増減させる。 これは、繰り返し探索するときに、現在の階層の深さを記憶するのに使われる。 `depth` が負になるとそれ以上探索しない。 DT structure の終端に達した時や、見つからないとき、エラーの時は負のエラーコードを返す。 ### `fdt_first_subnode` (fdt.c:160) int fdt_first_subnode(const void *fdt, int offset) DTB `fdt` の DT structure の先頭から `offset` バイト目 (ノードを指していないといけない)から探索を開始し、最初のサブノードを探す。 見つかれば、そのノードのオフセットを位置を返す。見つからないときは、 `-FDT_ERR_NOTFOUND' を返す。 ### `fdt_next_subnode` (fdt.c:171) int fdt_next_subnode(const void *fdt, int offset) DTB `fdt` の DT structure の先頭から `offset` バイト目 (ノードを指していないといけない)から探索を開始し、次のサブノードを探す。 見つかれば、そのノードのオフセットを位置を返す。見つからないときは、 `-FDT_ERR_NOTFOUND' を返す。 ### `_fdt_find_string` (fdt.c:229) const char *_fdt_find_string(const char *strtab, int tabsize, const char *s) 1つまたはそれ以上の文字列の連結 `strtab` (長さ `tabsize`) 中に、文字列 `s` が含まれているか検索する。 見つかると、そのポインタ位置を返す。見つからない場合は `NULL` を返す。 ### `_fdt_blocks_misordered` (fdt_rw.c:58) static int _fdt_blocks_misordered(const void *fdt, int mem_rsv_size, int struct_size) DTB `fdt` の構造を再構築する必要があるか判定する。 FDB の構造は常に Mermory Reserve Map, DT structure, DT strings の順に並んでいる必要がある。 `mem_rsv_size` には Memory Reserve Map のサイズ、`struct_size` には DT structure のサイズを渡す。 Mermory Reserve Map が DT structure 領域にオーバーラップしないか、 DT structuret が DT strings 領域にオーバーラップしないか、さらには DT strings の最後が DTB 全体のサイズを超えないかをチェックする。 再構築の必要があれば、`1`、それ以外は `0` を返す。 ### `_fdt_splice` (fdt_rw.c:97) _fdt_splice(void *fdt, void *splicepoint, int oldlen, int newlen) DTB `fdt` のポインタ位置 `splicepoint` から始まる、長さ `oldlen` 分のデータを、長さ `newlen` に変更するために、隙間を拡張したり、詰めたりする。 成功すると `0`、失敗すると負のエラーコードを返す。 ### `_fdt_splice_struct` (fdt_rw.c:123) static int _fdt_splice_struct(void *fdt, void *p, int oldlen, int newlen) DTB `fdt` のポインタ位置 `splicepoint` から始まる、長さ `oldlen` 分のデータを、長さ `newlen` に変更するために、隙間を拡張したり、詰めたりする。 さらに、DT structuret のサイズと、DT strings へのオフセットのヘッダー情報を更新する。 成功すると `0`、失敗すると負のエラーコードを返す。 ### `_fdt_splice_string` (fdt_rw.c:137) static int _fdt_splice_string(void *fdt, int newlen) DTB `fdt` の DT strings の末尾に、長さ `newlen` 分のスペースを確保し、 DT strings サイズのヘッダー情報を更新する。 成功すると `0`、失敗すると負のエラーコードを返す。 ### `_fdt_find_add_string` (fdt_rw.c:150) static int _fdt_find_add_string(void *fdt, const char *s) DTB `fdt` の DT strings 領域を検索し、文字列 `s` が見つかれば、そこへのオフセットを返す。 見つからない場合は、DT strings 領域の末尾に追加し、オフセットを返す。 失敗すると負のエラーコードを返す。 ### `_fdt_resize_property` (fdt_rw.c:205) static int _fdt_resize_property(void *fdt, int nodeoffset, const char *name, int len, struct fdt_property **prop) DTB `fdt` の DT structuret の先頭から `nodeoffset` 位置 (ノード先頭を指していないといけない) のノードからプロパティ名が `name` のものを探し、そのプロパティの長さを `len` にリサイズし、 `prop` にそのプロパティのポインタを返す。 成功すれば `0`、失敗すれば負のエラーコードを返す。 ### `_fdt_add_property` (fdt_rw.c:223) static int _fdt_add_property(void *fdt, int nodeoffset, const char *name, int len, struct fdt_property **prop) DTB `fdt` の DT structuret の先頭から `nodeoffset` 位置(ノード先頭を指していないといけない) のノードに新しいプロパティ (プロパティ名 `name`、プロパティ値の長さ `len`)を追加する。 同名のプロパティがすでに存在しているかはチェックしない。 追加したプロパティへのポインタを `prop` に詰めて返す。 ### `fdt_setprop` (fdt_rw.c:274) int fdt_setprop(void *fdt, int nodeoffset, const char *name, const void *val, int len) DTB `fdt` の DT structuret の先頭から `nodeoffset` 位置(ノード先頭を指していないといけない) のノードの `name` というプロパティの値を `val` (長さ `len`) に書き変える。 `name` という名前のプロパティがまだ存在していない場合は新しく追加する。 成功すれば `0` を返し、失敗すれば負のエラーコードを返す。 ### `fdt_add_subnode_namelen` (fdt_rw.c:334) int fdt_add_subnode_namelen(void *fdt, int parentoffset, const char *name, int namelen) DTB `fdt` の DT structuret の先頭から `parentoffset` 位置(ノード先頭を指していないといけない)のノードに 名前 `name` (長さ `namelen`) のサブノードを追加する。 サブノードは、親ノードのプロパティの直後に追加される。 すでに同名のサブノードが存在する場合は失敗する。 成功すると、追加されたサブノードへのオフセットを返す。失敗すると、負のエラーコードを返す。 ### `fdt_add_subnode` (fdt_rw.c:375) int fdt_add_subnode(void *fdt, int parentoffset, const char *name) DTB `fdt` の DT structuret の先頭から `parentoffset` 位置(ノード先頭を指していないといけない)のノードに 名前 `name` のサブノードを追加する。 サブノードは、親ノードのプロパティの直後に追加される。 すでに同名のサブノードが存在する場合は失敗する。 成功すると、追加されたサブノードへのオフセットを返す。失敗すると、負のエラーコードを返す。 ### `_fdt_packblocks` (fdt_rw.c:394) static void _fdt_packblocks(const char *old, char *new, int mem_rsv_size, int struct_size) アドレス `old` 上の DTB をアドレス `new` 上に再構築する。 新しい DTB の Memory Reserve Map サイズは `mem_rsv_size` に、 DT structuret サイズは `struct_size` となる。 ### `fdt_open_into` (fdt_rw.c:416) int fdt_open_into(const void *fdt, void *buf, int bufsize) DTB `fdt` をバッファ `buf` (サイズ `bufsize`) に読み出す。 通常は、そのままコピーされるだけだが、必要があるときには `_fdt_packblocks` 関数を呼び出して、リフォーマットする。 ### `fdt_pack` (fdt_rw.c:480) int fdt_pack(void *fdt) DTB `fdt` の余分な Memory Reserve Map 領域を詰めて、再パッキングする。
Device Tree アクセス関数まとめ (Linux Kernel)
Linux Kernel の Device Tree 関連の関数をまとめました。 関数名は `of_` で始まり (OF = Open Firmware)、 `drivers/of/` ディレクトリ以下で定義されています。 ### `of_n_addr_cells` (base.c:53) int of_n_addr_cells(struct device_node *np) ノード `np` の一つ上の親から上流に向かって探索し、 `#address-cells` プロパティが見つかるとその値を返す。 見つからない場合は、 `OF_ROOT_NODE_ADDR_CELLS_DEFAULT` を返す。 ### `of_n_size_cells` (base.c:69) int of_n_size_cells(struct device_node *np) ノード `np` の一つ上の親から上流に向かって探索し、 `#size-cells` プロパティが見つかるとその値を返す。 見つからない場合は、 `OF_ROOT_NODE_SIZE_CELLS_DEFAULT` を返す。 ### `of_node_get` (base.c:100) struct device_node *of_node_get(struct device_node *node) ノード `np` の参照カウンタを 1増やす。 ### `of_find_property` (base.c:328) struct property *of_find_property(const struct device_node *np, const char *name, int *lenp) ノード `np` から `name` という名前のプロパティを探し、そのプロパティ構造体へのポインタを返す。 見つからなければ、`NULL` を返す。 `lenp` が与えられていれば、プロパティ値の長さを詰めて返す。 ### `of_get_property` (base.c:383) const void *of_get_property(const struct device_node *np, const char *name, int *lenp) ノード `np` から `name` という名前のプロパティを探し、そのプロパティ値へのポインタを返す。 該当するプロパティが見つからなければ `NULL` を返す。 `lenp` が与えられていれば、プロパティ値の長さを詰めて返す。 ### `of_device_is_compatible` (base.c:566) static int __of_device_is_compatible(const struct device_node *device, const char *compat) ノード `np` がデバイス名 `compat` に対して compatible かどうかを調べる。 compatible なら 1、それ以外は 0 を返す。 ノード `np` の "compatible" という名前のプロパティの値に、文字列 `compat` が含まれるかどうかで判断される。 ### `of_get_parent` (base.c:656) struct device_node *of_get_parent(const struct device_node *node) ノード `node` の親のノードを返す。 ### `of_find_compatible_node` (base.c:870) struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible) 全ノード中の `from` 以降から `compatible` に対して compatible なノードを探索する。 見つかれば、そのノードへのポインタを返す。 見つからない場合は、`NULL` を返す。 なお、`from` に `NULL` を渡した場合は、全ノードを探索する。 `from` は通常、前回の探索の続きを実行するときに用いられる。 `type` に `NULL` 以外が与えられていれば、 device_type に対してもマッチングが行われる。 (`type` と `compatible` のAND条件。) `type` は Device Source Tree 中で `device_type` プロパティによって指定される。 ### `of_match_node` (base.c:954) const struct of_device_id *of_match_node(const struct of_device_id *matches, const struct device_node *node) マッチングテーブル(の配列の先頭) `match` を探索し、ノード `node` に対して compatible かどうかを調べる。 compatible ならば、マッチングテーブルのエントリーを返し、 compatible でないなら `NULL` を返す。 ### `of_find_matching_node_and_match` (base.c:981) struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match) 全ノード中の `from` 以降から、マッチングテーブル `matches` に対して、 compatible なノードを探索する。 見つかると、マッチした マッチングテーブルのエントリーを `match` に詰め、ノードを返す。 見つからない場合は、 `NULL` を返す。 なお、`from` に `NULL` を渡した場合は、全ノードを探索する。 `from` は通常、前回の探索の続きを実行するときに用いられる。 ### `of_find_node_by_phandle` (base.c:1041) struct device_node *of_find_node_by_phandle(phandle handle) 全ノードの中から、phandle 番号 `handle` を持つノードを返す。 phandle は 1から始まる整数である。 見つからない場合は `NULL` を返す。 ### `of_find_property_value_of_size` (base.c:1101) static void *of_find_property_value_of_size(const struct device_node *np, const char *propname, u32 len) ノード `np` から `propname` という名前のプロパティを探し、そのプロパティ値へのポインタを返す。 その際、プロパティ値の長さチェックが行われる。 プロパティ値の長さが、 `len` に満たない場合は `-EOVERFLOW` を返す。 ### `of_property_read_u32_array` (base.c:1228) int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz) ノード `np` から `propname` という名前のプロパティを探し、プロパティ値を `sz * 4` byte 分、 `out_value` へコピーする。 プロパティ値の長さが `sz * 4` byte に満たない場合は、コピーは行われず、 `-EOVERFLOW` を返す。 ### `of_property_read_u32` (of.h:692) static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) ノード `np` から `propname` という名前のプロパティを探し、プロパティ値の先頭 4 byte を `out_value` へコピーする。 エラーが起きると、非ゼロを返す。 ### `of_property_read_string_index` (base.c:1319) int of_property_read_string_index(struct device_node *np, const char *propname, int index, const char **output) ノード `np` の `propname` という名前のプロパティの値から、`index` 番目の文字列へのポインタを `output` に詰めて返す。 成功すると `0` を返す。 例えば、Device Tree Source で node1 { prop1 = "foo", "bar"; } という記述になっていたとする。 `of_property_read_string_index(node1, "prop1", 1, output)` は `output` に `"bar"` をセットする。 ### `of_property_match_string` (base.c:1356) int of_property_match_string(struct device_node *np, const char *propname, const char *string) ノード `np` から `propname` という名前のプロパティを探し、そのプロパティ値のリスト中から文字列 `string` を検索する。 見つかった場合、リスト中の index を返す。 例えば、Device Tree Source の記述が clock-name = "foo", "bar"; のようであったとする。 その場合、 `of_property_match_string(np, "clock_name", "foo")` は `0` を `of_property_match_string(np, "clock_name", "bar")` は `1` を返す。 `of_property_match_string(np, "clock_name", "baz")` は `-ENODATA` (見つからない)を返す。 ### `of_parse_phandle_with_args` (base.c:1602) int of_parse_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name, int index, struct of_phandle_args *out_args) ノード `np` から `list_name` という名前のプロパティを探し、そのプロパティ値 (phandle のリストになっている) の中から、`index` 番目の phandle の情報を、 `out_args` に詰めて返す。 エラーが起きると、非ゼロを返す。 `out_args->np` には指している phandle のノードを、`out_args->args_count` には引数の個数、`out_args->args[]` には引数を詰める。 例えば、 Device Tree Source の記述が以下のようになっていたとする。 クロック情報を受け渡しするときの典型的な書き方である。 clk1: node1 { #clock-cells = <2>; } clk2: node2 { #clock-cells = <1>; } node3 { clocks = <&clk1 5 6>, <&clk2 7> } この時、 `of_parse_phandle_with_args(node3, "clocks", "#clock-cells", 0, out_args);` は `out_args` に `clk1` の情報を詰めて返す。 out_args->np = node1; out_args->args_count = 2; out_args->args[0] = 5; out_args->args[1] = 6; といった具合。 `of_parse_phandle_with_args(node3, "clocks", "#clock-cells", 1, out_args);` は `out_args` に `clk2` の情報を詰めて返す。 ### `of_count_phandle_with_args` (base.c:1606) int of_count_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name) ノード `np` の `list_name` という名前のプロパティに、 phandle とその引数の組みが 何組含まれているかを返す。 ### `of_bus_default_count_cells` (address.c:50) static void of_bus_default_count_cells(struct device_node *dev, int *addrc, int *sizec) ノード `dev` の `#address-cells` および `#size-cells` の値をそれぞれ `addrc` と `sizec` に詰めて返す。 ### `of_translate_address` (address.c:555) u64 of_translate_address(struct device_node *dev, const __be32 *in_addr) ノード `dev` の アドレス `addr` を CPU View のアドレス空間に変換する。 ノードを上流に向かって探索し、`ranges` プロパティに記載されている変換ルールに従って、アドレス変換を行う。 この変換はルートノードに達するまで再帰的に行われる。 親ノードは必ず `range` プロパティを持っていなくてはならないが、値は空でもよい。 `range` プロパティの値が空の場合は 1:1変換が行われる。 ### `of_get_address` (address.c:586) const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags) ノード `dev` の `index` 番目のアドレスとサイズ (通常は `reg` というプロパティの格納されている。PCIバスなどは異なる)を返す。 `size` が与えられていれば サイズを詰める。 `flags` が与えられていれば、通常バスの場合は `IORESOURCE_MEM` を詰める。 アドレスへのポインタを返す。 ### `of_address_to_resource` (address.c:669) int of_address_to_resource(struct device_node *dev, int index, struct resource *r) ノード `dev` の `index` 番目のメモリリソース情報を `r` に詰めて返す。 具体的には、`reg` プロパティと親ノードの `range` プロパティによって CPU View に変換された物理アドレスが `r->start` と `r->end` にセットされる。 `reg-names` プロパティを持つ場合は、その値が `r->name` にセットされ、持たない場合は `dev->full_name` がセットされる。 ### `of_iomap` (address.c:714) void __iomem *of_iomap(struct device_node *np, int index) ノード `np` の `index` 番目の物理アドレスを取得し、 `ioremap` を行い、仮想アドレスを返す。
Device Tree 入門
Device Tree というのは、ハードウェアの詳細を記述したデータ構造体です。 元々は PowerPC Sybsystem から始まったようなのですが、すでに ARM Linux は DeviceTree 一色になってしまっています。 そのため Device Tree を知らないと、 SoC の移植はおろか、ドライバの開発もできない。 そこで、 Device Tree の初歩についてまとめてみることにします。 ただし、私自身が初心者ですので、難しいことは説明できませんし、間違っている部分もあるかもしれませんが、ご了承ください。 ARM のことしかわかりませんので、 ARM を対象として書くことにします。 ### 何故に DeviceTree か? ### より Generic な OS を記述するためです。 ハードウェアを差分を吸収するのがドライバの役目なのですが、勘違いしてはいけないのは、ドライバは「ハードウェアの制御の仕方」を記述するものであって、 デバイスのベースアドレスや、クロック、割り込み番号といった「ハードウェアのプロパティ」を記述するものではありません。 たとえば 8250 とレジスタ互換の UART はいろいろな SoC に搭載されていますが、ベースアドレスや割り込み番号は SoC ごとに違うでしょう。 1つの SoC に同じハードウェア IP が複数個載っていて、ベースアドレスだけ違う、ということもあります。 したがって、ドライバをより汎用的に記述するには、ハードウェアのプロパティは別の場所に記述しておいて、ドライバの初期化時に引き渡す必要があります。 以前は、ハードウェアのプロパティはボードファイル (ARM でいうと `arch/arm/mach-*/` 以下のファイル)に記述していて、 platform_driver を経由してドライバに渡すというのがよく行われていました。 現在では、新しいボードファイルの追加は推奨されず、代わりに Device Tree に記述します。 つまり、ハードウェアの細かい情報を Kernel 内にハードコーディングするのではなくて、 Device Tree という別のデータ構造に押し出すということです。 これによって Kernel がよい汎用的な記述になります。 さらに ARM Linux はより汎用的な方向に向かっています。マルチプラットフォームです。 (`CONFIG_ARCH_MULTIPLATFORM` を選択する) 例えば `arch/arm/configs/multi_v7_defconfig` でコンフィグレーションすると、さまざまな SoC で動く Kernel ができます。 再コンパイルすることなく、同一の Kernel イメージで OMAP や Tegra や Zynq が動くということです。 今後、新しい SoC に移植する場合には、専用のコンフィグレーションではなくて、マルチプラットフォームを使うべきです。 ### Device Tree の記述の仕方 ### Deviece Tree Source (以下 DTS) を記述し、Device Tree Compiler (以下 DTC) でコンパイルすると、 Device Tree Blob (以下 DTB) というバイナリーができます。 この DTB をブート時に Kernel に渡します。 DTS それ自体は非常にシンプルな言語で、以下のような感じで記述します。 /dts-v1/; / { node1 { a-string-property = "A string"; a-string-list-property = "first string", "second string"; a-byte-data-property = [01 23 34 56]; child-node1 { first-child-property; second-child-property = <1>; a-string-property = "Hello, world"; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */ child-node1 { }; }; }; 階層的にノードがあって、各ノードにプロパティ(とその値)を記述します。 XML みたいな構造のものと思っていいでしょう。 コンパイルの仕方ですが、上記のようなソースを `foo.dts` に書いて $ dtc -O dtb -o foo.dtb foo.dts とすると、 `foo.dtb` (DTB) ができます。 また、以下のようにして DTB から DTS へ戻すこともできます。 $ fdtdump foo.dtb DTC の公式リポジトリは `git://git.kernel.org/pub/scm/utils/dtc/dtc.git` です。 (以前は `git://jdl.com/software/dtc.git` だったが、こちらはもう更新されていないので注意!) ただし、 Kernel のビルド時には `scripts/dtc` に入っているものが使われますので、なくてもできます。 DTS の書き方ですが、[Device Tree Usage](http://www.devicetree.org/Device_Tree_Usage) のサイトがわかりやすいと思いますので、そちらで基本的な書き方を勉強するとよいです。 ### Device Tree の渡し方 ### Device Tree 以前は、 ATAGS という構造体にいろんな情報を格納して、Kernel の起動時に渡していました。 今でも ATAGS はサポートされているものの、より細かい記述のできる Device Tree に置き換わっています。 Kernel のエントリーポイントにおける レジスタは以下のようになっている必要があります。 | ATAGS | Device Tree ---|---------------------------------|---------------------------- r0 | 0 | 0 r1 | Machine type number | don't care r2 | Physical address of ATAGS | Physical address of DTB ### DTB の構造 ### DTS の記述の仕方は上記のとおりなのですが、DTB がどんな構造になっているのかを見てみます。 DTB は大きく見ると、以下のような構造になっています。 |----------------------------| | device-tree header | |----------------------------| | memory reserve map | |----------------------------| | | | device-tree structure | | | |----------------------------| | | | device-tree strings | | | |----------------------------| 前述の `foo.dtb` をダンプしてみると以下のようになった。 00000000 d0 0d fe ed 00 00 01 df 00 00 00 38 00 00 01 54 |...........8...T| 00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............| 00000020 00 00 00 8b 00 00 01 1c 00 00 00 00 00 00 00 00 |................| 00000030 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 |................| 00000040 00 00 00 01 6e 6f 64 65 31 00 00 00 00 00 00 03 |....node1.......| 00000050 00 00 00 09 00 00 00 00 41 20 73 74 72 69 6e 67 |........A string| 00000060 00 00 00 00 00 00 00 03 00 00 00 1b 00 00 00 12 |................| 00000070 66 69 72 73 74 20 73 74 72 69 6e 67 00 73 65 63 |first string.sec| 00000080 6f 6e 64 20 73 74 72 69 6e 67 00 00 00 00 00 03 |ond string......| 00000090 00 00 00 04 00 00 00 29 01 23 34 56 00 00 00 01 |.......).#4V....| 000000a0 63 68 69 6c 64 2d 6e 6f 64 65 31 00 00 00 00 03 |child-node1.....| 000000b0 00 00 00 00 00 00 00 3e 00 00 00 03 00 00 00 04 |.......>........| 000000c0 00 00 00 53 00 00 00 01 00 00 00 03 00 00 00 0d |...S............| 000000d0 00 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |....Hello, world| 000000e0 00 00 00 00 00 00 00 02 00 00 00 01 63 68 69 6c |............chil| 000000f0 64 2d 6e 6f 64 65 32 00 00 00 00 02 00 00 00 02 |d-node2.........| 00000100 00 00 00 01 6e 6f 64 65 32 00 00 00 00 00 00 03 |....node2.......| 00000110 00 00 00 00 00 00 00 69 00 00 00 03 00 00 00 10 |.......i........| 00000120 00 00 00 7b 00 00 00 01 00 00 00 02 00 00 00 03 |...{............| 00000130 00 00 00 04 00 00 00 01 63 68 69 6c 64 2d 6e 6f |........child-no| 00000140 64 65 31 00 00 00 00 02 00 00 00 02 00 00 00 02 |de1.............| 00000150 00 00 00 09 61 2d 73 74 72 69 6e 67 2d 70 72 6f |....a-string-pro| 00000160 70 65 72 74 79 00 61 2d 73 74 72 69 6e 67 2d 6c |perty.a-string-l| 00000170 69 73 74 2d 70 72 6f 70 65 72 74 79 00 61 2d 62 |ist-property.a-b| 00000180 79 74 65 2d 64 61 74 61 2d 70 72 6f 70 65 72 74 |yte-data-propert| 00000190 79 00 66 69 72 73 74 2d 63 68 69 6c 64 2d 70 72 |y.first-child-pr| 000001a0 6f 70 65 72 74 79 00 73 65 63 6f 6e 64 2d 63 68 |operty.second-ch| 000001b0 69 6c 64 2d 70 72 6f 70 65 72 74 79 00 61 6e 2d |ild-property.an-| 000001c0 65 6d 70 74 79 2d 70 72 6f 70 65 72 74 79 00 61 |empty-property.a| 000001d0 2d 63 65 6c 6c 2d 70 72 6f 70 65 72 74 79 00 |-cell-property.| 000001df 以下では、各領域ごとに見ていこうと思う。 #### Header #### 先頭から 40 byte (0x28 byte) 分が Header 領域である。 各ワードの意味は以下の通りです。 +0 +4 +8 +C 00000000 magic totalsize off_dt_struct off_dt_string 00000010 off_mem_rsvmap version last_comp_version boot_cpuid_phys 00000020 size_dt_strings size_dt_struct - `magic`: 常に `FDT_MAGIC` (= `0xd00dfeed`) - `totalsize`: この DTB のサイズ - `off_dt_struct`: DT structure の開始位置 - `off_dt_strings`: DT strings の開始位置 - `offset_to`: Memory Reserve Map の開始位置 - `version`: Version。現在は 17 - `last_comp_version`: 互換性のある最後のバージョン - `boot_cpuid_phys`: ブートさせるCPUのID (使い方よくわかりませんが、たぶん気にしなくてOK) - `size_dt_strings`: DT strings のサイズ - `size_dt_struct`: DT structure のサイズ #### Memory Reserve Map #### Kernel が書き潰してはいけない、メモリの予約領域を表した配列。 struct fdt_reserve_entry { fdt64_t address; fdt64_t size; }; のように 64 bit 長の物理アドレスとサイズのペアで記述され、エントリーはいくつでも持つことができる。 `size` が 0 のエントリーが終了を意味する。 例えば、 DTB 自身や InitRamdisk が置かれているメモリ領域は Kernel に書き潰されないように、Memory Reserve Map に記述する必要がある。 上記の `foo.dtb` の例では 0x28 ~ 0x37 がこの領域に相当するが、単に 0 埋めされているだけである。(つまり予約領域なし) 実際のところ、 Memory Reserve Map は、DTC よりもブートローダーによって記録されることが多い。 コンパイル段階では DTB や InitRamdisk をメモリ上のどこに置くか決まっていないため。 通常はブートローダーが DTB や InitRamdisk を適切な物理メモリ上に置いたあと、 Memory Reserve Map を更新する。 #### DT structure #### ノードやプロパティの構造をバイナリ化した領域。 (ただしプロパティの名前だけは DT structure ではなくて、 DT strings に記録される) データ構造をパースできるように、ノードの開始、終了、プロパティなどタグが定義されている。 #define FDT_BEGIN_NODE 0x1 /* Start node: full name */ #define FDT_END_NODE 0x2 /* End node */ #define FDT_PROP 0x3 /* Property: name off, size, content */ #define FDT_NOP 0x4 /* nop */ #define FDT_END 0x9 上記の `foo.dtb` の例では、 `0x00000038` 移行が DT structure に相当します。 各アドレスのデータが何を意味しているのか、データ構造をインデントを入れてわかりやすく表現すると以下のようになる。 00000038 FDT_BEGIN_NODE 0000003c node_name = NULL (/) 00000040 FDT_BEGIN_NODE 00000044 node_name = "node1" 0000004c FDT_PROP 00000050 data_length = 9 00000054 nameoff = 0 00000058 data = "A string" 00000064 FDT_PROP 00000068 data_length = 0x1b 0000006c nameoff = 0x12 00000070 data = "first string", "second string" 0000008c FDT_PROP 00000090 data_length = 4 00000094 nameoff = 0x29 00000098 data = 0x01, 0x23, 0x23, 0x56 0000009c FDT_BEGIN_NODE 000000a0 node_name = "child-node1" 000000ac FDT_PROP 000000b0 data_length = 0 000000b4 nameoff = 3e 000000b8 FDT_PROP 000000bc data_length = 4 000000c0 nameoff = 0x53 000000c4 data = 0x00000001 000000c8 FDT_PROP 000000cc data_length = 0xd 000000d0 nameoff = 0 000000d4 data = "Hello,world" 000000e4 FDT_END_NODE 000000e8 FDT_BEGIN_NODE 000000ec node_name = "child-node2" 000000f8 FDT_END_NODE 000000fc FDT_END_NODE 00000100 FDT_BEGIN_NODE 00000104 node_name = "node2" ...... このように、DTS の構造をそっくりそのままバイナリに変換したものが DTB となっています。 ノードやプロパティの出現順もそのままで、非常に簡単な変換アルゴリズムになっていることがわかると思います。 その他、いくつかの特徴をまとめておきます。 - データの切れ目は常に 4バイト境界 データが 4バイトに満たない場合はパディングされる。 - ビッグエンディアン - 先頭からなめないと、構文解析できない 例えば、途中から読み始めて `0x00000001` というデータを見つけても、ノード先頭(`FDT_BEGIN_NODE`)を表しているのか、プロパティの値なのか判別できないため。 - データの頻繁な更新に向かない 隙間なくデータが配置されているため、途中に新しいノードやプロパティを挿入したり、より長いプロパティ値に書きかえるのが苦手。 DTB を更新する場合には、後続のデータを必要サイズ分後ろにずらしてから、該当箇所を書きかえる必要があるため、メモリコピーが発生する。 - データの型情報は記録されない `"Hello,world"` のような文字列や `<1>` のような 32 bit 値などはあくまで DTS 上での表現である。 DTB に変換されるときに、いずれも単なるバイトストリームとして記録されるので、 DTB だけからはそれが文字列を表しているのか、数字なのかはわからない。 どう解釈するかは、 Kernel 側が知っている。 プロパティ名が `compatible` なら値は文字列だろうし、 `reg` なら値はアドレス値だろうから、そもそも型を記録しなくても困らない。 #### DT strings #### プロパティ名の文字列を格納している領域。 どうして、ノード名は DT structure の方に埋め込まれているのに、 プロパティ名は DT strings に格納し、 DT structure にはプロパティ名へのポインタを格納しているのでしょうか? おそらく、プロパティ名には、`compatible`, `reg`, `interrupts` のように同じ名前のものが繰り返し出現するので、 同じ文字列は 1回だけ DT strings に記録し、それへのポインタを持つ方がサイズが小さくなるからでしょう。 ### phandle ### Device Tree Source は階層的な構造を取りますが、何に基づいて階層化するかというと、基本的に CPU から見たアドレス空間です。 もうちょっと具体的にいうと、バスの構造に基づいて階層化して記述するのが普通です。 ですので、バス構造に基づいた親子関係(例えば 「I2Cコントローラー」と「I2Cバスにぶら下がっている EEPROM」)は Device Tree の構造で表すことができます。 ところが、バスの構造に現れない親子関係はうまく表現できません。例えば、 - 割り込みコントローラ(親)とその割り込みを使うデバイス(子) - クロックを供給するブロック(親)とそのクロックを使うデバイス(子) といった関係です。 例として以下のようなソースを見てみます。 /dts-v1/; / { amba { Label0: interrupt-controller { }; Label1: pll { }; FooDevice { clocks = <&Label1 100 200>; interrupt-parent = <&Label0 10>; }; }; external_bus { BarDevice { interrupt-parent = <&Label0 5>; }; }; }; `amba` と `external_bus` はバスを、 `interrupt-controller`, `pll`, `FooDevice`, `BarDevice` はデバイスを表していると思って下さい。 ノードの階層構造から、 `interrupt-controller`, `pll`, `FooDevice` は AMBAバスに、 `BarDevice` は外部バスにぶら下がっている といったことは読み取れます。 割り込みの関係、例えば `FooDevice` と `BarDevice` は `interrupt-controller` という割り込みコントローラにつながっている、 といったことは phandle という仕組みを使って記述します。 割り込みコントローラーが複数あることもありうるので、ちゃんと指定してあげないと、どれに繋がっているかわからないのです。 まず、親となる `interrupt-controller` になんでもいいのでラベルをつける。 ここでは `Label0: `としたが、実際はもうちょっとわかりやすいラベルをつけます。 子である `FooDevice` と `BarDevice` からは `<&Label0>` といった感じで親を指定する。 これを phandle といいます。通常は、いくつかの引数も合わせて指定します。 例えば、割り込みの場合は、つながっている割り込みコントローラの他に、割り込み番号が何番か、 といった情報も必要なので、 `10` とか `5` とかはそういったパラメータのつもりです。 同様にクロックの関係も、 親である `pll` ノードに `Label1: `というラベルをつけ、クロックを使う `FooDevice` には `<&Label1>` で指定する。 こういう風に記述すると、いったい何が起きるのかを見るために、いったんコンパイルして、それをダンプします。 $ dtc -O dtb -o foo.dtb foo.dts && fdtdump foo.dtb 以下のように表示された。 /dts-v1/; // magic: 0xd00dfeed // totalsize: 0x16a (362) // off_dt_struct: 0x38 // off_dt_strings: 0x144 // off_mem_rsvmap: 0x28 // version: 17 // last_comp_version: 16 // boot_cpuid_phys: 0x0 // size_dt_strings: 0x26 // size_dt_struct: 0x10c / { amba { interrupt-controller { linux,phandle = <0x00000002>; phandle = <0x00000002>; }; pll { linux,phandle = <0x00000001>; phandle = <0x00000001>; }; FooDevice { clocks = <0x00000001 0x00000008 0x00000002>; interrupt-parent = <0x00000002 0x00000001>; }; }; external_bus { BarDevice { interrupt-parent = <0x00000002 0x00000002>; }; }; }; `interrupt-controller` と `pll` ノードに、勝手にプロパティが追加されているのに気づいたでしょうか? これが phandle の正体です。phandle というのは単なる整数なのです。 Device Tree Compiler は `<&Label>` といった記述を見つけると、指し示された親に `linux,phandle"` と `plandle` というプロパティを追加し、1 から始まる整数を割り当てます。 そして、子の `<&Label>` の記述をその整数に置き換えます。 `linux,phandle` または `phandle` の値を元に検索すれば、所望の親を見つけられるというわけです。 phandle は `1` から割り振るというのは味噌です。 `< >` という記号は 32bit幅の unsigned integer を表すのに使います。 phandle を探す関数は、 phandle を見つけられない場合は `0` を返すようになっています。 割り込みコントローラを指すのに `interrupt-parent` というプロパティ名を使いますよとか、クロックを指すのには `clocks` というプロパティ名を使いますよ、とかいうのはもう一段上のレベルの取り決めです。 それぞれのプロパティ名をどう解釈するかはデバイスドライバの実装の話ですが、いちいちソースコードを読むのも大変なので、 Linux の `Documentation/devicetree/bindings` の下のドキュメントを読むとたいてい書いてあります。 なんで `linux,plandle` と `plandle` と2つも追加するのかは、詳しくは知らないのですが、後方互換性とかの問題だと思います。 一応 Device Tree というのは Open Firmware という規格で決まっているらしく、Linux 独自のものではないです。 `linux,phandle` みたいに `linux,` で始まるのは Linux の拡張機能で、最初は `linux,plandle` を使っていたけど、 途中でそれが Open Firmware 本家に取り込まれて `plandle` を使うようになったという事情だと想像します。 (間違っていたらすいません。。)
2014年5月27日火曜日
LinuxCon Japan 2014
5月20-22日に LinuxCon Japan に行ってきました。 [![Welcome Board](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnDSE3YSGlVd92Y1Jy3qIN4-f8H-rtNjf3zHLLehaJKp60Cf97irXzB1PCU7FJD3lcAtIIYpU7ldZVSPybHiK62jZ28NxpdtMqLHNErrkmD7Q7CX9Mq56SMBQnkSCHLBu4iyU3if9LtWg/s320/lcj2014_board.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnDSE3YSGlVd92Y1Jy3qIN4-f8H-rtNjf3zHLLehaJKp60Cf97irXzB1PCU7FJD3lcAtIIYpU7ldZVSPybHiK62jZ28NxpdtMqLHNErrkmD7Q7CX9Mq56SMBQnkSCHLBu4iyU3if9LtWg/s1600/lcj2014_board.jpg)
[![Linus](http://2.bp.blogspot.com/-PS8zFY4Lm6c/U4KCVMzxQxI/AAAAAAAAKlM/QnoL2icWPzE/s320/lcj2014.jpg)](http://2.bp.blogspot.com/-PS8zFY4Lm6c/U4KCVMzxQxI/AAAAAAAAKlM/QnoL2icWPzE/s1600/lcj2014.jpg) 2日目最後のステージに Linus が登場。 会場が一斉に写真を取り出すも、「カメラは後にしてくれ」と言っているところ。
[![T-shirt](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgromlCrySVZIibIKkhvzsN2mIPOE-WahzuVSN5X2_4s19mGWoJp9sgPJwBrLZgU2d0YmwtRFM56NxOTyKdrLT-_-qTnibuixlJXNm-HygWnB2jtI53bR-kohZS7IB-2XOoxFrey_XoOsg/s320/lcj2014_tshirt.jpg)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1e2YaUhEUWCpTcfAvfGqK130rwo0CpFOMMYFm_yMBt1609wybYQ0xNAw1wOYzSwkcoXnUbA2tGDHEUGwwEXTYi5ycJCkwPi_i9YvrCapa35t1OWmpSAXGyqZ0dRbBrriIQmrenukhyEU/s1600/lcj2014_tshirt.jpg) クールなメッセージ入りの Tシャツ。 今回は日本人向け Mサイズがもらえたのでよかった。 [![U-Boot mini-mini-beer summit](http://3.bp.blogspot.com/-MTwKQ9qrExM/U4KCreXG2DI/AAAAAAAAKlw/DocyhdH2hHU/s320/uboot_summit.jpg)](http://3.bp.blogspot.com/-MTwKQ9qrExM/U4KCreXG2DI/AAAAAAAAKlw/DocyhdH2hHU/s1600/uboot_summit.jpg) U-Boot の開発者と再会。 屋形船での飲み会。
2014年5月14日水曜日
sed で遊ぶ
それなりに実用的だと思われる例を書き留めておきます。 ほとんど、 Linux Kernel のコーディングスタイル絡みですが。。 #### 2 行以上連続する空行を 1 行の空行にまとめる #### /^$/ { N /^\n$/D } `N` は次の行をパターンスペースに読み込む。 `D` はパターンスペース中に最初に現れる `\n` までを削除し、スクリプトの先頭に戻る。 #### 8 スペースによるインデントを タブに置き換える #### 視認性のために、以下のコードではスペースを `SPC`, タブを `TAB` と書いている。 :foo s/^\(TAB*\)SPC\{8\}/\1TAB/ t foo `:foo` はラベル。 `t foo` は直前の置換が成功したら、ラベル `foo` に分岐するという意味です。 例えば、行頭にスペースが 16 個あったら、タブ 2個に置き換えて欲しいので、ループを使ってみた。 #### 行末の空白文字を削除する #### これは簡単。 s/[[:space:]]*$// `[[:space:]]` はスペースとタブにマッチします。 #### ファイル末の空行を削除する #### :top /^\n*$/ { $d N b top } ファイル末に 2 行以上連続する空行がある場合には、すべて削除したいので、 上記のようなスクリプトになりました。 ${ /^$/d } というスクリプトだと、最終行の空行のみ削除します。 これでも、最初の例の「複数行の空行を1行にまとめる」と組み合わせると、十分目的は達成できます。 #### ファイル先頭の空行を削除する #### 0,/^..*$/ { /^$/d } ファイル先頭に 2 行以上連続する空業がある場合にも、すべて削除されます。 `0,/^..*$/` は範囲指定で、ファイル先頭から、空行でない行が出現するまでマッチします。
2014年5月13日火曜日
U-Boot と Linux Kernel のメインラインで Zynq を動かす 2014年4月版
[以前](/2014/01/u-boot-linux-kernel-zynq.html)、2014年1月時点での U-Boot と Linux のメインラインで Zynq を動かす方法を紹介しました。 その後、コード(特に U-Boot)が大きく変わっているので、 2014年4月時点での動かし方を紹介することにします。 使用するのは、 - U-Boot 2014.04 - Linux Kernel 3.14 - Zynq ZC706 ボード です。 その前に、 Zynq のブートシーケンスを復習しておきましょう。 Active Boot (= JTAG ブート以外)は 1. Boot ROM 2. FSBL 3. U-Boot 4. Linux Kernel というのが、Xilinx が公式にサポートしているブートシーケンスで、 [Xilinx Wiki ページ](http://www.wiki.xilinx.com/)もこのやり方を紹介しています。 最近の U-Boot では 1. Boot ROM 2. U-Boot SPL 3. U-Boot 4. Linux Kernel というブートシーケンスも可能になっています。 SPL というのは Secondary Progmam Loader の略です。 DRAM 等のメモリを初期化し、U-Boot 本体を NAND, MMC 等のデバイスからロードするための、 より小さなブートローダーといったものです。 U-Boot の標準のインフラとして用意されています。 詳しく知りたい人は U-Boot の `doc/README.SPL` を読んでください。 これを選択するメリットは、 FSBL (First Stage Boot Loader) を生成するために、 XSDK (Xilinx SDK) を起動しなくても済む、ということです。 さらに簡略化した 1. Boot ROM 2. U-Boot SPL 3. Linux Kernel というブートシーケンスもあります。 (Falcon ブートといいます) これのメリットは U-Boot 本体をスキップすることで、より高速に Linux を起動できることです。 ただし、現時点ではサポートが限定的でまともに動かすのは難しいので、今回は割愛します。 おそらく、そう遠くないうちにまともに動かせるようになると思いますが。 以下では、 U-Boot 2014.04 から可能になった SPL を用いたブートのやり方を紹介します。 ### STEP1: U-Boot のビルド ##### Input Files Required - ARM Cross Compiler - `ps7_init.c`: ISE / Vivado で "Export Hardware for SDK" を実行すると出力される - `ps7_init.h`: ISE / Vivado で "Export Hardware for SDK" を実行すると出力される ##### Output Files Produced - `u-boot.bin`: U-Boot 本体の RAWバイナリ - `u-boot.img`: `u-boot.bin` に uImage ヘッダーをつけたもの - `spl/u-boot-spl.bin`: U-Boot SPLの RAWバイナリ - `tools/mkimage`: U-Boot で扱うイメージを生成するツール。 ##### Task Description $ git clone git://git.denx.de/u-boot.git $ cd u-boot $ git checkout v2014.04 でソース取得して、v2014.04 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) まず、少々ソースコードをいじらなくてはなりません。 `include/configs/zynq-common.h` を開き、以下のように `CONFIG_OF_CONTROL` ~ `CONFIG_RSA` までを無効にする。 --- a/include/configs/zynq-common.h +++ b/include/configs/zynq-common.h @@ -199,6 +199,7 @@ #define CONFIG_FIT #define CONFIG_FIT_VERBOSE 1 /* enable fit_format_{error,warning}() */ +#if 0 /* FDT support */ #define CONFIG_OF_CONTROL #define CONFIG_OF_SEPARATE @@ -207,6 +208,7 @@ /* RSA support */ #define CONFIG_FIT_SIGNATURE #define CONFIG_RSA +#endif /* Extend size of kernel image for uncompression */ #define CONFIG_SYS_BOOTM_LEN (20 * 1024 * 1024) なんで、上記を無効にするかというと、前回の U-Boot 2014.01 では U-Boot を DeviceTree 付きで動かしたのですが、コードのマージが中途半端に行われたために、 2014.04 で再び `CONFIG_OF_CONTROL` が動かなくなってしまったためです。 頑張って動かすことはできるのですが、 Linux Kernel から DeviceTree の記述をいろいろと持ってこなくてはいけなかったり、 SPL から u-boot.img + DeviceTree をロードするのにコード修正したりと、 いろいろと修正箇所が多いので、無効にしてしまった方が楽です。 また、あとで、Kernel を TFTP でダウンロードしたいので、`include/configs/zynq-common.h` の適当なところに #define CONFIG_IPADDR 192.168.11.2 #define CONFIG_SERVERIP 192.168.11.1 #define CONFIG_ETHADDR 00:0a:35:00:01:22 の3行を足す。 `CONFIG_IPADDR` は Zynqボードに割り振る IPアドレス、 `CONFIG_SERVERIP` は TFTP サーバーのアドレスに合わせて下さい。 MACアドレスは、(他のネットワーク機器と被らなければ)適当でいい。 TFTP サーバーがなくても、動かすことはできるので、ない人は上記はスキップして下さい。 さらに、 `ps7_init.c`, `ps7_init.h` を U-Boot の `board/xilinx/zynq` ディレクトリにコピーする。 このファイルが、 FSBL の代わりをするための、肝になるファイルです。 また touch board/xilinx/xil_io.h で、空の `xil_io.h` を作る。 (`ps7_init.c` が `xil_io.h` をインクルードしているので、これがないとエラーになる。) あとは $ make zynq_zc70x_config $ make CROSS_COMPILE=arm-linux-gnueabi- のようにして、コンフィグレーションとビルドをする。 もしくは $ make zynq_zc70x_config all CROSS_COMPILE=arm-linux-gnueabi- のように 1行で、コンフィグレーションとビルドを同時にすることもできる。 `ps7_init.c` と `ps7_init.h` が warning を出しますが、気にしなくてもOK。 気になる人は、関数のプロトタイプの引数部に `void` を足してください。 ### STEP2: Linux Kernel のビルド ##### Input Files Required - ARM Cross Compiler ##### Output Files Produced - `arch/arm/boot/zImage`: Kernel Image - `arch/arm/boot/dts/zynq-zc706.dtb`: Kernel をコンフィグレーションする DTB (Device Tree Blob) (従来、 U-Boot から Kernel を起動するときは `arch/arm/boot/uImage` を使っていたが、これは使わない。) ##### Task Description $ git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git $ cd linux $ git checkout v3.14 でソース取得して、v3.14 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) 以下のようにして ARMv7 Multi な設定にする。 $ make ARCH=arm multi_v7_defconfig ここで $ make ARCH=arm menuconfig をして、少々設定をいじる。 Device Drivers ---> Block devices ---> [*] RAM block device support (16384) Default RAM disk size (kbytes) のようにたどり、 `RAM block device support` にチェックを入れ、 `Default RAM disk size` を `16384` に設定する。 もう一つ Device Drivers ---> Character devices ---> [ ] Legacy (BSD) PTY support のようにたどり、`Legacy (BSD) PTY support` のチェックを外す。 あとは $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- でビルド。 ### STEP3: Ramdisk のダウンロード ##### Input Files Required - None ##### Output Files Produced - `arm_ramdisk.image.gz`: Kernel がマウントする init ramdisk (を gzip 圧縮したもの) ##### Task Description [こちら](http://www.wiki.xilinx.com/Build+and+Modify+a+Rootfs) から arm_ramdisk.image.gz をダウンロードする。 ### STEP4: ITB (Image Tree Blob) の生成 ##### Input Files Required - `linux/arch/arm/boot/zImage` - `arm_ramdisk.image.gz` - `linux/arch/arm/boot/dts/zynq-zc706.dtb` - `u-boot/tools/mkimage` カレントディレクトリから見て、上記の配置になっているとする。 ##### Output Files Produced - `fit.itb`: U-Boot から Kernel を起動するためのイメージ ##### Task Description Kernel Image, Ramdisk, DTB (Device Tree Blob) を一つにまとめた ITB というのを作ります。 ITB を作るには、 ITS(Image Tree Source) を記述して、 `mkimage` に食わせます。 以下の内容を `fit.its` というファイルに記述する。 /dts-v1/; / { description = "Kernel, ramdisk and FDT blob"; #address-cells = <1>; images { kernel@1 { description = "Linux Kernel 3.14 configured with multi_v7_defconfig"; data = /incbin/("linux/arch/arm/boot/zImage"); type = "kernel"; arch = "arm"; os = "linux"; compression = "none"; load = <0x00008000>; entry = <0x00008000>; hash@1 { algo = "md5"; }; }; ramdisk@1 { description = "Ramdisk for Zynq"; data = /incbin/("arm_ramdisk.image.gz"); type = "ramdisk"; arch = "arm"; os = "linux"; compression = "gzip"; load = <0x00000000>; entry = <0x00000000>; hash@1 { algo = "sha1"; }; }; fdt@1 { description = "FDT for ZC706"; data = /incbin/("linux/arch/arm/boot/dts/zynq-zc706.dtb"); type = "flat_dt"; arch = "arm"; compression = "none"; hash@1 { algo = "crc32"; }; }; }; configurations { default = "config@1"; config@1 { description = "Zynq ZC706 Configuration"; kernel = "kernel@1"; ramdisk = "ramdisk@1"; fdt = "fdt@1"; }; }; }; あとは以下のようにすれば、`fit.itb` ができる。 $ u-boot/tools/mkimage -f fit.its fit.itb ### STEP5: JTAG (Slave Boot) から U-Boot と Linux を起動する ##### Input Files Required - `u-boot.bin`: STEP1 で作成したもの - `fit.itb`: STEP4 で作成したもの - `xmd`: ISE / Vivado のインストールディレクトリに入っている - `ps7_init.tcl`: ISE / Vivado から "Export Hardware for SDK" を実行すると出力される - `stub.tcl`: Xilinx のページからダウンロードできる `ug873-design-files.zip` の中に入っている - `fpga.bit`: ISE / Vivado で生成した FPGA bit file (Optional) ##### Task Description `fit.itb` を TFTP の公開ディレクトリに置く。(TFTP 環境のない人はスキップして下さい) Zynq ボードのブートモードの選択スイッチを JTAG に合わせて電源入れる。JTAG でボードと接続し、XMD を開く。 $ xmd XMD のプロンプトから以下を実行する。(FPGA は必要なければダウンロードしなくても良い) XMD% connect arm hw ;# Open JTAG connection XMD% rst -slcr ;# Reset the whole system XMD% fpga -f fpga.bit ;# Download FPGA bit file (Optional) XMD% source ps7_init.tcl XMD% ps7_init ;# Initialize DDR, IO pins, etc. XMD% ps7_post_config ;# Enable level shifter XMD% source stub.tcl ;# start CPU1 XMD% targets 64 ;# connect to CPU0 XMD% dow -data u-boot.bin 0x04000000 ;# Download u-boot to address 0x04000000 XMD% con 0x04000000 ;# start CPU0 from address 0x04000000 なお、毎回これを打ち込むのも面倒ですので、 `foo.tcl` に書いておきましょう。 XMD% source foo.tcl で XMD から読み込むか、シェルから $ xmd -tcl foo.tcl とすればよいです。 U-Boot のプロンプトが出た後、放っておくと、自動的に TFTPサーバーから `fit.itb` をダウンロードして、 Linux が起動する。 TFTP サーバーがない場合は、`con 0x04000000` の前に XMD% dow -data fit.itb 0x02000000 とすれば、 JTAG 経由で `fit.itb` をダウンロードできるので(時間かかりますが、、) あとは U-Boot のプロンプトから > bootm 2000000 と入力して Linux を起動させる。 ### STEP6: SDカード用のブートイメージを作成する ##### Input Files Required - `u-boot/spl/u-boot-spl.bin`: STEP1 で作成したもの - `bootgen`: ISE / Vivado のインストールディレクトリに入っている ##### Output Files Produced - `boot.bin` ##### Task Description `foo.bif` というファイル(名前適当でよい)に以下のように記述する。 image: { [bootloader,load=0x00000000,startup=0x00000000]u-boot/spl/u-boot-spl.bin } そして $ bootgen -image foo.bif -w on -o boot.bin とすると、`boot.bin` ができる。SDカードのブートイメージは必ず `boot.bin` というファイル名でないといけないので注意する。 ### STEP6B: SDカード用のブートイメージを作成する (もうちょっと簡単なやり方) ##### Input Files Required - `u-boot/spl/u-boot-spl.bin`: STEP1 で作成したもの ##### Output Files Produced - `boot.bin` ##### Task Description `bootgen` を使わずに `boot.bin` を作成する方法を紹介します。 u-boot-xlnx のコードを取ってきます。 git clone git://github.com/Xilinx/u-boot-xlnx.git `tools` ディレクトリの下に `zynq-boot-bin.py` という Python スクリプトが 入っているので、これを `~/bin` かどこか適当な PATH にコピーする。 あとは zynq-boot-bin.py -o boot.bin -u u-boot/spl/u-boot-spl.bin とすれば、 `boot.bin` ができます。 BIF ファイルを記述しなくてもいい分、こちらの方が簡単だと思います。 なお、 u-boot-xlnx だと、 `zynq-boot-bin.py` が Makefile からフックされていて、 make すると自動で `boot.bin` までできるので、更に楽なのですが、 (しかもローカルでいろいろとソースをいじくらなくても動く) ここのページではあくまで、メインラインでやることを目指しています。 ### Step7: SDカードから U-Boot と Linux Kernel を起動する ##### Input Files Required - `boot.bin`: STEP6 または STEP6B で作成したもの - `u-boot.img`: STEP1 で作成したもの - `fit.itb`: STEP4 で作成したもの ##### Task Description FAT でフォーマットしたSDカードに `boot.bin`, `u-boot.img`, `fit.itb` をコピー。 SDカードを Zynq ボードに挿し、ブートモードの選択スイッチを SD カードに合わせて電源入れる。 ### STEP8: FSBL を使ったブートシーケンス 上記の通り、 SPL を使ったブートシーケンスを紹介しましたが、 従来通りの FSBL を使ったブートシーケンスも可能です。 その場合は U-Boot に `ps7_init.c` と `ps7_init.h` をコピーしたり、 `xil_io.h` を作ったりする必要はないです。 STEP6 の部分を以下のようにアレンジすればよいです。 ##### Input Files Required - `fsbl.elf`: FSBL (First Stage Boot Loader)。XSDK で生成。 - `fpga.bit`: FPGA bit file (Optional) - `u-boot/u-boot.bin`: STEP1 で作成したもの - `bootgen`: ISE / Vivado のインストールディレクトリに入っている ##### Output Files Produced - `boot.bin` ##### Task Description `foo.bif` というファイル(名前適当でよい)に以下のように記述する。 image: { [bootloader]fsbl.elf fpga.bit [load=0x04000000,startup=0x04000000]u-boot/u-boot.bin } FPGA Bit file のダウンロードが不要なら `fpga.bit` の行は削除してよい。 あとは $ bootgen -image foo.bif -w on -o boot.bin とすると、`boot.bin` ができるので、 FAT でフォーマットしたSDカードに `boot.bin` と `fit.itb` をコピー。 ### 今後の開発の行方 Zynq は U-Boot の中でも、特に頻繁に更新されている SoC で、今後もやり方が変わっていく可能性が高いです。 今回、ローカルでソースをいじくっている部分のいくつかは、修正パッチを投稿しておいたので、 次のリリースではもうちょっと楽になるはず。 U-Boot 2014.04 ではまともに動いてませんが、 DeviceTree を使った U-Boot のコンフィグレーションに移行していくでしょう。 最終的には、 `ps7_init.c` と `ps7_init.h` をコピーすることもなくなり、 すべての情報を DeviceTree から読み込むようになるだろう。 (と、コードを書いている Xilinx のエンジニアは語っておりました。)
2014年2月7日金曜日
Zombie Shuffling?
Linux Kernel 3.14-rc1 からコードネームが "Shuffling Zombie Juror" になりました。 VERSION = 3 PATCHLEVEL = 14 SUBLEVEL = 0 EXTRAVERSION = -rc1 NAME = Shuffling Zombie Juror たぶん、これの由来になったであろう投稿が Google+ にありました。(Linus は Facebook よりも Google+ の方がお気に入りです。) 2014/1/25 の Linus の投稿より。 > I have a zombie shuffling desk! > > So normal people call it a "walking desk", but quite frankly, I have a hard time typing (or particularly using the mouse with any precision) at any speed setting over one mile per hour, so it really is more of a zombie shuffling thing than actually walking. > > But even that should be better than sitting all day long, right? > > Wish me luck. Or at least persistence. Google+ の投稿の方を先に読んでいたので、3.14-rc1 の Makefile 先頭を見た時に、ニヤッ、としてしまいました。 Linus が最近興味を持っているものが反映されているのは面白いですね。
2014年1月31日金曜日
Zynq のビルドプロセスをスクリプト化する
Zynq の話なんですが、そこそこ慣れてくると、Vivado や XSDK の GUI が使いにくく思えてきます。 GUI というのは、最初は何となくわかりやすいような印象を与えるが、ある程度使いこなせるようになると、 CUI の方がありがたいです。 基本的に、開発中というのは「ソースコードの修正」→「ビルド」→「動作確認」の繰り返しです。 いちいち、GUI を開いて、FPGA をコンパイルしたり、ハードウェアをエクスポートして、 XSDK に食わせたりとかのルーチンワークを手動でやりたくないのです。 幸い、Vivado には Tcl インターフェースがあるので、GUI でできることは、CUI でもできるのです。 私がこれをやりたい理由を挙げると、 - GUI は重い - GUI は自動実行できないので、繰り返し作業に向いていない - GUI は間違えやすい チームでの開発になったりすると、中には詳しくないメンバーもいたりする。 特に Zynq はビルド手順がかなり複雑になってしまっているので、 GUI でのビルド手順を習得するには、それなりに時間がかかるし、操作ミスも発生する。 ビルドプロセスを完全にスクリプト化してしまえば、「とりあえず "make" って打っとけや」で済む。 - GUI のプロジェクトはバージョンコントロール(git) での管理が難しい バージョン管理をせずに仕事を進めるというのは、恐ろしいことである。 これをしないと、ソースを過去に戻したり、開発プロセスを他のメンバーと共有できない。 しかしながら、Vivado のプロジェクトとバージョン管理は相性が悪い、 というかそもそもバージョン管理のことを考えてないんじゃないかと思う。 FPGA の時はプロジェクトが管理するのは RTL と制約ファイルぐらいだったから、 RTL と 制約ファイルをバージョン管理して、プロジェクトはその都度、再構築すればよかった。 しかし、Zynq の場合は、ARM 周りの設定とか、IP 間の接続とか、いろんなものがプロジェクトで管理されている。 プロジェクト自体を git に登録するのは、一体何がソースで、 何がジェネレートされたファイルなのかよくわからないので無理がある。 そこで、RTL や 制約ファイルとともに Vivado を制御するスクリプトを Tcl で書き、これらを git で管理することにした。 なに、大した知識は必要ない。 Tcl の初歩的な文法がわかればよい。(知らなくても 半日もあれば勉強できる。) あとは、Vivado をコントロールするコマンド群だが、これも簡単にわかる。 Vivado を起動した時に、カレントディレクトリに `.jou` という拡張子のファイル (Journal File) が作られるが、このファイルに、 Vivado が実行した操作が Tcl のコマンドとして記録されていく。 なので、知りたいコマンドがあるときは、GUI から一度実行してみて、Journal File を見れば、対応するコマンドが一目瞭然なのだ。 例えば、プロジェクトを作成するのは `create_prj` だし、RTL を追加するには `import_files`、合成は `launch_runs systh_1` でよいというのがわかる。 詳しいコマンドの意味を調べたいときは "Vivado Design Suite Tcl Command Reference Guide" というドキュメントがある。 そんな感じで、プロジェクト作って、RTL と 制約ファイル追加して、合成して、配置配線して、 Bit ファイル作って、HW 情報をエクスポートして、なんてのをスクリプトで組んでいけばよい。 あとは Block Design だが、これも Tcl スクリプト化できる。 Block Design を開いた状態で、 Vivado のメニューから 「File → Export → Export Block Design...」 を選べば、Tcl スクリプトに出力できる。 Block Design を Tcl からプロジェクトへリストアするには、その Tclスクリプトを `source` で読み込んで `save_bd_design` でよい。 このようにして組んだ Tcl スクリプトをバッチモードで実行するには以下のようにする。 $ vivado -mode batch -source
これで、Vivado の GUI を開く必要がなくなった。 GUI を使うのは Block Design を編集するときぐらいである。 さらには Makefile も作って、FSBL, U-Boot, Linux など他のコンポーネントも自動でビルドするようにして、 XMD からブートさせるところまで、自動化してしまいます。 このようにして作った、非常に軽量な完全自動化プロジェクトを git で履歴管理します。快適です。 PetaLinux も Xilinx が用意してくれているオールインの環境(あまり詳しくない)だと思うが、こちらはかなり重そう。。
2014年1月21日火曜日
U-Boot と Linux Kernel のメインラインで Zynq を動かす
[以前](/2014/01/zynq-2.html)、U-Boot と Linux をソースからビルドして Zynq 上で動かすには、[Wiki Page](http://www.wiki.xilinx.com/) を読むのがわかりやすいと書きました。 このページを参照しながら、やっている人も多いのではと思います。 Xilinx は GitHub に[ローカルな開発リポジトリ](https://github.com/Xilinx) を持っていて、 Wiki Page もこのリポジトリを使っています。 ここでは、Xilinx のリポジトリではなく、U-Boot と Linux Kernel のメインラインからコードを持ってきて、 Linux をブートさせるやり方を紹介します。 ちょうど、本日(=2014.1.21) U-Boot 2014.01 がリリースされました。 このリリースから Zynq の各種ボードサポートが U-Boot のメインラインにマージされたので、 Xilinx ローカルリポジトリを追跡しなくても動かせるようになりました。 また、前日 (=2014.1.20) には Linux 3.13 がリリースされましたので、これを使ってみます。 今回使うのは - U-Boot 2014.01 - Linux Kernel 3.13 - Zynq ZC706 ボード です。 ただし、Wiki Page で紹介されているやり方と結構違います。主な違いは - U-Boot 自身のコンフィグレーションにも Device Tree が必要 - Kernel のイメージは、従来のレガシー uImage ではなく、 FIT (Flattened uImage Tree) を使う - Kernel の defconfig は `multi_v7_defconfig` を使う 入門者には何言っているかわからないかもしれないが、以下で丁寧に手順を説明します。 ### STEP0: DTC (Device Tree Compiler) の準備 ##### Input Files Required - None ##### Output Files Produced - `dtc`: Device Tree Compiler ##### Task Description U-Boot を Device Tree 付きでビルドするには version 1.4 以降の DTC が必要になる。 ディストリビューションに標準で用意されている DTC では不足するかもしれないのでバージョンを確認。 $ dtc -v Version: DTC 1.3.0 バージョンが 1.4.0 よりも古い場合は、自分でビルドします。 $ git clone git://git.kernel.org/pub/scm/utils/dtc/dtc.git でソースを取ってきて $ make $ make install でビルドとインストールができる。 `$(HOME)/bin` の下に `dtc` が入るので PATH を通しておく。 ### STEP1: U-Boot のビルド ##### Input Files Required - `dtc` - ARM Cross Compiler ##### Output Files Produced - `u-boot-dtb.bin`: U-Boot の RAWバイナリと U-Boot をコンフィグレーションする DTB を連結したもの - `tools/mkimage`: U-Boot で扱うイメージを生成するツール。 (Wiki Page では `u-boot` を使っているが、これは使わない。) ##### Task Description $ git clone git://git.denx.de/u-boot.git $ cd u-boot $ git checkout v2014.01 でソース取得して、v2014.01 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) あとで、Kernel を TFTP でダウンロードしたいので、`include/configs/zynq-common.h` を開いて、適当なところに #define CONFIG_IPADDR 192.168.11.2 #define CONFIG_SERVERIP 192.168.11.1 #define CONFIG_ETHADDR 00:0a:35:00:01:22 の3行を足す。 `CONFIG_IPADDR` は Zynqボードに割り振る IPアドレス、 `CONFIG_SERVERIP` は TFTP サーバーのアドレスに合わせて下さい。 MACアドレスは、(他のネットワーク機器と被らなければ)適当でいい。 TFTP サーバーがなくても、動かすことはできるので、ない人は上記はスキップして下さい。 あとは $ make zynq_zc70x_config $ make CROSS_COMPILE=arm-linux-gnueabi- DEVICE_TREE=zynq-zc706 のようにして、コンフィグレーションとビルドをする。 もしくは $ make zynq_zc70x CROSS_COMPILE=arm-linux-gnueabi- DEVICE_TREE=zynq-zc706 のように 1行で、コンフィグレーションとビルドを同時にすることもできる。 ### STEP2: Linux Kernel のビルド ##### Input Files Required - ARM Cross Compiler ##### Output Files Produced - `arch/arm/boot/zImage`: Kernel Image - `arch/arm/boot/dts/zynq-zc706.dtb`: Kernel をコンフィグレーションする DTB (Device Tree Blob) (従来、 U-Boot から Kernel を起動するときは `arch/arm/boot/uImage` を使っていたが、これは使わない。) ##### Task Description $ git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git $ cd linux $ git checkout v3.13 でソース取得して、v3.13 タグをチェックアウト。 (何かあっても自分で対処できる人は masterブランチでやってもOK) 以下のようにして ARMv7 Multi な設定にする。 $ make ARCH=arm multi_v7_defconfig ここで $ make ARCH=arm menuconfig をして、少々設定をいじる。 Device Drivers ---> Block devices ---> [*] RAM block device support (16384) Default RAM disk size (kbytes) のようにたどり、 `RAM block device support` にチェックを入れ、 `Default RAM disk size` を `16384` に設定する。 もう一つ Device Drivers ---> Character devices ---> [ ] Legacy (BSD) PTY support のようにたどり、`Legacy (BSD) PTY support` のチェックを外す。 あとは $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- でビルド。 ### STEP3: Ramdisk のダウンロード ##### Input Files Required - None ##### Output Files Produced - `arm_ramdisk.image.gz`: Kernel がマウントする init ramdisk (を gzip 圧縮したもの) ##### Task Description [こちら](http://www.wiki.xilinx.com/Build+and+Modify+a+Rootfs) から arm_ramdisk.image.gz をダウンロードする。 ### STEP4: ITB (Image Tree Blob) の生成 ##### Input Files Required - `linux/arch/arm/boot/zImage` - `arm_ramdisk.image.gz` - `linux/arch/arm/boot/dts/zynq-zc706.dtb` - `u-boot/tools/mkimage` カレントディレクトリから見て、上記の配置になっているとする。 ##### Output Files Produced - `fit.itb`: U-Boot から Kernel を起動するためのイメージ ##### Task Description Kernel Image, Ramdisk, DTB (Device Tree Blob) を一つにまとめた ITB というのを作ります。 ITB を作るには、 ITS(Image Tree Source) を記述して、 `mkimage` に食わせます。 以下の内容を `fit.its` というファイルに記述する。 /dts-v1/; / { description = "Kernel, ramdisk and FDT blob"; #address-cells = <1>; images { kernel@1 { description = "Linux Kernel 3.13 configured with multi_v7_defconfig"; data = /incbin/("linux/arch/arm/boot/zImage"); type = "kernel"; arch = "arm"; os = "linux"; compression = "none"; load = <0x00008000>; entry = <0x00008000>; hash@1 { algo = "md5"; }; }; ramdisk@1 { description = "Ramdisk for Zynq"; data = /incbin/("arm_ramdisk.image.gz"); type = "ramdisk"; arch = "arm"; os = "linux"; compression = "gzip"; load = <0x00000000>; entry = <0x00000000>; hash@1 { algo = "sha1"; }; }; fdt@1 { description = "FDT for ZC706"; data = /incbin/("linux/arch/arm/boot/dts/zynq-zc706.dtb"); type = "flat_dt"; arch = "arm"; compression = "none"; hash@1 { algo = "crc32"; }; }; }; configurations { default = "config@1"; config@1 { description = "Zynq ZC706 Configuration"; kernel = "kernel@1"; ramdisk = "ramdisk@1"; fdt = "fdt@1"; }; }; }; あとは以下のようにすれば、`fit.itb` ができる。 $ u-boot/tools/mkimage -f fit.its fit.itb ### STEP5: JTAG (Slave Boot) から U-Boot と Linux を起動する ##### Input Files Required - `u-boot-dtb.bin`: STEP1 で作成したもの - `fit.itb`: STEP4 で作成したもの - `xmd`: ISE / Vivado のインストールディレクトリに入っている - `ps7_init.tcl`: ISE / Vivado から "Export Hardware for SDK" を実行すると出力される - `stub.tcl`: Xilinx のページからダウンロードできる `ug873-design-files.zip` の中に入っている - `fpga.bit`: ISE / Vivado で生成した FPGA bit file (Optional) ##### Task Description `fit.itb` を TFTP の公開ディレクトリに置く。(TFTP 環境のない人はスキップして下さい) Zynq ボードのブートモードの選択スイッチを JTAG に合わせて電源入れる。JTAG でボードと接続し、XMD を開く。 $ xmd XMD のプロンプトから以下を実行する。(FPGA は必要なければダウンロードしなくても良い) XMD% connect arm hw ;# Open JTAG connection XMD% rst -slcr ;# Reset the whole system XMD% fpga -f fpga.bit ;# Download FPGA bit file (Optional) XMD% source ps7_init.tcl XMD% ps7_init ;# Initialize DDR, IO pins, etc. XMD% ps7_post_config ;# Enable level shifter XMD% source stub.tcl ;# start CPU1 XMD% targets 64 ;# connect to CPU0 XMD% dow -data u-boot-dtb.bin 0x04000000 ;# Download u-boot to address 0x04000000 XMD% con 0x04000000 ;# start CPU0 from address 0x04000000 なお、毎回これを打ち込むのも面倒ですので、 `foo.tcl` に書いておきましょう。 XMD% source foo.tcl で XMD から読み込むか、シェルから $ xmd -tcl foo.tcl とすればよいです。 U-Boot のプロンプトが出た後、放っておくと、自動的に TFTPサーバーから `fit.itb` をダウンロードして、 Linux が起動する。 TFTP サーバーがない場合は、`con 0x04000000` の前に XMD% dow -data fit.itb 0x02000000 とすれば、 JTAG 経由で `fit.itb` をダウンロードできるので(時間かかりますが、、) あとは U-Boot のプロンプトから > bootm 2000000 と入力して Linux を起動させる。 ### STEP6: SDカード用のブートイメージを作成する ##### Input Files Required - `fsbl.elf`: FSBL (First Stage Boot Loader)。XSDK で生成。 - `fpga.bit`: FPGA bit file (Optional) - `u-boot/u-boot-dtb.bin`: STEP1 で作成したもの - `bootgen`: ISE / Vivado のインストールディレクトリに入っている ##### Output Files Produced - `boot.bin` ##### Task Description `foo.bif` というファイル(名前適当でよい)に以下のように記述する。 image: { [bootloader]fsbl.elf fpga.bit [load=0x04000000,startup=0x04000000]u-boot/u-boot-dtb.bin } FPGA Bit file のダウンロードが不要なら `fpga.bit` の行は削除してよい。 ELF ファイルはロードアドレスとエントリーアドレスを自動抽出してくれるが、 バイナリの場合は `load=` と `startup=` で指定する必要がある。 あとは $ bootgen -image foo.bif -w on -o boot.bin とすると、`boot.bin` ができる。SDカードのブートイメージは必ず `boot.bin` というファイル名でないといけないので注意する。 ### Step7: SDカードから U-Boot と Linux Kernel を起動する ##### Input Files Required - `boot.bin`: STEP6 で作成したもの - `fit.itb`: STEP4 で作成したもの ##### Task Description FAT でフォーマットしたSDカードに `boot.bin` と `fit.itb` をコピー。 SDカードを Zynq ボードに挿し、ブートモードの選択スイッチを SD カードに合わせて電源入れる。 ### ZC706 以外のボードの場合 動作確認はしていないが、上記とほぼ同じやり方でできずはず。 U-Boot のビルドの部分が、ZC702 ボードなら $ make zynq_zc70x CROSS_COMPILE=arm-linux-gnueabi- ZED ボードならば $ make zynq_zed CROSS_COMPILE=arm-linux-gnueabi- となる。 U-Boot の `boards.cfg` というファイルを見れば、自分のボードに対応するコマンドがわかる。 Linux の Device Tree も `arch/arm/boot/dts` ディレクトリに各ボードごとの DTB ができているので、それを使う。 ### その他参照資料 - Device Tree を用いた U-Boot のコンフィグレーションについて知りたい場合は U-Boot ソースツリーの `doc/README.fdt-control` を参照 - ITS (Image Tree Source) の書き方を知りたい場合は U-Boot ソースツリーの `doc/uImage.FIT/` 以下のドキュメント参照 - XMD の使い方は Xilinx の資料 "Embedded System Tools Reference Manual" 参照 - FSBL の作り方は Xilinx の資料 "Zynq-7000 All Programmable SoC: Concepts, Tools, and Techniques" 5章を参照 - Bootgen をもっと詳しく知りたい場合は Xilinx の資料 "Zynq-7000 All Programmable SoC Software Developers Guide" 参照 ### ご注意 ここに記載の内容は 2014年1月時点のソースでのやり方ですので、今後もこのやり方が通用するかはわかりません。 コードはめまぐるしく変わっていきますので。
2014年1月10日金曜日
Vivado で FPGA をリモートコンフィグレーション
### サーバー/クライアントモデルになった FPGA コンフィグレーション Vivado で便利だと思ったのが、リモートで FPGA のコンフィグレーションが行えることです。 どういうことかというと、Vivado が起動している PC と ボードが繋がっている PC が別々でもいいのです。 |-------| |-------| JTAG | | LAN | | cable |------------| | PC1 |----------| PC2 |---------| FPGA Board | | | | | |------------| |-------| |-------| 図のように LAN でつながった PC1 と PC2 があったとする。 Vivado は PC1 上で動作している。 Vivado がサクサク動くように PC1 は高速なサーバーマシンだとしよう。 PC2 は作業者が実際に操作するクライアント端末である。非力なマシンでもOK。 PC1 には触らないので、別の部屋にあってもよい。 Zynq ボードは、LED をモニタしたり、ディップスイッチを押したりしたいので、ボードは PC2 に接続する。 この状況で、PC1 上で動作している Vivado から PC2 につながったボード上の FPGA をコンフィグレーションできます。 #### 手順 作業者は PC2 から PC1 へ ssh などでログインし、Vivado を起動する。 PC2 にボードを接続し、PC2 上で `vcse_server` というのを起動する。 $ vcse_server Vivado Cse Server Version 2013.2 - Compiled Jun 15 2013 11:11:22 Vivado Cse Server: Opened server at port: 60001 To exit the server, type command 'exit' VCSE % cse_server がポート番号 60001 を開いたというのが表示されました。 PC1 上の Vivado で FPGA の bit stream を作成したら、「Program and Debug」→ 「Open Hardware Session」→「Open a new hardware target」と選択すると、 接続する Cse Server を設定するダイアログが開きます。 [![Open Hardware Session](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyzIjS_h4GhKU4Nx8sAAiOoRUhNQoMgMmsgwm7qndUYCONdEUb0elCiAwIebLGYZijjjGNFMD19e6JOe-tsanfhFIdUTdRf1rz83KEnH12rguwGCeammUHmRQT4U74B4At8pyv9YEnzZQ/s320/hardware_session.png)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyzIjS_h4GhKU4Nx8sAAiOoRUhNQoMgMmsgwm7qndUYCONdEUb0elCiAwIebLGYZijjjGNFMD19e6JOe-tsanfhFIdUTdRf1rz83KEnH12rguwGCeammUHmRQT4U74B4At8pyv9YEnzZQ/s1600/hardware_session.png) 通常は、 Vivado の動作しているPC (この場合はPC1)のIPアドレスがデフォルトで入っていると思いますが、 これをさっき vcse_server を起動したPC (PC2) のIPアドレスに修正します。 ポート番号もさっきの番号 60001 になっているのを確認しておきます。 すると PC2 につながったボードに接続できるので、あとはいつもの感じで FPGA へ bit stream をダウンロードします。 全く同様に Vivado Logic Analyzer (ISE のときの ChipScope みたいなもの)もリモートで使えます。 ### XSDK もリモートでデバッグする Zynq を動かすには ARM 用のソフトウェアと FPGA のハードウェアの両方を開発する必要があります。 FPGA 開発がリモートで行えるのならば、XSDK (Eclipse を Xilinx 用に拡張したもの) もリモートでやりたいところです。 2つやり方を見つけたので記録。 #### やり方その1: xmd を使う ボードを JTAG ケーブルで PC2 に接続し、PC2 上で xmd を起動し、`connect arm hw` を実行。 $ xmd Xilinx Microprocessor Debugger (XMD) Engine Xilinx EDK 14.6 Build EDK_P.68d Copyright (c) 1995-2012 Xilinx, Inc. All rights reserved. XMD% XMD% connect arm hw JTAG chain configuration -------------------------------------------------- Device ID Code IR Length Part Name 1 4ba00477 4 Cortex-A9 2 23731093 6 XC7Z045 -------------------------------------------------- Enabling extended memory access checks for Zynq. Writes to reserved memory are not permitted and reads return 0. To disable this feature, run "debugconfig -memory_access_check disable". -------------------------------------------------- CortexA9 Processor Configuration ------------------------------------- Version.............................0x00000003 User ID.............................0x00000000 No of PC Breakpoints................6 No of Addr/Data Watchpoints.........4 Connected to "arm" target. id = 64 Starting GDB server for "arm" target (id = 64) at TCP port no 1234 XMD% TCPポート 1234 番が開いたのがわかる。 PC1 上で動作している XSDK で プロジェクトを右クリックし、 「Debug As」→「Debug Configurations...」と選ぶ。 [![Debug Configuration](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ_fRDFGBFjmPCNR1wY10aL65kZYzFDzUNYgbRuOKGJt-41bgH7TzFKP3bVd5fY_yy-O5RJfXe4Ueybj1dJptPoL2nvVy92ICn4QeLc0UMUndhrN3wjuPLiFo9yJ_C4UXRlxs01_MEb5Y/s320/debug_config.png)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ_fRDFGBFjmPCNR1wY10aL65kZYzFDzUNYgbRuOKGJt-41bgH7TzFKP3bVd5fY_yy-O5RJfXe4Ueybj1dJptPoL2nvVy92ICn4QeLc0UMUndhrN3wjuPLiFo9yJ_C4UXRlxs01_MEb5Y/s1600/debug_config.png) Xilinx C/C++ application (GDB) を右クリックし、「New」を選択。 「Remote Debug」というタブがあるので、「IP Address」の欄の localhost を消し、 PC2 の IPアドレスを入力する。 「Port」の欄には、 PC2 側が待っている 1234 番が入っているのを確認する。 あとは「Debug」ボタンを押す。ステップ実行などができる。 #### 方法その2: hw_server を使う 基本的に [http://www.xilinx.com/support/documentation/sw_manuals_j/xilinx14_6/SDK_Doc/tasks/sdk_t_tcf_remote_debug.htm](http://www.xilinx.com/support/documentation/sw_manuals_j/xilinx14_6/SDK_Doc/tasks/sdk_t_tcf_remote_debug.htm) にしたがってやれば OK。 PC2 上で `hw_server` 起動 $ hw_server -s tcp::3122 ****** Xilinx hw_server v2013.4 **** Build date : Dec 9 2013-17:26:10 ** Copyright 1986-1999, 2001-2013 Xilinx, Inc. All Rights Reserved. INFO: hw_server application started INFO: Use Ctrl-C to exit hw_server application PC1 上で動いている XSDK 上で メニュー「Xilinx Tools」→「Configure JTAG Settings」を選び、 - Type: Xilinx Hardware Server - Hostname: hw_server を起動したPC (この場合PC2) のIPアドレス - Port: hw_server で開いているポート (この場合 3122) を設定する。 プロジェクトを右クリックし、「Debug As」→「Launch Hardware (System Debugger)」を選ぶ。
新しい投稿
前の投稿
ホーム
登録:
投稿 (Atom)