とあるエンジニアの備忘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 も更新する。
新しい投稿
前の投稿
ホーム
登録:
投稿 (Atom)