無線ルータのような組み込み系のシステムではFlashにkernelとrootファイルシステムを入れています。よく使われるFlash(NOR)には二種類あってCFI(Common Flash Interface)というメモリのようにピンの多いタイプとSPI(Serial Peripheral Interface)というピンの少ないタイプがあります。CFIと同じようなパッケージに入ったNANDというFlashもあります。
CFIの方が原始的で通常のメモリのように読めますが、SPIはドライバが無いと読めません。
これらのフラッシュは書き込み時以外read onlyで使用することを前提にしていて、read/writeなファイルシステムを置くと、時期に壊れます。取り外しが大変なので、read/writeなファイルシステムでは使わないことをお勧めします。
CFIとSPIは対になる機能の名称ではないのですが、FreeBSDのドライバがそう呼ばれるので、使っています。CFIはメモリの情報を読み出すための機能になり、SPIはバスの名前です。CFIはFlashは通常ReadOnlyですが特定のアドレスにデータを書き込む事によりデータではなくデバイスの情報が読めるようになり、特定のアドレスにデータを書いて元に戻します。CFIはパラレルと呼ばれる事もあります。
CFIはROMの頃にだんだんいろいろな書き込み使用がつくられて、対応するのをライターソフトにスタティックでもつことが困難になったため、デバイスに持たせるように考えられたものです。
SOCのサポートによりどちらが使えるかは決まってきますが、2000-2005くらいまではほとんどがCFIのタイプを使っていましたが、2005以降はSPIなタイプのFlashも使われるようになりました。
2000年以前はマスクROM/EEPROMを使った製品もあったようですが、容量が小さい上に失敗が許されないなどの制約があったためFlashに置き換わりました。製品ではFlashの一部を設定保存用に利用していたりします。この機能は昔はバッテリーバックアップしたメモリだったんですが、これも制約が多くFlashにより置き換えられました。
2000年くらいの出始めのFlashは書き込み可能回数が少なかったようですが、いまでは10000回以上の製品がほとんどです。
最近のMIPSやARMなSOCを使った無線ルータのFlashにはU-BootなどのブートプログラムとLinuxカーネルとrootファイルシステムと各種設定がFlashに入って出荷されています。
電源投入により起動した時はCFIに含まれているバイナリは直接実行可能ですが、SPIに含まれるバイナリは直接実行できないので、いったん内部ROMで起動してSPIの内容をRAMにコピーして実行されます。SPIの中には圧縮して保存している場合もあります。
文鎮になってしまった無線ルータを復旧(英語ではdebrickというようです)する場合にJTAGでFlashに書き込みをおこなう事がありますが、これはCFIなFlashを対象にしています。JTAGは難易度高く、安定せず使えない事もあります。
もしJTAGでFlashを焼くことができる場合はbootだけを焼いて、bootでkernelやrootfsを焼くのが現実的です。
ブートプログラムでよく使われるU-BootにはFlashの書き込み機能があり、この機能を使いファームウエアのアップデートに失敗し文鎮化した機器を復旧する事ができます。この機能でLinuxなシステムをFreeBSDに置き換えます。U-Bootで読み込みできるイメージは以下のような形式になります。
圧縮kernelと圧縮rootfsはパティングされて、ブロックサイズ単位になります。U-Bootからアクセスされるのはlzmaやgzで圧縮された圧縮kernelだけです。。
この形式のイメージをTFTPでビルドしたサーバからメモリにコピーしてFlashに焼きます。kernelを起動する時には圧縮されたkernelをメモリに展開して、実行を移します。カーネルにFlashを読み込むドライバが入っていなくても起動は可能ですが、rootfsがmountできずに止まってします。
FreeBSDではCFIなFlashはsys/dev/cifに含まれるcfi,cfidというドライバを使って読み込みができます。SPIなFlashはsocに対応したspiドライバとsys/dev/spibusのspibusとsys/dev/flashの下のmx25lで読み込みができます。これらのブロックデバイスをgeomを通してファイルシステムとしてアクセスします。CFIは自動認識しますが、SPIはmx25l.cにサポートチップのIDのリストがあり、このリストに含まれないデバイスは認識されません。
fdtを使ったsys/armでは以下のようなdtsファイルでcfidが使えるようになります。
localbus@1f000000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges = <>;
flash@1f000000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "cfi-flash";
reg = <0x1f000000 0x400000>;
partition@0 {
reg = <0x0 0x20000>;
label = "config";
};
};
};
起動時のログ
cfi0: <AMD/Fujitsu - 4MB> mem 0x1f000000-0x1f3fffff on simplebus0
cfi0: [8x8KB,63x64KB]
cfid0 on cfi0
FreeBSDでcfid0やmx25l0に入っているrootfsを読み込むためのgeomモジュールは以下が用意されています。
モジュール名 | 機能 |
---|---|
geom_map | hintsに書かれた設定を元にFlashの内容をgeomにdisk deviceとして登録するためのモジュール |
geom_redboot | RedBootのパティション情報を元にgeomにdisk deviceとして登録するためのモジュール |
geom_flashmap | FDTの情報を元にスライスを設定するモジュール。 |
geom_uzip | 圧縮されたファイルシステムを展開するためのモジュール。geom_uncompressで提供さ圧縮されていた圧縮形式もマージされています。geom_uncompressはZRouterのAleksandrが作られたようです。 |
geom_mapの設定は以下のようにhintsに書きます。
hint.map.0.at="flash/spi0"
hint.map.0.start=0x00000000
hint.map.0.end=0x00010000
hint.map.0.name="bootloader"
hint.map.0.readonly=1
hint.map.1.at="flash/spi0"
hint.map.1.start=0x00010000
hint.map.1.end=0x00020000
hint.map.1.name="factory"
hint.map.1.readonly=1
hint.map.2.at="flash/spi0"
hint.map.2.start=0x00020000
hint.map.2.end="search:0x00100000:0x10000:.!/bin/sh"
hint.map.2.name="kernel"
hint.map.3.at="flash/spi0"
hint.map.3.start="search:0x00100000:0x10000:.!/bin/sh"
hint.map.3.end=0x007fffff
hint.map.3.name="rootfs"
geom_uzipで利用するのはmap.3ですが、このstart,endが間違っていると正しく展開できません。~~kernelとrootfsのサイズはビルドしなければ決定しないので、一度ビルドしてサイズを確認してhintsを書き直して再ビルドしてイメージの作成が必要だったりします。~~searchで自動設定できました。
展開されると/dev/map/rootfs.uzipとなり以下のkernel optionでマウントされます。
ROOTDEVNAME="cd9660:/dev/map/rootfs.uzip"
上記のdtsでgeom_flashmapとgeom_mapを使った時のブートログはこんなです。
cfid0: slice 00000000-0001ffff: config (127KB)
MAP: No valid partition found at cfid0
MAP: cfid0s.config: 0x20000, data=0x20000 "/dev/map/config"
SPIはFlash以外でも利用されてsipbusを拡張して使っているケースもありますが、影響範囲が大きいのでheadにはマージが難しいのかもしれません。拡張可能なフレームワークを作れると良いのかと思います。
データの保存
以下のようなmapを設定して、Flashの最後の部分をデータ保存用に確保してみます。
hint.map.5.at="flash/spi0"
hint.map.5.start="0x007e0000"
hint.map.5.end=0x007fffff
hint.map.5.name="storage"
この例では64Kのブロックを2つで128K確保しています。ところが、128K読むとエラーになり、どうも最後のブロックが正しく64Kアクセスできないようです。
# dd if=/dev/map/storage of=aaa bs=65536
dd: /dev/map/storage: Invalid argument
1+0 records in
1+0 records out
65536 bytes transferred in 0.113355 secs (578148 bytes/sec)
先頭の64Kだけ読むと問題ありません。
# dd if=/dev/map/storage of=aaa bs=65536 count=1
1+0 records in
1+0 records out
65536 bytes transferred in 0.111372 secs (588442 bytes/sec)
書き込みも同じ状態です。ドライバかgeom関係かspiか何が悪いかは調べてません。
カーネルのエントリーポイント
ビルドしたカーネルのELFファイルにはエントリーポイントがあります。エントリーポイントはreadelfコマンドで確認する事ができます。ブートはこのアドレスに実行を移して、カーネルが実行されます。
microserver % readelf -h Buffalo_WZR-HP-G300NH_kernel
ELF Header:
Magic: 7f 45 4c 46 01 02 01 09 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - FreeBSD
ABI Version: 0
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0x80001120
Start of program headers: 52 (bytes into file)
Start of section headers: 5720716 (bytes into file)
Flags: 0x50001001, noreorder, o32, mips32
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
U-Bootの場合はimageにこのエントリーポイントをヘッダーに入れてあり、これに従いU-Bootがカーネルをブートします。imageを作る時にオプションで設定します。
RedBootの場合は、圧縮カーネルをfisコマンドで焼く時に-eオプションで指定してこのアドレスに実行を移します。
CFEの場合は通常0x80001000から実行されていて変更方法が分からなかったのldscript.mipsを修正してエントリーポイントが0x80001000になるようにカーネルをビルドしてイメージを作るようにしています。
boot方法いろいろ
通常はkernelとrootfsをFlashに焼きます。
Flashが小さくてUSBが使える場合には、rootfsをUSB memoryに置く事が出来ます。この時にUSB memoryであれば容量が確保できるのでrootfsは圧縮せずにisoのまま使えます。
RedBootのAR5312などのターゲットでbootが大きくなりkernelとrootfsがFlashに入らないために、起動時にサーバからtftpでrootfsをFlashに焼いて、その後に同じようにtftpでkernelをメモリに読み込んで起動しています。これらは起動スクリプトに入れてあります。
rootfsをnfsにするという方法もありますが、サーバを常時起動しておく必要があり設定が複雑になるので私は試してません。
最近のarmのワンボードコンピューターはsdからのブートができるものがあるようですが、私は持っていないので試していません。
後日追記
FreeBSDにはdev/nandというFlashのドライバも存在しますが、手元に動作環境がないので、どのようなものかわかりません。
FreeBSDのCFIドライバはエンディアンを自動判定しているのですが、ハードウエアでもエンディアンの変換をおこなっていることがあり、AtherosのSOCで認識できないケースがありました。このためCFI_HARDWAREBYTESWAPというカーネルオプションを追加してありますのでもしCFIを認識できない時はこのオプションをお試しください。
cfiなデバイスでcfiモードから通常モードに戻る時にCFI_BCS_READ_ARRAYを書き込んでいるのだが、これが0xffではうまくいかずRESETの0xf0だとうまくいくデバイス(MX29LV320C)があった。MXのデータシートを見ても0xffは記載が無く0xf0の記載はある。
RESETの0xffと0xf0の処理はNetBSDでは両方使ってワークアラウンドにしていたので、FreeBSDでも同じようにしました。
U-Bootのコマンドを使ってハンドパワーでCFIを確認する方法は以下のような感じです。
ar7100> md 0xbe000000
be000000: 100000ff 00000000 100000fd 00000000 ................
be000010: 1000017f 00000000 1000017d 00000000 ...........}....
be000020: 1000017b 00000000 10000179 00000000 ...{.......y....
be000030: 10000177 00000000 10000175 00000000 ...w.......u....
be000040: 10000173 00000000 10000171 00000000 ...s.......q....
be000050: 1000016f 00000000 1000016d 00000000 ...o.......m....
be000060: 1000016b 00000000 10000169 00000000 ...k.......i....
be000070: 10000167 00000000 10000165 00000000 ...g.......e....
be000080: 10000163 00000000 10000161 00000000 ...c.......a....
be000090: 1000015f 00000000 1000015d 00000000 ..._.......]....
be0000a0: 1000015b 00000000 10000159 00000000 ...[.......Y....
be0000b0: 10000157 00000000 10000155 00000000 ...W.......U....
be0000c0: 10000153 00000000 10000151 00000000 ...S.......Q....
be0000d0: 1000014f 00000000 1000014d 00000000 ...O.......M....
be0000e0: 1000014b 00000000 10000149 00000000 ...K.......I....
be0000f0: 10000147 00000000 10000145 00000000 ...G.......E....
ar7100> mw.w 0xbe0000aa 0x98
ar7100> md 0xbe000000
be000000: 00000000 00000000 00000000 00000000 ................
be000010: 00000000 00000000 00000000 00000000 ................
be000020: 00510052 00590002 00000040 00000000 .Q.R.Y.....@....
be000030: 00000000 00000027 00360000 00000006 .......'.6......
be000040: 00060009 00130003 00050003 00020019 ................
be000050: 00020000 00060000 000100ff 00000000 ................
be000060: 00020000 00000000 00000000 00000000 ................
be000070: 00000000 00000000 0000ffff ffffffff ................
be000080: 00500052 00490031 00330014 00020001 .P.R.I.1.3......
be000090: 00000008 00000000 000200b5 00c50005 ................
be0000a0: 0001ffff ffffffff ffffffff ffffffff ................
be0000b0: ffffffff ffffffff ffffffff ffffffff ................
be0000c0: ffffffff ffffffff ffffffff ffffffff ................
be0000d0: ffffffff ffffffff ffffffff ffffffff ................
be0000e0: ffffffff ffffffff ffffffff ffffffff ................
be0000f0: ffffffff ffffffff ffffffff ffffffff ................
ar7100> mw.w 0xbe000000 0xf0
ar7100> md 0xbe000000
be000000: 100000ff 00000000 100000fd 00000000 ................
be000010: 1000017f 00000000 1000017d 00000000 ...........}....
be000020: 1000017b 00000000 10000179 00000000 ...{.......y....
be000030: 10000177 00000000 10000175 00000000 ...w.......u....
be000040: 10000173 00000000 10000171 00000000 ...s.......q....
be000050: 1000016f 00000000 1000016d 00000000 ...o.......m....
be000060: 1000016b 00000000 10000169 00000000 ...k.......i....
be000070: 10000167 00000000 10000165 00000000 ...g.......e....
be000080: 10000163 00000000 10000161 00000000 ...c.......a....
be000090: 1000015f 00000000 1000015d 00000000 ..._.......]....
be0000a0: 1000015b 00000000 10000159 00000000 ...[.......Y....
be0000b0: 10000157 00000000 10000155 00000000 ...W.......U....
be0000c0: 10000153 00000000 10000151 00000000 ...S.......Q....
be0000d0: 1000014f 00000000 1000014d 00000000 ...O.......M....
be0000e0: 1000014b 00000000 10000149 00000000 ...K.......I....
be0000f0: 10000147 00000000 10000145 00000000 ...G.......E....
ar7100>
ARMでもできました。
Marvell>> mw.w 0xff8000aa 0x98
Marvell>> md 0xff800000
ff800000: 00000000 00000000 00000000 00000000 ................
ff800010: 00000000 00000000 00000000 00000000 ................
ff800020: 00520051 00020059 00400000 00000000 Q.R.Y.....@.....
ff800030: 00000000 00270000 00000036 00040000 ......'.6.......
ff800040: 000a0000 00050000 00040000 00170000 ................
ff800050: 00000002 00000000 00070002 00200000 .............. .
ff800060: 007e0000 00000000 00000001 00000000 ..~.............
ff800070: 00000000 00000000 00000000 00000000 ................
ff800080: 00520050 00300049 00000030 00010002 P.R.I.0.0.......
ff800090: 00040001 00000060 00850000 000200c5 ....`...........
ff8000a0: 00000000 00000000 00000000 00000000 ................
ff8000b0: 00000000 00000000 00000000 00000000 ................
ff8000c0: 00000000 00000000 00000000 00000000 ................
ff8000d0: 00000000 00000000 00000000 00000002 ................
ff8000e0: 00000000 00000000 00000000 00000000 ................
ff8000f0: 00000000 00000000 00000000 00000000 ................
Marvell>> mw.w 0xff800000 0xf0
Marvell>> md 0xff800000
ff800000: 28cd3d45 0059e000 00000003 00000000 E=.(..Y.........
ff800010: 706d6f43 73736572 52206465 53464d4f Compressed ROMFS
ff800020: 85ea64e8 00000000 00000cbb 000002b6 .d..............
ff800030: 706d6f43 73736572 00006465 00000000 Compressed......
ff800040: 01f441ed f40000b4 000004c0 01f441ed .A...........A..
ff800050: f4000df8 00001001 006e6962 01f441ed ........bin..A..
ff800060: f4000000 00000001 00766564 01f441ed ........dev..A..
ff800070: f4000244 0000ef81 00637465 01f441ed D.......etc..A..
ff800080: f4000518 00012181 0062696c 01f441ed .....!..lib..A..
ff800090: f4000000 00000001 00746e6d 01f441ed ........mnt..A..
ff8000a0: f4000000 00000001 636f7270 01f441ff ........proc.A..
ff8000b0: f4000000 00000001 00706d74 01f481b4 ........tmp.....
ff8000c0: f40f9f60 03f39002 616d4975 00006567 `.......uImage..
ff8000d0: 01f441ed f4000014 00019d41 00727375 .A......A...usr.
ff8000e0: 01f4a1ff f4000003 04f1d381 00726176 ............var.
ff8000f0: 01f441ed f4000094 0001a401 00777777 .A..........www.
Comcerto-1000 > md.b 0x20000000
20000000: 12 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 ................
20000010: 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 ................
20000020: 20 01 00 84 80 01 00 84 e0 01 00 84 40 02 00 84 ...........@...
20000030: a0 02 00 84 00 03 00 84 60 03 00 84 78 56 34 12 ........`...xV4.
Comcerto-1000 > mw.w 0x200000aa 0x98
Comcerto-1000 > md.b 0x20000000
20000000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
20000010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
20000020: 51 00 52 00 59 00 02 00 00 00 40 00 00 00 00 00 Q.R.Y.....@.....
20000030: 00 00 00 00 00 00 27 00 36 00 00 00 00 00 03 00 ......'.6.......
OpenOCDでハンドパワーでCFIの確認(KS8695)
> mwb 0x020000aa 0x98
> mdb 0x02000000 64
0x02000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00
0x02000020: 51 00 52 00 59 00 02 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 27
00 36 00 00 00 00 00 05 00
> mwb 0x02000000 0xf0
> mdb 0x02000000 64
0x02000000: 12 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f
e5 14 f0 9f e5 14 f0 9f e5
0x02000020: 20 01 70 00 80 01 70 00 e0 01 70 00 40 02 70 00 a0 02 70 00 00 03 70
00 60 03 70 00 ef be ad de
AR7161などSPIのFlashを二つサポートしているケースもありますが、これらは別々のボリュームとなるので、連続して使う事はできません。U-Boot的には連続して扱っている様なので二つ目の扱いがちょっと厄介です。
spi0: <AR71XX SPI> at mem 0x1f000000-0x1fffffff on nexus0
spibus0: <spibus bus> on spi0
mx25l0: <M25Pxx Flash Family> at cs 0 mode 0 on spibus0
mx25l0: mx25ll128, sector 65536 bytes, 256 sectors
random: harvesting attach, 8 bytes (4 bits) from mx25l0
mx25l1: <M25Pxx Flash Family> at cs 1 mode 0 on spibus0
mx25l1: mx25ll128, sector 65536 bytes, 256 sectors
古いadm5120のボードに付いている、TE28F160とJS28F160をUrJTAGで焼いたらflashが見えなくなってしまいました。これは誤ったデータを焼いたため初期化がおこなわれず、flashが見えなくなったようです。以下のページにあるようにurjtagで初期化をおこなったところ見えるようになりました。
CFIのピン配置
古いCFIなFlashはピン配置が違う物があります。
上が古いIntelの28F160C3などで下がMXの29LV320です。基板はどちらかに対応して物か、切り変わり時期のものはまれに両方に対応できるようにでパターンを用意して0オームの抵抗で切り替えられるようにした物もあるようです。
IDTのRC32334という古いSOCにはメモリコントローラにWrite Protect/Write Sizeがあり、これを操作しないとCFIが見えませんでした。
bootを作るときなど、SPI Flashを抜き差しできるようにしてみることがあります。
こつはSOPのパターンははがれやすいので、DIPの足にリードを先に付けてからSOP側をハンダ付けすると良いです。
SPI Flashをオンボードで焼く方法
SPI FlashのVCCが本体のVCCと切り離せれば、実装したまま書き換えが可能です。0Ωの抵抗がSPI FlashのVCC付近にあればそれを外してライターを接続します。
16ピンですがこんな感じにしてflashromが使えます。
長いこと、他の方がやってるの見て、どうやるんだろうと思っていたのだが、ある基板をみて思いついた。製品開発時にも使われる手法なのかもしれない。
昔あったMindspeedという会社のComcertoというarmのSOCはCFIを汎用のExpansion Busという機構にぶら下げていました。この機構はCFI以外も汎用のIOとして使え、また一つはNAND用にも使えるようです。
CFIでJTAGが使える場合はオリジナルのファームのバックアップが可能ですが、それが出来ない場合でFlashにリカバリー用などの通常使用しない領域があれば、その領域に新しいイメージを焼いて試せば、オリジナルを残したまま、Hackすることが出来ます。
# ブートローダーがとまらないとき
SPI Flashでブートローダーが止められず、OSが起動してしまう場合は、一旦SPI Flashを外して中身を吸い出して、ブートローダーだけを残して消して貼り付け直すと、ブートローダーがOSを見つけられず、コマンドに落とせるケースもあります。
ブート時にキーボードをいろいろ押すと止まることもあります。止まったらautobootしないようにsetenvなどをすると良いです。