Linux KernelのImageやDevice TreeをARM用にクロスコンパイルする方法

Yocto ばかり使っていると、たまにコンパイルする時に凡ミスをするのでメモ。

##### 動作環境
* Ubuntu Ubuntu 16.04.6 LTS

##### ターゲット
* Aarch64(ARMv8)

# 事前準備
## ツールチェインの取得
ここでは [Linaro GCC][1] (執筆時点の最新 7.4-2019.02)を使用していますが、
違うツールチェインでも以降の操作における基本的な手順は同じです。

```shell
# 作業用ディレクトリの作成
$ mkdir ~/work
$ export WORK=~/work
# ツールチェインの取得と展開
## aarch64-linux-gnu : Linux 64-bit binaries for the Aarch64 Linux cross-toolchain
$ wget https://releases.linaro.org/components/toolchain/binaries/7.4-2019.02/aarch64-linux-gnu/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu.tar.xz
$ tar Jxvf ./gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu
## arm-linux-gnueabi : Linux 64-bit binaries for the ARMv7 Linux soft float cross-toolchain
$ wget https://releases.linaro.org/components/toolchain/binaries/7.4-2019.02/arm-linux-gnueabi/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabi.tar.xz
$ tar Jxvf ./gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabi.tar.xz
## arm-linux-gnueabihf : Linux 64-bit binaries for the ARMv7 Linux hard float cross-toolchain
$ wget https://releases.linaro.org/components/toolchain/binaries/7.4-2019.02/arm-linux-gnueabihf/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz
$ tar Jxvf ./gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz
```
## Kernel の ソースコードの取得
執筆時点の最新のStable Kernelは5.0.9です。
最新バージョンは https://www.kernel.org/ で確認できます。

```shell
# 作業用ディレクトリへの移動
$ cd ${WORK}
# tarball の場合
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.9.tar.xz
$ tar Jxvf ./linux-5.0.9.tar.xz
$ cd ./linux-5.0.9/
# git　の場合
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ -b linux-5.0.y
cd ./linux
```

# クロスコンパイル

## 環境変数の設定
```shell
# ツールチェインのパスを追加 (Linux 64-bit binaries for the Aarch64 Linux cross-toolchain)
export PATH="${WORK}/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin:$PATH"
# CPUアーキテクチャの指定
export ARCH=arm64
# クロスコンパイラの指定 (設定した${PATH}の配下に"aarch64-linux-gnu-gcc"や"aarch64-linux-gnu-g++"がある)
export CROSS_COMPILE="aarch64-linux-gnu-"
# 同時に実行可能なジョブ(コマンド)数の指定 = コンパイルの並列化 (※)
export JOBS=`grep -c "processor" /proc/cpuinfo`
```

#### コンパイル(make)の並列化
マルチコア環境で指定すると並列処理できるため、ビルド時間が短縮されます。
CPUの物理コア数、論理コア数(スレッド数)等の情報は"/proc/cpuinfo"で確認できます。
指定する値＝コア数 or コア数×2+1 等、諸説ありますが、上ではスレッド数を指定しています。

```shell
# 物理コア数を指定する場合
export JOBS=`grep cpu.cores /proc/cpuinfo | sort -u | cut -f 3 -d " "`
# スレッド数を指定する場合
export JOBS=`grep -c "processor" /proc/cpuinfo`
# スレッド数 × 2 を指定する場合
export JOBS=`expr `grep -c "processor" /proc/cpuinfo` \* 2`
```

<details><summary>Kernel v5.09 での並列化によるビルド時間の比較 (クリックすると展開されます)</summary><div>
ホストPCやコンパイルのターゲットが変われば、傾向も大きく変わる可能性がありますので
以下の結果は参考程度に見てください。(make Image での結果です)

|並列化数|Intel i7-4790K @ 4.00GHz|Intel i7-4785T @ 2.20GHz|備考|
|:---:|---:|---:|:---|
|1|25m56.608s|39m26.194s|並列化無し|
|4|8m23.280s|12m31.039s|物理コア数|
|5|8m18.921s|12m3.865s|物理コア数 + 1|
|8|7m49.529s|11m7.954s|スレッド数|
|9|7m51.041s|11m16.613s|スレッド数 + 1|
|16|8m0.959s|11m35.233s|スレッド数 * 2|

</div></details>


## クロスコンパイル
```shell
# Default の Config を読み込み
$ make -j ${JOBS} defconfig
# Config をカスタマイズ
$ make menuconfig
# コンパイル
$ make -j ${JOBS} Image dtbs modules
```
コンパイル時の各引数は以下のものを作成することを指定しています。

- Image : Kernel Image の作成。(出力先：arch/arm64/boot/Image)
- dtbs : Device Tree Blob(DTB)の作成。(出力先：arch/arm/boot/dts/${ベンダー名}/*.dtb)
- modules : Loadable module の作成。(出力先：各ドライバ依存)

# TIPS
## DTC (Device Tree Compiler) を使った Device Tree のコンパイル
Device Tree は DTC (Device Tree Compiler) を使って、単体でもコンパイルすることが出来ます。

### DTS（Device Tree Source) から DTB (Device Tree Blob) へ
```shell
# Kernelのソースツリーに含まれるDTCを使う場合
$ cd ${WORK}/arch/${ARCH}/boot/dts/${ベンダー名}/
$ ${WORK}/scripts/dtc/dtc -I dts -O dtb -o hogehoge.dtb hogehoge.dts
# DTCをホストPCにインストールする場合
$ sudo apt-get install device-tree-compiler
$ dtc -I dts -O dtb -o hogehoge.dtb hogehoge.dts
```

### DTB（Device Tree Blob) から DTS (Device Tree Source) へ
```shell
# Kernelのソースツリーに含まれるDTCを使う場合
$ cd ${WORK}/arch/${ARCH}/boot/dts/${ベンダー名}/
$ ${WORK}/scripts/dtc/dtc -I dtb -O dts -o hogehoge.dts hogehoge.dtb
# DTCをホストPCにインストールする場合
$ sudo apt-get install device-tree-compiler
$ dtc -I dtb -O dts -o hogehoge.dts hogehoge.dtb
```
通常、DTS は*.dtsiや他の*.dtbをincludeしていることが多く、
読み解く場合や比較する場合に見づらいことがあります。
その場合、一度 DTB にコンパイルした後、DTS に戻せば1ファイルになるので読みやすくなります。

## Kernel Config の保存と比較
Kernel Config を比較する時 (特にどうやって作られたか分からないdefconfigや.configなどを渡された時など)
比較対象をそのまま diff で比較すると、実際にはそんなに差分が無いにも関わらず、
依存関係等の影響で大量の差分が表示されてしまい、時間がかかることがあります。

そんな時は savedefconfig を使うとすっきりした defconfig ファイルを出力してくれるので便利です。

```shell
# 比較元の Config を A_defconfig として保存
$ make hogehogeA_defconfig
$ make savedefconfig
$ mv defconfig A_defconfig
# 比較先の Config を B_defconfig として保存
$ make hogehogeB_defconfig
$ make savedefconfig
$ mv defconfig B_defconfig
# 比較
$ diff -ur A_defconfig B_defconfig
```
menuconfig で カスタマイズした defconfig を保存、比較したい場合も
menuconfig の後にsavedefconfig を実行すれば OK です。
.configからdefconfigを生成してくれます。

```shell
# Config をカスタマイズ
$ make menuconfig
# defconfig ファイルを作成
$ make savedefconfig
```


[1]:https://releases.linaro.org/components/toolchain/binaries/