こんにちは、scottです。
今日の3分ハッキングでは組み込み系機器でよく使われているブートローダであるDas U-Boot(以下U-Bootと記述)で、LinuxでNANDフラッシュのいちファイルシステムであるUBIFS[1]内に保存されているLinux (Debian 9)を起動してみたいと思います。
......といっても、今時の機器ですと、記憶装置としてNANDフラッシュを直接実装するのではなく、SDカードのようにソフトウェアから手軽に扱えるeMMCを実装することも多く、(話題のRaspberry Piに至っては(micro)SDカードそのものですね!)そのような環境ではUBIFSを使うこともない(というか使えない)と思いますが、ググってみてもあまり情報が日本語で出てこなかったのでメモついでに書いておきたいと思います。
U-Bootの用意
まずはUBIFS対応のU-Bootを用意します。
ほとんどの環境ではUBIFS対応されていないU-Bootが使われていると思いますので、その場合(ubifsload等のコマンドが無い場合)はUBIFS対応のU-Bootのバイナリをつくりましょう!
もちろん、対応しているU-Bootが既に手元にある場合はこの章を飛ばしてかまいません。(思ったよりこの章が長くなってしまった......。)
私が最近試したU-Bootは2017.09で、Marvell社が開発したSheevaplugというコンピュータ上で動かしていましたので、以降これを前提に書きます。一部機種依存なところがありますが、コマンドの使い方等はお役立てできると思います。
まず一通りお好みでconfigを設定した後、make nconfig
で Command line interface
内にある以下の2つにチェックを入れましょう。
Enable UBI - Unsorted block images commands
Enable UBIFS - Unsorted block images filesystem commands
(Enable UBI
...にチェックを入れると出てくる)
そろそろ文字だらけで記事が殺風景になってきていますね......無駄に図にすると以下のような感じです。
これだけでUBIのサポートやMTD partition supportなども自動的にチェックが入るので楽です。
また、Command line interface
→ Boot commands
から bootz
コマンドを有効にしておきます。(Debianのvmlinuzを読む時に使います。)
次にビルド......といきたい所ですが、いくつか注意ポイントがあったので手当て(patch)しておきます。
- gccのバージョンによってはU-Bootが使うCの規格とズレが生じてコンパイルに支障が出ることがあったので明示的に指定しておく(ついでに
CROSS_COMPILE
も書いちゃう)
diff a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,9 @@
# SPDX-License-Identifier: GPL-2.0+
#
+CROSS_COMPILE=arm-none-eabi-
+KCFLAGS=-std=gnu90
+
VERSION = 2017
PATCHLEVEL = 09
SUBLEVEL =
- Sheevaplug固有?: なぜか最適化オプションを-Osや-O2にするとまったくU-Bootを起動できなくなる[要調査]ので、-O1にする(以下のpatchでは
General setup
→Optimize for size
が無効の前提)
diff a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -590,7 +590,7 @@ ifeq ($(CONFIG_XTENSA),)
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -Os
else
-KBUILD_CFLAGS += -O2
+KBUILD_CFLAGS += -O1
endif
KBUILD_CFLAGS += $(call cc-option,-fno-stack-protector)
- Sheevaplug固有: U-Boot形式でくるまれていないinitrdに対応していると言いはっておく(さもなくばDebian上で生成したinitrd.imgを無加工で読み込めない)
diff a/include/configs/mv-common.h b/include/configs/mv-common.h
--- a/include/configs/mv-common.h
+++ b/include/configs/mv-common.h
@@ -66,6 +66,7 @@
#define CONFIG_CMDLINE_TAG 1 /* enable passing of ATAGs */
#define CONFIG_INITRD_TAG 1 /* enable INITRD tag */
#define CONFIG_SETUP_MEMORY_TAGS 1 /* enable memory tag */
+#define CONFIG_SUPPORT_RAW_INITRD 1
#define CONFIG_SYS_CBSIZE 1024 /* Console I/O Buff Size */
- 私固有: NANDフラッシュをカツカツに使っていたので、むりやり調整
diff a/include/configs/sheevaplug.h b/include/configs/sheevaplug.h
--- a/include/configs/sheevaplug.h
+++ b/include/configs/sheevaplug.h
@@ -23,7 +23,10 @@
/*
* Standard filesystems
*/
-#define CONFIG_SYS_MVFS
+/*#define CONFIG_SYS_MVFS*/ /* depends on bzip2 */
+/* following two configs are done by CONFIG_SYS_MVFS in mv-common.h */
+#define CONFIG_MTD_DEVICE /* required for mtdparts commands */
+#define CONFIG_MTD_PARTITIONS
/*
* mv-plug-common.h should be defined after CMD configs since it used them
@@ -42,8 +42,8 @@
* it has to be rounded to sector size
*/
#define CONFIG_ENV_SIZE 0x20000 /* 128k */
-#define CONFIG_ENV_ADDR 0x80000
-#define CONFIG_ENV_OFFSET 0x80000 /* env starts here */
+#define CONFIG_ENV_ADDR 0x60000
+#define CONFIG_ENV_OFFSET 0x60000 /* env starts here */
/*
* Default environment variables
さて、手当てが終わったら、ビルドしましょう。
host$ make
CHK include/config/uboot.release
CHK include/generated/version_autogenerated.h
CHK include/generated/timestamp_autogenerated.h
...
(中略)
...
LD u-boot
OBJCOPY u-boot.srec
OBJCOPY u-boot-nodtb.bin
COPY u-boot.bin
SYM u-boot.sym
MKIMAGE u-boot.kwb
CHK include/config.h
CFG u-boot.cfg
CFGCHK u-boot.cfg
host$
できましたね。Sheevaplug向けの設定の場合はu-boot.kwb
というファイルをNANDフラッシュの先頭に書くことで、次回起動時から今ビルドしたU-Bootを使ってくれるようになります。
ちなみに私は、いきなりNANDフラッシュに書いて文鎮になるのは怖かったので、その前にOpenOCD 0.2.0(とても古い)をVirtualBox 5系(新しい)上で動くVineLinux 4.2(とても古い)上で動かしてJTAG経由でu-boot.kwb
をSDRAM上にロードして確認していました[2]。新しめのOpenOCD(0.10.0など)では動かなかったので、このようなとても古い環境を使いました。
ファイルシステムの用意
がんばってUBIFS上に用意しましょう。その説明は一本の記事にできるくらい長くなると思うので、今回は割愛します。(めんどくさいわけではない)
ただし今回はUBIFS内のカーネルファイルを起動しようとしているので、 /boot
を別パーティションにする必要はありません。
また、次に説明するコマンドを簡単・変更不要にするために以下のsymlinkを /
に用意しておきます。
/vmlinuz
→boot/vmlinuz-4.9.0-5-marvell
/initrd.img
→boot/initrd.img-4.9.0-5-marvell
/devicetree.dtb
→usr/lib/linux-image-4.9.0-5-marvell/kirkwood-sheevaplug.dtb
リンク先パスは導入しようとしているLinuxのバージョンや環境によって適宜読み替えてください。
なお最初2つ(vmlinuz
, initrd.img
)は、Debianのlinux-image系のパッケージをインストールすると自動で作成されるはずなので、実質最後の devicetree.dtb
のみ手動で作成することになるはずです。
【本題】U-BootからUBIFSを操作
というわけで御膳立てが無駄に長くなってしまいましたが、U-BootでUBIFS内にあるLinuxを起動するコマンドをゆったりと紹介したいと思います。
今回扱うNANDフラッシュの中身は以下のようになっているとします。
まずはパーティションを定義しましょう。
setenv mtdparts mtdparts=orion_nand:384k(uboot)ro,128k(config),4608k(dummy),-(fs)
上記パラメータ(パーティション構成、サイズ)は私の場合なので、適宜アドレス・サイズは変更してください。ここでは"fs"(384k+128k+4608k=5120k ~ NANDフラッシュ最後まで)内にUBIFSが鎮座しているとします。
mtdparts
という変数に mtdparts=
から始まる内容を設定するのがすごくモヤっとしますが、そういう仕様なのでしかたありません。
続いて、UBIFSなUBI volumeがどのパーティションに入っているかをU-Bootに教えます。
ubi part nand0,3
ここで nand0,3
の 3
の部分には、UBIFSがあるパーティションを0オリジンで指定します。今回は mtdparts
で0オリジンで3番目にUBIFSを置いているので 3
としています。(fs
のように名前で指定できなさそうなのが悲しい。)
これを実行し正しく終了すると以下のようなログが出てくると思います。
ubi0: attaching mtd1
ubi0: scanning is finished
ubi0: attached mtd1 (name "mtd=3", size 507 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 129024 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 512
ubi0: VID header offset: 512 (aligned 512), data offset: 2048
ubi0: good PEBs: 4056, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 1, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 264/141, WL threshold: 4096, image sequence number: 298739845
ubi0: available PEBs: 0, total reserved PEBs: 4056, PEBs reserved for bad PEB handling: 40
そうしたら、どのUBI volumeをUBIFSとして扱っていいかをU-Bootに教えます。
ubifsmount ubi0:rootfs
ここで rootfs
の部分にはUBI volume名を指定します。あなたの環境に合うように適宜読み替えてください。
ここまで来ると、UBIFS内を覗けるようになっているはずです。 ubifsls /bin
などとしてみると確認できると思います。
それでは本題のLinuxのカーネル、initramfs、FDTのメモリ上への読み込みをしましょう。
ubifsload 800000 /vmlinuz
ubifsload c00000 /initrd.img
ubifsload f00000 /devicetree.dtb
800000
などの部分にはロード先アドレス、 /vmlinuz
などの部分にはロードしたいファイルを指定します。それぞれ別ファイルやU-Boot自体に被らないように余裕を持たせたアドレスを指定してあげましょう。
釈迦に説法かもしれませんが、環境によっては 0
番地以外にSDRAMがマップされているかもしれないので注意しましょう。
カーネル等がロードできたら、カーネルコマンドラインを設定してあげましょう。
set bootargs ${mtdparts} console=ttyS0,115200 root=ubi0:rootfs ro rootfstype=ubifs ubi.mtd=fs
ここで rootfs
の部分はUBI volume名、fs
の部分はUBIなパーティションの名前(mtdparts
でつけた名前)を指定します。
console
のパラメータ変更や別のパラメータの追加はお好みでどうぞ。
さてここまで来たらカーネルを起動するほかないので、やっちゃいましょう!
bootz 800000 c00000:300000 f00000
パラメータの数値はそれぞれ以下の通りです。
800000
: vmlinuzのアドレスc00000
: initrd.imgのアドレス300000
: initrd.imgのサイズ(本来より大きくても大丈夫のようです)f00000
: dtbのアドレス
これを実行したら、おそらくLinuxが起動されてくると思います 。できなかったら......ごめんなさい。
上記のコマンド群をまとめると、以下のようになります。
setenv mtdparts mtdparts=orion_nand:384k(uboot)ro,128k(config),4608k(dummy),-(fs)
ubi part nand0,3
ubifsmount ubi0:rootfs
ubifsload 800000 /vmlinuz
ubifsload c00000 /initrd.img
ubifsload f00000 /devicetree.dtb
set bootargs ${mtdparts} console=ttyS0,115200 root=ubi0:rootfs ro rootfstype=ubifs ubi.mtd=fs
bootz 800000 c00000:300000 f00000
※パラメータはこれまでに紹介したままなので適宜読み替えてください。
うまく起動できたら、mtdparts
変数を設定した後、bootcmd
変数に ubi part
コマンド以降の内容をセミコロン区切りで ubi part nand0,3; ubifsmount ...; ...; bootz ...
のように設定し、saveenv
コマンドで保存しておけば、次回から自動で起動するようになります。(ここでsymlinkが活きてきます。)
なお bootcmd
変数を設定する際、たとえば set bootargs ...
を含めようとしている場合、 $
をエスケープしてあげる必要があると思うので、そこだけ注意してください。
カーネル等のロード先アドレスに余裕を持たせて設定しておけば、カーネル、initrd.imgやdtbファイルの更新時にU-Bootの変数を書き換える必要がないのでおすすめです。
それでは。
UBIFS: NANDフラッシュの特性を考慮したファイルシステムのひとつ。NANDフラッシュではHDDなどと違い、同じ場所に何度もデータを書き込むと、その場所にはデータが正しく書けなくなるという特性がある。UBIFSのようなファイルシステムには他にjffs2、yaffsなどがある。UBIFSはjffs2に比べて高速マウントできるなどの特長がある。詳しくは
WebGoogle検索で。 ↩︎OpenOCD使ってJTAG経由で確認: これは本当にメモ中のメモなんですが、
reset
→init
→sheevaplug_init
→load_image u-boot.kwb 0x5FFE00
→verify_image u-boot.kwb 0x5FFE00
→resume 0x600000
といった感じのコマンドをOpenOCDに送っていました。0x5FFE00というのは、U-Bootのロードアドレスおよびエントリポイントが0x600000で、u-boot.kwb
のヘッダが(U-Boot 2017.09時点で)0x200バイトであるためそのぶん引いた値です。なおreset
後は毎回u-boot.kwb
をload_image
する必要があります。なぜならreset
~sheevaplug_init
間はSDRAMのリフレッシュが止まっており、1~2秒であってもメモリ上のデータは化けているからです! 私はこれに気付かず数十分を無駄にしました。 ↩︎