細かく分けてしまうと、ウェアレベリングをそのパーティション内でやらざるを得ないという問題が出る。スワップは領域が小さい上に書き換え頻度が多くなることになり、持つこと自体が厳しくなる。そのため、大きな領域にして UBI パーティションとして、その後の分割は後で考えることにしたい。
UBI 以外は、ブート用と カーネル用のパーティションに分ける。
ただし、使うのは先頭だけで、隙間をたっぷり取っておく。(
隙間の使い道はあとで考える。)
4MB ブート用 (代替ブロック 2)
60MB カーネル用 (代替ブロック 2)
残り UBI 用 (代替ブロック 10)
こんなところでどうだろう。
と書いた。これについてもう少し考察しておきたい。
最新の案:
4MB ブート用 (代替ブロック 2)
60MB カーネル用 (代替ブロック 2)
残り UBI 用 (代替ブロック 200 for 4Kpage,2GB)
E7002(tcc8902) のファームウェアアップデート方法についてちょっと調べた。ポイントだけ書くと、
- FWDN というツールでアップデートするが、tcc8902 の USB boot にも対応している。
- 書き込むデータはいくつかに分割されている。その種類は、
Boot Loader , Kernel Image, Ramdisk File, MTD(tcc8900_mtd.img :121MB) - >MTD については、NAND Data.fai (730KB) というファイルも必要。
FWDN が MTD を外部から書くのだとすれば、バッドブロック処理は必須で、そのための情報が NAND Data.fai のような気がする。(違うかも知れない)。
Boot Loader , Kernel Image, Ramdisk File についても バッドブロック処理があるのかも知れないが、簡単なものではないかと思える。
いま作ろうとしているものも事情は同じ。上記も参考にしよう。
まず、ブート用、カーネル用 の書き換え頻度は少ない。 バッドブロックが新たにできる確率は小さい。それでも 初期状態ですら存在する確率は 0 ではないので、対処だけはしておく。
対処は、簡単なもので良く、mtd ドライバのバッドブロックに対応すれば十分。対応は、書き込みツールと Boot Loader など読み込むコードで行う。Linux の方法を採用しているだけだから、Linux 自体は なにもしなくて良い。
さて問題は、UBI 用である。手持ちの SSD は、32GB/2Kブロック (16K 個のブロック)に対して、1000個ほどの代替ブロックを用意している。2GB/512B ブロック なら 4K 個のブロック数なので、250 個ほどは 代替ブロックが必要という計算になる。にもかかわらず 10個ほどしか用意しないのは、mtd の仕組みは 使わないと思っているから。
問題は バッドブロックだけではない。たとえ再インストールしても いままでの書き換え回数の情報は残したい。
で、それをするためには、UBI は、Linux で必ず更新する..ということにしまうのが一番簡単である。
インストールする際にも、Linux を動かして Linux が自分で UBI を初期化することになる。
そう決めたときの課題は、インストールする内容をどこから持ってくるか。
- USB から
インストールは、USBBOOT だけで行なおうと考えている。それならば自由度は高く USBBOOT 経由でデータを転送することが可能ではある。
ただそうした場合、ドライバーを作成しなくてはならない。これは大分面倒。- もうひとつは、microSD/SD から。
- カーネル用パーティションから
これが一番簡単。ただ、容量が足りなくなるのが心配だし、容量が無駄に使われるのが嫌な点。
足りなくなる点については、少し考察してみよう。オリジナルのファームウェアは、圧縮して 20-30MB 。android 2.1(tcc8902) は、60MB ほど。ubuntu を採用した SmartQ5 では、170MB ほど。
android ですら 60MB なら、個人が用意できる基本ファームウェアがそれ以上になるとは思えない。それでも超える場合は、上の方法を使うということでどうだろう。
これは、どこに置くか決めるだけで面倒ではない。だが、操作のほうが面倒だ。データは PC にあるので、microSD/SD にいちいち書きこまなくてはならない。
スターンドアローンで アップデートできるなら microSD/SD に置いても良いのだが.. 一部は PC / 残りは microSD/SD というのは避けたい。
まぁ.. こういうわけで、圧縮したイメージを カーネル用パーティションの隙間におくことにしよう。
次の問題は、インストーラ。インストーラ用のカーネル と initrd を別に持つというのもひとつの方法だが、正規の initrd に 組み込んでしまうのはどうか?
あるボタンを押して立ち上げれば、initrd がそれを認識して、インストールを行う。初期化できたところで、正規の mount 処理を動かす。
こんなかんじ。新しくなっていれば、自動的にインストールするのでも良い。
どうしても panic するという自体になってしまったら、初期化すれば立ち上がる。
ただ、ユーザのデータと その下位構造の UBI は勝手に壊さないようにする。ユーザのデータや UBI まで壊れた場合は、確認を求める。No! の場合、その場は電源を切る。後で USBBOOT を使った修復ツールを動かすとか別途考える。
パーティションの容量配分は、これで良いか?
4MB ブート用 (代替ブロック 2)
60MB カーネル用 (代替ブロック 2)
残り UBI 用 (代替ブロック 10) - カーネル用パーティションから
と書いたが、これで良いのだろうか? まず、代替ブロックとはどういう単位で代替するのか? 代替ブロックのために 容量がどれだけ犠牲になるのか知らないといけない。
ちょっとコードを見てみる。代替ブロックの定義は、get_jz_badblock_table() を call することで取得する。
これは、mtdblock-jz.c の mtdblock_zone_init() で行っている。
で、reserved_sectors という変数に 得た値 を (phys_erase_shift - 9) だけ左シフトして格納している。
どうも reserved_sectors は 512B 単位で 代替ブロックの単位は、消去ブロックサイズ (4Kpage なら 512K)らしい。
そうなると 4MB の ブート用パーティションは 1MB 取られていることになる。
次に管理領域。
total_virt_block = total_phys_block - bad_block_num
(単位:消去ブロック, total_phys_block は、mtd の size(バイト単位) を phys_erase_shift している。)
となっているから、reserved_sectors に管理領域が含まれているようだ。
さて、エラーを見つけたら マークを付けるようだ。これは nand_base.c の nand_default_block_markbad() 。見ると OOB に対して 2 バイト分 上書き?している。
それはともかく、それをどう変換するのか .. 関数は、mtdblock-jz.c の mtdblock_address_translate() らしい。これは block_lookup[] という配列を使って virt → phys の変換をしている。512KB 単位なら 4GB でも 8K エントリで 32KB しか消費しないからこれで良いのだろう。 --- 問題はそれをどう作っているか? ということになる。
どうも nandblock-jz.c の mtdblock_block_lookup_map_entry() というのが更新する関数らしい。そして、badblock を見つけたときに どこを割り当てるかというのは、mtdblock_find_free_block() ということらしい。
なにをしているかというと block_info[] を頭からサーチして、free block の マークが付いているものをさがしている。
block_info[] をどう作っているのか調べないといけない。ちなみに、erase すると block_info[].lifetime を +1 している。
このデータをいつどう書き戻しているのかというのは結構重要そう。
block_info は、
struct mtdblk_block_info {
unsgined int lifetime;
unsgined char tag; /* 0x01: FREE 0x02: BAD 0x04: EMPTY*/
};
という構造。mtdblock_zone_init で read_oob して メモリ上の配列として構成しているようだ。
ここまでの理解は、
- read エラーを見つけたら 消去ブロック単位で代替する。
- 代替ブロックは oob を線形サーチすれば見つかる。(管理のための領域は、データブロックにはない)
カーネル用の領域は、こういう仕組みが役に立つだろう。普通なら 5% の代替エリアが必要だとすれば、6 個ぐらいあった方が良い。書き換え頻度が少ないが 代替の単位が大きいので 2 個では不安なのだが、これで行くことにする。
それに対して ブート用はどうだろう? カーネルでの書き換えは 4Kpage の場合 512KB 単位になるわけで、4MB で 8 つの エリアがある。壊れれば代替してくれるわけだが、先頭が壊れた場合 代替してくれても困るかもしれない。でも、なにか物理的な先頭ページにアクセスする手段があるのだろう。そうだとすれば、壊れていた場合に mtdblock の先頭にアクセスすると、代替されて 無駄に 1 ブロック使われるという弊害がでるが、気にしないことにする。
8 個のブロックのうち ブートローダが 1 個。代替に 2 個。5 つのエリア(2.5MB) は自由に使えることになる。
5 つのうち 少なくとも 1 つはカーネルパラメータを入れたい。(
2 つ 3 つあっても良い。)
残りは、u-boot を使いたくなった場合に使うことにする。一応 2 個のエリア (1MB) 確保できているから大丈夫だろう。
最後に UBI 用についてなのだが、512KB 単位で代替しているわけがない。10 個あっても UBI を通して使う限り関係ないはず。
mtdblock としてアクセスした場合に 代替されてしまうかも知れないのだが、そういう場合も、UBI を通せば関係ないような気がする。
というわけで UBI もこのままで良いだろう。
追記: どうも良くないようだ。
badblock の処理は 下位レイヤに従う。... どうも badblock が出来にくくするために ウェアレベリンングを行うのであって、badblock が出来てしまえばすなおに代替するようだ。
そうであれば 5 % の 200 個 必要そうだ。これは 4Kpage,2GB の場合で 2Kpage や 4GB なら倍の 400 個。
実際の OOB の代替ブロックを示すデータについて:
OOB に格納する形式は、
struct mtdblk_fake_fsbuf {
unsigned int block_addr_field1;
unsigned int block_addr_field2;
unsigned int lifetime;
};
という形式で、oob のオフセット 2 から格納されている 。
block_addr_field1 , block_addr_field2 に virt_block 番号が入っていて、読み出したとき同じなら正しいデータと見做す。
lifetime は 消去回数。
このデータがどこにあるかというと、各消去ブロックの先頭ページに対する oob 。次のページから CONFIG_MTD_OOB_COPIES 分だけ同じデータがあり、壊れていたら コピーを見ることになっている。
あと、代替ブロックが出来ていたら、代替ブロックの方を常に見ないと整合性が取れない。壊れているという mark はどこにあるのか?
どうも nand_default_block_markbad() という関数でこの処理をやっているらしい。
.. 見てみると、代替する場合は、各消去ブロック の 127ページ 目の oob の先頭から 2 バイトを 0 にする。判断は、先頭の 1 バイトが 0xff 以外だったとき 代替されていると判断する。
(そういえば、バッドブロックのコードは、usbboot にもあった)
これで、OOB の 先頭から 14 バイト分がどう使われるかは分かった。( そして 4Kpage で 8bit-BCH の場合は、 オフセット 24 から 最後まで ECC が入る。)
(つづく)