reComputer J2012(Jetson Xavier NX)をリードオンリー化

Seeed K.K.の中井です。

reComputer J2012でデモを作成していたのですが、開発中はrebootコマンドなどで安全に再起動やシャットダウンができるのですが、デモ中や設置・撤収時はやはり電源ぶち切りになってしまいます。デモ稼働中に電源をぶち切りしてしまった場合にはファイルシステムが壊れてしまう可能性があるので、やはりリードオンリー化が必要ですね。

早速overlayrootをインストールして試してみましたが、正常に利用することはできませんでした。

ということでinitrdを変更してreComputer J2012をリードオンリー化してみたのですが、ググってみてもJetson L4Tのリードオンリー化の情報が少なかったので、この記事に手順をまとめてみました。

リードオンリー化

リードオンリー化する方法として、今回は「OverlayFS」を利用したいと思います。 参考にした手順は下記になります。

elinux.org

やることは、下記3つです。

  • Linux KernelにOverlayFSを組込む (デフォルト状態ではモジュールとなっている)
  • initrdイメージをカスタマイズしてルートファイルシステムにOverlayFSを利用するように変更
  • 作成したイメージで起動できるように変更

作業の前に

Linux KernelのバージョンとJetson Linux Driver Package (L4T)のバージョンを確認しておきます。 Linux Kernelのバージョンは「4.9.299-tegra」、L4Tのバージョンは「32.7.3」でした。

$ uname -a
Linux reComputerJ2012 4.9.299-tegra #1 SMP PREEMPT Tue Nov 22 09:24:30 PST 2022 aarch64 aarch64 aarch64 GNU/Linux
$ jetson_release
Software part of jetson-stats 4.1.4 - (c) 2023, Raffaello Bonghi
Model: NVIDIA Jetson Xavier NX Developer Kit - Jetpack 4.6.3 [L4T 32.7.3]
NV Power Mode: MODE_10W_DESKTOP - Type: 5
jtop:
 - Version: 4.1.4
 - Service: Active
Libraries:
 - CUDA not installed!
 - cuDNN: Not installed
 - TensorRT: Not installed
 - VPI: Not installed
 - Vulkan: 1.2.70
 - OpenCV not installed!

Linux Kernelの変更

reComputer J2012のeMMCではLinux Kernelなどをビルドするには容量が不足する可能性があるので、この記事では作業用PC(Linux)を用意してクロスビルドすることにします。とくに特別なビルド環境が必要なわけではないのですが公式ドキュメントに目を通しておくと良いと思います。

さて、先ほど確認したLinux Kernelバージョンをもとに下記からソースコードとツールチェーンをダウンロードします。

developer.nvidia.com

とりあえず必要なのは下記。

作業用PCでダウンロードしておきます。(なぜこのようなファイル名になっているのか。。)

$ ls
l4t-gcc-7-3-1-toolchain-64-bit 
remack-sdksjetpack-463r32releasev73sourcest186publicsourcestbz2

一応アーカイブの種類を確認してファイル名を変更しておきます。

$ file remack-sdksjetpack-463r32releasev73sourcest186publicsourcestbz2
remack-sdksjetpack-463r32releasev73sourcest186publicsourcestbz2: bzip2 compressed data, block size = 900k
$ file l4t-gcc-7-3-1-toolchain-64-bit
l4t-gcc-7-3-1-toolchain-64-bit: XZ compressed data
$ mv remack-sdksjetpack-463r32releasev73sourcest186publicsourcestbz2 remack-sdksjetpack-463r32releasev73sourcest186publicsources.tbz2
$ mv l4t-gcc-7-3-1-toolchain-64-bit l4t-gcc-7-3-1-toolchain-64-bit.tar.xz

ダウンロードしたソースコードのアーカイブとツールチェーンを展開します。

$ tar -xf remack-sdksjetpack-463r32releasev73sourcest186publicsources.tbz2
$ tar -xf l4t-gcc-7-3-1-toolchain-64-bit.tar.xz
$ ls -d
Linux_for_Tegra
gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu
l4t-gcc-7-3-1-toolchain-64-bit.tar.xz
remack-sdksjetpack-463r32releasev73sourcest186publicsources.tbz2

後程Linux Kernelをビルドするときにクロスコンパイラを指定するので、ここでクロスコンパイラのプリフィックスを設定しておきます。PATHが通っていないのでフルパスで指定しておきます。

$ export CROSS_COMPILE_AARCH64=$(pwd)/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-

Linux Kernelのソースコードを展開します。

$ cd Linux_for_Tegra/source/public/
Linux_for_Tegra/source/public$ tar -xf kernel_src.tbz2 

続いてコンフィグレーションを行います。 デフォルト状態(tegra_defconfig)では、OverlayFSはモジュール(overlay.ko)となるように設定されているので、コンフィグレーションメニューからKernelイメージに組み込まれるように変更します。

Linux_for_Tegra/source/public$ cd kernel/kernel-4.9/
Linux_for_Tegra/source/public/kernel/kernel-4.9$ make ARCH=arm64 CROSS_COMPILE=$CROSS_COMPILE_AARCH64 tegra_defconfig
Linux_for_Tegra/source/public/kernel/kernel-4.9$ make ARCH=arm64 CROSS_COMPILE=$CROSS_COMPILE_AARCH64 menuconfig

    File systems  --->
<*> Overlay filesystem support  ←<M>から<*>に変更

正しく変更できていれば、下記のように"y"になります。(モジュールとなっている場合は"m")

Linux_for_Tegra/source/public/kernel/kernel-4.9$ grep OVERLAY_FS .config
CONFIG_OVERLAY_FS=y

続いてビルドします。LOCALVERSIONの付与を忘れずに。

Linux_for_Tegra/source/public/kernel/kernel-4.9$ make ARCH=arm64 CROSS_COMPILE=$CROSS_COMPILE_AARCH64 LOCALVERSION="-tegra"

ビルドが完了するとarch/arm64/boot/Imageにバイナリが作成されるので、reComputer J2012本体にコピーしておきます。下記はSCPで転送した例です。※reComputer側のユーザー名とIPアドレスは環境に合わせて変更する必要があります

Linux_for_Tegra/source/public/kernel/kernel-4.9$ scp arch/arm64/boot/Image jetson@192.168.1.127:/home/jetson/Image.new

initrdの変更

initrdについては「Drivers Package (BSP)」をダウンロード・展開して、 Linux_for_Tegra/nv_tegra/l4t_deb_packages/nvidia-l4t-initrd_32.7.3-20221122092958_arm64.deb に含まれるものを変更するのが良いと思いますが、基本的に同じもののはずなので実機で使用しているinitrdイメージをコピーして変更します。

下記はSCPで実機から作業PCへコピーしている例です。一応作業PC側ではinitrd.origにリネームしています。

$ scp jetson@192.168.1.127:/boot/initrd ./initrd.orig

任意のディレクトリ(ここではinitrd_files)を作成しinitrdを展開します。

$ mkdir -p initrd_files
$ cd initrd_files/
initrd_files$ gzip -cd ../initrd.orig | cpio -imd --quiet

初期化スクリプトのinitを変更します。ここではテキストエディタにnanoを使っていますが、お好きなもので構いません。

initrd_files$ nano init
--- init.orig   2023-02-07 13:59:22.646934066 +0900
+++ init        2023-02-07 14:28:56.296346799 +0900
@@ -142,11 +142,6 @@
                echo "ERROR: ${rootdev} not found" > /dev/kmsg;
                exec /bin/bash;
        fi
-       mount /dev/${rootdev} /mnt/;
-       if [ $? -ne 0 ]; then
-               echo "ERROR: ${rootdev} mount fail..." > /dev/kmsg;
-               exec /bin/bash;
-       fi;
 elif [[ "${rootdev}" == sd* ]]; then
        if [ ! -e "/dev/${rootdev}" ]; then
                while [ ${count} -lt 50 ]
@@ -214,13 +209,36 @@
 # Disable luks-srv TA
 nvluks-srv-app -n > /dev/null 2>&1;

+# create /mnt as mount point
+mount -t tmpfs inittemp /mnt;
+mkdir /mnt/lower;
+mkdir /mnt/rw;
+mount -t tmpfs root-rw /mnt/rw;
+mkdir /mnt/rw/upper;
+mkdir /mnt/rw/work;
+mkdir /mnt/lower;
+mount -o ro /dev/${rootdev} /mnt/lower;
+if [ $? -ne 0 ]; then
+       echo "ERROR: ${rootdev} mount fail..." > /dev/kmsg;
+       exec /bin/bash;
+fi;
+mkdir /mnt/newroot;
+mount -t overlay -o lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work overlayfs-root /mnt/newroot;
+if [ $? -ne 0 ]; then
+       echo "ERROR: overlay fail..." > /dev/kmsg;
+       exec /bin/bash;
+fi;
+
 echo "Rootfs mounted over ${rootdev}" > /dev/kmsg;
+mkdir /mnt/proc;
+mkdir /mnt/sys;
+mkdir /mnt/dev;
 mount -o bind /proc /mnt/proc;
 mount -o bind /sys /mnt/sys;
 mount -o bind /dev/ /mnt/dev;
-cd /mnt;
+cd /mnt/newroot;
 cp /etc/resolv.conf etc/resolv.conf

 echo "Switching from initrd to actual rootfs" > /dev/kmsg;
-mount --move . /
+
 exec chroot . /sbin/init 2;

initrdを作成し直して実機にコピーします。

initrd_files$ find . | cpio -H newc --owner root:root -o | gzip -9 -n > ../initrd.new
initrd_files$ cd ..
$ scp initrd.new jetson@192.168.1.127:/home/jetson/

作成したイメージで起動できるように変更

ここからは実機での作業です。

作成したLinux Kernelイメージ(Image.new)とinitrdイメージ(initrd.new)をbootディレクトリに配置、その後にextlinux.confを変更して作成した新しいイメージで起動するように変更していきます。

jetson@reComputerJ2012:~$ sudo cp Image.new initrd.new /boot/
jetson@reComputerJ2012:~$ cd /boot/extlinux/
jetson@reComputerJ2012:/boot/extlinux$ sudo vi extlinux.conf
--- extlinux.conf.orig  2023-02-07 14:44:23.728124577 +0900
+++ extlinux.conf       2023-02-07 15:20:19.949131799 +0900
@@ -5,6 +5,12 @@

 LABEL primary
       MENU LABEL primary kernel
+      LINUX /boot/Image.new
+      INITRD /boot/initrd.new
+      APPEND ${cbootargs} root=/dev/nvme0n1p1 rw rootwait rootfstype=ext4 console=ttyTCU0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0
+
+LABEL original
+      MENU LABEL original kernel
       LINUX /boot/Image
       INITRD /boot/initrd
       APPEND ${cbootargs} root=/dev/nvme0n1p1 rw rootwait rootfstype=ext4 console=ttyTCU0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0

※APPENDの「root=/dev/nvme0n1p1」はルートデバイスとしてM.2 NVMe SSDを利用しているためです。
 eMMCを利用している場合には「root=/dev/mmcblk0p1」などにする必要があります。

上記のようにextlinux.confを修正すると通常起動時には「primary kernel」が選択されOverlayFSが有効となった状態(=リードオンリー)で起動します。 シリアルコンソールを接続している場合には、下記のように「primary kernel」と「original kernel」を選択することができ、「original kernel」を選択するとOverlayFSが無効となった状態で起動します。

I> L4T boot options
I> [1]: "primary kernel"
I> [2]: "original kernel"
I> Enter choice:

動作確認

これで作業は完了です。

実機を再起動させるとOverlayFSが有効となりリードオンリー化されて起動します。mountコマンドを実行するとoverlayが有効となっていることが確認できます。

jetson@reComputerJ2012:~$ mount | grep overlay
overlayfs-root on / type overlay (rw,relatime,lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work)

変更履歴

日付 変更者 変更内容
2023/2/8 m.nakai 作成