Jetson Linux R36.2のkernelをビルドしてみた

# はじめに

去年 NVIDIA から Jetson Orin 向けの [JetPack 6.0 Developer Preview](https://developer.nvidia.com/embedded/jetpack-sdk-60dp) ([Jetson Linux R36.2](https://developer.nvidia.com/embedded/jetson-linux-r362)) がリリースされました。

遅ればせながらちょっとずついじっていこうと思います。
色々深いことをやろうとすると kernel カスタマイズは必要になってくるので、kernel とドライバのビルドをやってみました。

# 要約

- Jetson Linuxの kernel と module をビルドしてみたよ
- 公式手順には乗ってないけど `./nvbuild.sh` でもビルドできたよ
- R36.2からは kernel と Jetson 向けドライバはソースツリーが完全に分離したよ
- Jetson向けのドライバは kernel config では有効/無効の変更ができないよ

# 先人たちに感謝

先人たちのトレースになりますが、自分で手を動かさないと理解できない人なので、トレースしながら気になる点をメモしていこうと思います。
以下の記事を参考にしました。

https://qiita.com/uzuna/items/e82a6d669cc6ea7be7eb

公式サイトはこちら

- [Jetson Linux R36.2](https://developer.nvidia.com/embedded/jetson-linux-r362)
- [NVIDIA Jetson Linux Developer Guide](https://docs.nvidia.com/jetson/archives/r36.2/DeveloperGuide/index.html)

# 準備

## ホスト環境

公式サイトの[QuickStart](https://docs.nvidia.com/jetson/archives/r36.2/DeveloperGuide/IN/QuickStart.html#assumptions)によると Ubuntu 22.04LTS か 20.04LTS を使う前提になっているようでしたが、手元の環境が Ubutnu 23.10 のため、23.10 で試しました。

> You have a Jetson developer kit and a separate Linux (Ubuntu 22.04 or Ubuntu 20.04) host system.

公式ドキュメントの[Prerequisites](https://docs.nvidia.com/jetson/archives/r36.2/DeveloperGuide/SD/Kernel/KernelCustomization.html#prerequisites)では以下のパッケージをインストールすればビルドできるとのことです。私の環境では、すでにビルド関係のパッケージが入っていたので検証はできていないです。エラーが出たら都度必要なパッケージを入れてみてください。

```
$ sudo apt install git-core
$ sudo apt install build-essential bc
```

## ファイルのダウンロード

kernel のソースとビルドに必要な toolchain (コンパイラ等)をダウンロードします。
以下の Jetson Linux のサイトの「Downloads and Links」から以下の２つをダウンロードします。

- Driver Package (BSP) Sources
    - → public_sources.tbz2
- Bootlin Toolchain gcc 11.3
    - → aarch64--glibc--stable-2022.08-1.tar.bz2

https://developer.nvidia.com/embedded/jetson-linux-r362

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3036044/d24277ad-9672-11fe-4e4a-178c6952d4f7.png)

## Toolchainの展開

以下の手順では作業ディレクトリを `$WORK` とします。

まず、作業ディレクトリを作成し、コンパイラ類である aarch64--glibc--stable-2022.08-1.tar.bz2 を展開します。

```
$ mkdir -p $WORK
$ cd $WORK
$ tar xf PATH/TO/aarch64--glibc--stable-2022.08-1.tar.bz2
```

`aarch64--glibc--stable-2022.08-1` というディレクトリが作られ、その下に toolchain が展開されます。

## ソースツリーの展開

[公式のビルド手順](https://docs.nvidia.com/jetson/archives/r36.2/DeveloperGuide/SD/Kernel/KernelCustomization.html#to-manually-download-and-expand-the-kernel-sources)を参考にアーカイブを展開していきます。


作業ディレクトリに public_sources.tbz2 を展開します。
公式の手順では `<installer-path>/Linux_for_Tegra/..` と、Linux_for_Tegraの展開先に上書きする形で展開していますが、特に他のディレクトリに依存していないみたいなので作業ディレクトリに直に展開してしまいます。

```
$ cd $WORK
$ tar xf PATH/TO/public_sources.tbz2
```

`Linux_for_Tegra/source/` 以下にソースのアーカイブが展開されます。
たくさんありますが、kernelとmoduleに関連しているのは以下の 3 つだけのようです。

- kernel_src.tbz2
- kernel_oot_modules_src.tbz2
- nvidia_kernel_display_driver_source.tbz2

Linux_for_Tegra/source 以下に展開する手順になっているのですが、source 直下にもいろいろスクリプトとかMakefileが展開されて、管理が難しくなりそうなので、別ディレクトリに展開します。

`$WORK/source` というディレクトリを作成し、その下にアーカイブを展開します。

```
$ mkdir -p $WORK/source
$ cd $WORK/source
$ tar xf $WORK/Linux_for_Tegra/source/kernel_src.tbz2
$ tar xf $WORK/Linux_for_Tegra/source/kernel_oot_modules_src.tbz2
$ tar xf $WORK/Linux_for_Tegra/source/nvidia_kernel_display_driver_source.tbz2
```

展開後は以下のようなファイル/ディレクトリが作成されます。

```
$WORK/source/
├── Makefile
├── generic_rt_build.sh
├── hardware/
├── hwpm/
├── kernel/
├── kernel_src_build_env.sh
├── nvbuild.sh
├── nvcommon_build.sh
├── nvdisplay/
├── nvethernetrm/
├── nvgpu/
└── nvidia-oot/
```

R36.2 では kernel の下はクリーンなカーネルが格納されていて(R36.2ではUbuntu Jammyのkernel)、Jetson用のコードは `nvidia-oot` に括りだされているのが特徴的です(oot は out-of-tree の略)。

R35.4.1 までは Jetson 用の処理がマージされた形で kernel-tree が提供されていたのですが、kernel バージョンをユーザが(ある程度)自由に選択できるように完全に別ツリーの形で提供されています。

これは linux の driver を書いてた身からすると、結構挑戦的な変更のように思います。
かなりきれいにドライバを設計しないと、kernel 側と oot 側に分けるのが難しそうな気がしてます。
(kernel側の共通コードをちょっといじって対応するとか、足りてないAPIを勝手に追加して対応といった dirty hack が使えない)
さすが NVIDIA。Jeston の固有コードを積極的に upstream にマージしていっているだけありますね。

# 公式手順でビルド

ソースやtoolchainの準備ができたのでビルドしていきます。

## kernel本体のビルド

[Building the Jetson Linux Kernel](https://docs.nvidia.com/jetson/archives/r36.2/DeveloperGuide/SD/Kernel/KernelCustomization.html#building-the-jetson-linux-kernel
)に沿って make します。

toolchain は `$WORK/aarch64--glibc--stable-2022.08-1` 以下に展開されているので、`CROSS_COMPILE=` でフルパスで参照します。

今回は real-time ではない normal な kernel をビルドするため、公式の2の手順(`./generic_rt_build.sh "enable"`)はスキップします。

```
$ cd $WORK/source
$ export CROSS_COMPILE=$WORK/aarch64--glibc--stable-2022.08-1/bin/aarch64-buildroot-linux-gnu-
$ make -C kernel
```

defconfig に何が使われるのか気になったので Makefile を見てみたところ、`defconfig` が指定されていました。kernel config を変更する場合は `$KERNEL_DEF_CONFIG` を設定すると良いようです。

```:kernel/Makefile
KERNEL_SRC_DIR ?= kernel-jammy-src
KERNEL_DEF_CONFIG ?= defconfig
...
	$(MAKE) \
		ARCH=arm64 \
		-C $(kernel_source_dir) \
		LOCALVERSION=$(version) \
		$(KERNEL_DEF_CONFIG)
```

次に、ビルドした kernel と *.ko ファイルを rootfs のディレクトリにコピーします。
ここでは、どのようなファイルがコピーされるのかを調べたいので、適当に rootfs ディレクトリを用意してそこにインストールしてみます。
テストなので非rootで実行していますが、Linux_for_Tegra/rootfs にインストールする場合は公式ドキュメントのように`sudo -E make install`が必要です。

```
$ mkdir -p $WORK/rootfs/boot
$ mkdir -p $WORK/rootfs/lib
$ INSTALL_MOD_PATH=$WORK/rootfs make install -C kernel
$ cp kernel/kernel-jammy-src/arch/arm64/boot/Image /boot/
```

## Out-of-Tree モジュールのビルド

kernel 本体のビルドが終わったら、Jetson 固有のモジュールをビルドします。
ここでも RT kernel は使わないので、2の手順はスキップします。
oot モジュールをビルドするときには、上でビルドした kernel 本体のパスを `$KERNEL_HEADERS` に設定する必要があります。

```
$ cd $WORK/source
$ export CROSS_COMPILE=$WORK/aarch64--glibc--stable-2022.08-1/bin/aarch64-buildroot-linux-gnu-
$ export KERNEL_HEADERS=$WORK/kernel/kernel-jammy-src
$ make modules
```

ビルドしたモジュールを rootfs にインストールします。上のビルド時に作成した rootfs ディレクトリにインストールします。

```
$ INSTALL_MOD_PATH=$WORK/rootfs make modules_install
```

インストール前後のファイルを見ると、oot モジュールは `rootfs/lib/modules/5.15.122-tegra/extra/` 以下にインストールされるようです。
extra に配置することでオリジナルの kernel module と棲み分けをしているみたいですね。

# nvbuild.sh でもビルドできた

公式手順では上のようなビルド手順が必要なのですが、なんと nvbuild.sh で一発でビルドできました。

toolchainとソースツリーの展開が終わったら、CROSS_COMPILE= を設定して ./nvbuild.sh だけでもビルドできました。楽ちん

```
$ cd $WORK/source
$ export CROSS_COMPILE=$WORK/aarch64--glibc--stable-2022.08-1/bin/aarch64-buildroot-linux-gnu-
$ ./nvbuild.sh
```

nvbuild.sh を使ったビルドでは、`kernel_out` というディレクトリにソース等をコピーしてビルドするようです。

ビルドした .ko ファイルをインストールするには、`INSTALL_MOD_PATH`を設定して`./nvbuild.sh -i` でできるようです。

```
$ mkdir -p $WORK/rootfs/boot
$ mkdir -p $WORK/rootfs/lib
$ INSTALL_MOD_PATH=$WORK/rootfs ./nvbuild.sh -i
```


# カスタマイズの方法は？

以上でビルドはできたので、どうすれば config を変更できるのか調べてみました。

まず kernel 本体側の config について。
こちらは上で記載したように `KERNEL_DEF_CONFIG` に defconfig の名前を指定することでカスタマイズできそうです。

Linux kernel で一般的な、`make menuconfig` などで .config を変更後、`make savedefconfig` で defconfig を生成するなどで対応できそうです。

一方で `nvidia-oot` ディレクトリについては、Makefileを変更しないとカスタマイズはできないようでした。

例えば camera 関係のドライバの Makefile は以下のようになっています。
`obj-m` とモジュール化が強制的に指定されています。
個別に無効化するには Makefile を修正するしかなさそうです。

```:nvidia-oot/drivers/media/i2c/Makefile
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

subdir-ccflags-y += -Werror

obj-m += max9295.o
obj-m += max9296.o
ifeq ($(findstring ack_src,$(NV_BUILD_KERNEL_OPTIONS)),)
obj-m += max96712.o
obj-m += ar1335_common.o
obj-m += lt6911uxc.o
obj-m += nv_imx185.o
obj-m += nv_imx219.o
obj-m += nv_imx274.o
obj-m += nv_imx318.o
obj-m += nv_imx390.o
obj-m += nv_imx477.o
obj-m += nv_ov5693.o
obj-m += nv_ar0234.o
obj-m += pca9570.o
obj-m += nv_hawk_owl.o
obj-m += virtual_i2c_mux.o
endif
```

kernel ツリーと nvidia-oot を完全に分離したため、oot 側の Kconfig を読み込ませることができないためと思われます。実際 nvidia-oot ディレクトリには Kconfig ファイルはありませんでした。

今の所、Jetson Linux R36.2 は Jetson Orin のみサポートしており、Xavier はサポートから外されたため、Orin で使われないドライバがツリー上から削除されたようなので、それほど気にしなくてもよいのかもしれません。

# おわりに

Jetson Linux R36.2 の kernel と module をビルドして、カスタマイズする方法を調べてみました。
kernel ツリーと粗結合にすることで、ユーザが使いたい kernel バージョンで Jetson を使えるようにするという意欲的な試みを感じることができました。
一方で NVIDIA 製ドライバではカスタマイズ性が犠牲になっている面もあることがわかりました。