「RaspberryPi の U-boot をクロスコンパイルする」
https://qiita.com/nanbuwks/items/4f2d676d2c5696c33c04
では、RaspberryPi 3 用の U-boot を PC 上の Ubuntu 20.04 でビルドしました。
今回は、更に進めて initramfs まで進めます。
Linux の起動
PC で Linux を起動する場合、一般的に以下のようになっています。
- BIOS や UEFI やブートコード
- GRUB や U-boot
- vmlinuz + initramfs
- ルートファイルシステムをマウント
- ルートファイルシステム上の /sbin/init や systemd を実行
BIOSやUEEFIやGRUBはPCで用いられ、組み込みではブートコードやU-bootを使うことが多いです。
組み込み Linux での起動
以下のような流れが多く採用されています。
- ブートコード
- U-boot
- vmlinuz + initramfs
- ルートファイルシステムをマウント
- ルートファイルシステム上の /sbin/init や systemd を実行
Raspberry Pi での起動
RaspberryPi は組み込みの範疇となりますが、組み込みで標準的に使われる u-boot は使われておらず独特な起動方法となります。
Raspberry Pi / 2 / 3 の場合
- ROM から GPU の DPS コアのブートストラップを起動
- GPU は SDカードの第1パーティションから bootcode.bin を起動
- GPU は bootcode.bin に沿って start.elf を起動、start.elf は HDMI 出力に GPU のテストパターン表示(虹色パターン)
- GPU は start.elf に沿って config.txt を読み込み、Liux カーネル(RaspberryPi3の場合 SD カードの第1パーティション中の Kernel7.img)をメモリにロード
- CPU起動
- Linux カーネルは initramfs を読み込み、ROOTパーティションをマウント、init プロセスを起動
cf., https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#boot-sequence
Raspberry Pi 4 の場合
- ROM から GPU の DPS コアのブートストラップを起動
- GPU は EEPROM から bootloader を起動、起動メニューを HDMI 出力に表示
- GPU は bootloader に沿って SD カードの 第1パーティーションから start4.elf を起動、start4.elf は HDMI 出力に GPU のテストパターン表示(虹色パターン)
- GPU はstart7.elf に沿って EEPROM から config , SDカードの第1パーティションから config.txt を読み込み、Liux カーネル(SD カードの第1パーティション中の Kernel7l.img または Kernel8.img )をメモリにロード
- CPU起動
- Linux カーネルは initramfs を読み込み、ROOTパーティションをマウント、init プロセスを起動
cf., https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-4-boot-flow
RaspberryPi はオプションで様々な起動方法がありますが、SDカードから起動するスタンダードな起動フローが上記のものとなります。
今回の目的
Raspberry Pi の起動フローは上記のように独特ですが、今回は上記の方法をアレンジして U-boot を間に挟み、U-boot 以降は組み込みLinuxのスタンダードな流れで起動するようにします。
- ROM から GPU の DPS コアのブートストラップを起動
- GPU は SDカードの第1パーティションから bootcode.bin を起動
- GPU は bootcode.bin に沿って start.elf を起動
- GPU は start.elf に沿って config.txt を読み込み、u-boot (SD カードの第1パーティション中の「 u-boot.bin 」)をメモリにロード
- CPU起動
- CPU は u-boot に沿って Linux カーネル、デバイスファイル、initramfs を読み込み (SD カードの第1パーティション中に格納)、メモリにロード
- Linux カーネル起動
- init プロセスを起動
すべてを自前で作りたいのですが、最初の方に使う
- ROM 中の GPU のブートストラップ (GPUコード)
- SDカード中の bootcode.bin (GPUコード)
- SDカード中の start.elf (GPUコード)
はオープンソースではなく、 Raspberry Pi 専用に事前に用意されているものを使います。
cf.,https://elinux.org/RPi_Software#GPU_bootloaders
ちなみに、Raspberry Pi 3 ではここで動作するのは ARM のコードではなく Broadcom BCM2837 ( Raspberry Pi 3 の場合)/Broadcom BCM2837B0 ( Raspberry Pi 3 + の場合 ) SOC に統合されている Broadcom VideoCore IV GPU 用のコードになります。
その後に使用する以下のものはオープンソースのものを使い、自前で作成します。
- SDカード中にある config.txt。( start.elf に u-boot.bin を読み込ませる指示をするためのテキストファイル)
- SDカード中の u-boot.bin。(ARMコード)
- SDカード中の Image。これは Linux カーネルイメージ。(ARMコード)
- SDカード中の initramfs イメージ。(RAMに展開されるファイルシステムを圧縮したもの)
これらを作成、実行は RaspberryPi の実機で行います。
作成にあたっては、RaspberryPi でも可能ですが時間がかなりかかりますので今回は PC 上にクロス環境を構築して作成します。クロス環境は Ubuntu Linux および Windows で確認しています。Macの場合は手元に環境が無いのでチャレンジしていませんが、基本的に同じようにして作成できるはず?
理解できるよう、作業していきます。
元ネタ
「Boot a Raspberry Pi 4 using u-boot and Initramfs」
https://hechao.li/2021/12/20/Boot-Raspberry-Pi-4-Using-uboot-and-Initramfs/
を元にします。
元ネタは Raspberry Pi 4 ですが、本稿では Raspberry Pi 3 をターゲットとします。
環境
- Ubuntu 22.04 LTS ja デスクトップ および WSL2
- Raspberry Pi 3
- マイクロ SD カード
WSL2
の人は以下のようにしてインストール、Ubuntu が使えるようにしておきます。
https://qiita.com/nanbuwks/items/e3db5fe9c03c76a62227
SDイメージを用意する
元ネタの2. Preparation は実際のSDカードですが、今回はイメージファイルを作成して作業します。
「Ubuntu / WSL2 でディスクイメージファイルを作成」
https://qiita.com/nanbuwks/items/11c296a26a17c4ed3129
のように用意してください。
ツールチェイン
あらかじめ、以下のようにインストールしておきます。
元ネタの「3. Toolchain」を参考に、以前記した以下の記事のように作業をします。
「RaspberryPi 用のクロスツールチェインを Ubuntu 上にビルド」
https://qiita.com/nanbuwks/items/e19887e6a66262d325ca
のとおりですが、以下の2点のみ変更します。
ひとつは git checkout において、
$ git checkout crosstool-ng-1.25.0 -b 1.25.0
とします。
もうひとつは
$ ./ct-ng menuconfig
で Operating System を確認したら、 5.16.9 になっていました。
後ほど Linux カーネルをダウンロードしますが、テスト時は rpi-5.15.y を使うことにしたのでその場合問題が起こります。なのでカーネルソースよりも古いバージョンにします。今回は以下のようにかなり古いバージョンに設定しました。
┌────────────────────── Version of linux ───────────────────────┐
│ Use the arrow keys to navigate this window or press the │
│ hotkey of the item you wish to select followed by the <SPACE │
│ BAR>. Press <?> for additional information about this │
│ ┌───────────────────────────^(-)────────────────────────────┐ │
│ │ ( ) 5.1.21 │ │
│ │ ( ) 5.0.19 │ │
│ │ (X) 4.20.17 │ │
│ │ ( ) 4.19.229 │ │
│ │ ( ) 4.18.20 │ │
│ │ ( ) 4.17.19 │ │
│ └───────────────────────────v(+)────────────────────────────┘ │
├───────────────────────────────────────────────────────────────┤
│ <Select> < Help > │
└───────────────────────────────────────────────────────────────┘
あとは解説通り進めて、「ツールチェインをビルド」の
$ ./ct-ng build
まで終わらせて、パスを通しておきます。
U-boot
以下の記事の通りU-bootを作ります。
「RaspberryPi の U-boot をクロスコンパイルする」
https://qiita.com/nanbuwks/items/4f2d676d2c5696c33c04
資料の通り、以下の4つを用意します。
- bootcode.bin
- start.elf
- u-boot.bin
- config.txt
このうち、u-boot.bin は今作成したものですが、 bootcode.bin と start.elf はRaspberryPi 専用のできあいのものを使うことになります。config.txt は start.elf で利用するためのもので start.elf が u-boot.bin を読み込みできるように設定します。
この4つを SD カードイメージの boot パーティションに配置し、umount します。
SD カードにイメージを書き込み、Raspberry Pi 3 に装填、起動できたら U-boot 作成は OK です。
Linux カーネルの作成
通常は含まれている bcや cpio コマンドですが、最低限の環境だと bc が無かったので、あらかじめ入れておきます。
$ sudo apt install bc cpio
元ネタの 「5. Kernel」に沿って、以下のように作業します。
$ git clone --depth=1 https://github.com/raspberrypi/linux.git
$ cd linux
ここで取得したブランチは rpi-5.15.y でした。
Raspberry Pi 3 のデフォルト設定をそのまま使用します。
$ make ARCH=arm64 CROSS_COMPILE=aarch64-rpi3-linux-gnu- bcm2711_defconfig
$ make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-rpi3-linux-gnu-
10分ぐらいかかりました。
ビルドできたら Image ファイルと .dtb と名前のついたデバイスツリーファイルを SDカードイメージにコピーします。
$ cp arch/arm64/boot/Image ../boot
$ cp arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb ../boot
$ cd ..
ちなみに、bcm271* でこれだけ種類があるのでそれっぽいのを選択しました。
bcm2710-rpi-2-b.dtb bcm2710-rpi-3-b.dts bcm2710-rpi-zero-2.dtb bcm2711-rpi-400.dts
bcm2710-rpi-2-b.dts bcm2710-rpi-cm3.dtb bcm2710-rpi-zero-2.dts bcm2711-rpi-cm4.dtb
bcm2710-rpi-3-b-plus.dtb bcm2710-rpi-cm3.dts bcm2711-rpi-4-b.dtb bcm2711-rpi-cm4.dts
bcm2710-rpi-3-b-plus.dts bcm2710-rpi-zero-2-w.dtb bcm2711-rpi-4-b.dts bcm2711-rpi-cm4s.dtb
bcm2710-rpi-3-b.dtb bcm2710-rpi-zero-2-w.dts bcm2711-rpi-400.dtb bcm2711-rpi-cm4s.dts
u-boot から Linux カーネルを呼び出せるようにする
以下の内容の boot_cmd.txt を作ります。
fatload mmc 0:1 ${kernel_addr_r} Image
setenv bootargs "console=serial0,115200 console=tty1 root=/dev/mmcblk0p1 rw rootwait init=/hello"
booti ${kernel_addr_r} - ${fdt_addr}
起動コマンドの意味:
-
fatload mmc 0:1 ${kernel_addr_r} Image
この行は、カーネルイメージファイルを 0番目のmmc デバイスのパーティション1 から読み込み、メモリに配置します。 -
setenv bootargs "console=serial0,115200 console=tty1 root=/dev/mmcblk0p1 rw rootwait init=/hello"
カーネルの起動オプションを設定します。ルートファイルシステムとして SD カードの第一パーティションを使うようにしてます。また、init プロセスとしてルートファイルシステム中の /hello を指定していますがまだ /hello は配置していないため実際には動作しません。 -
booti ${kernel_addr_r} - ${fdt_addr}
カーネルにデバイスツリーバイナリのアドレスを指定して起動します。 - とあるのは、もし initramfs を使う場合はRAMディスクファイルシステムのメモリ上のアドレスをここに指定します。
コマンドファイルを変換して u-boot で使用する uImage フォーマットに変換します。
$ ../../u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot_cmd.txt boot.scr
Image Name:
Created: Sun Sep 25 14:08:37 2022
Image Type: AArch64 Linux Script (uncompressed)
Data Size: 182 Bytes = 0.18 KiB = 0.00 MiB
Load Address: 00000000
Entry Point: 00000000
Contents:
Image 0: 174 Bytes = 0.17 KiB = 0.00 MiB
できた boot.scr を boot パーティションにコピーします。
SDカードをRaspberryPi に取り付け起動すると、Linuxカーネルが呼び出されます。今回は /hello が無いので 途中でカーネルパニックで止まります。
init プログラムの作成
先程は /hello をinitプロセスとして実行するようにしていましたが、ここを処理するテストプログラムを作成します。
実行がなされるかどうかだけのテストとして、以下のプログラムとしました。
#include <stdio.h>
int main(void) {
printf("Hello Init!\n");
return 0;
}
static ビルドしておきます。
$ ~/x-tools/aarch64-rpi3-linux-gnu/bin/aarch64-rpi3-linux-gnu-gcc -static -o hello hello.c
できた hello を boot パーティションにコピーします。
起動すると Kernel panic で止まりますが、途中で hello プログラムが実行されていることがわかります。
なお、ここで FATAL: kernel too old と出ることがありました。
これは、 crosstool-ng の Operating system の設定と、カーネルソースのバージョンがミスマッチしているために起こります。カーネルソースのバージョンまたは crostool-ng の設定を修正してください。
ルートファイルシステムの作成
先程は、SD カードの第1パーティションをルートファイルシステムとして指定しました。しかしながらSDカード内部はディレクトリも何も作っていませんでした。
「Filesystem Hierarchy Standard」
https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.pdf
のような、Linux ぽいファイルシステムを作ります。
元ネタの「6. Root filesystem」に沿って作業していきます。
まずは、ローカルで作業用フォルダを作ります。
$ mkdir rootfs
移動し、必要なディレクトリを作成します。
$ cd rootfs
$ mkdir {bin,dev,etc,home,lib64,proc,sbin,sys,tmp,usr,var}
$ mkdir usr/{bin,lib,sbin}
$ mkdir var/log
$ sudo chown -R root:root *
$ cd ..
更に、usr に 先程の hello を配置します。
$ sudo cp hello rootfs/usr
この rootfs を SDカードに書き込みましょう。
$ sudo cp -a rootfs/* <SDカード第2パーティション>
boot_cmd.txt を以下のように変更します。
fatload mmc 0:1 ${kernel_addr_r} Image
setenv bootargs "console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rw rootwait init=/usr/hello"
booti ${kernel_addr_r} - ${fdt_addr}
第二パーティションの /usr/hello を起動するようにします。
boot.scr に反映させます。
$ ../../u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot_cmd.txt boot.scr
これを boot パーティションに書き込み、RaspberryPi に装着して起動します。
第2パーティションの /usr/hello が起動していることがわかります。
initramfs をつくる
ルートファイルシステムを、カーネルが認識するファイルシステムに配置すればそれを使ってLinuxが起動できます。
ここまで作成した rootfs フォルダを、RAM ディスクにする作業を行います。
もとねたの「7. Boot the Board」に沿って作業していきます。
initramfs は cpio の newc フォーマットを使うらしいです。
The cpio file format used by initramfs is the "newc" (aka "cpio -H newc") format,
cf., https://www.kernel.org/doc/Documentation/early-userspace/README
$ find . | cpio -H newc -ov --owner root:root -F ../initramfs.cpio
$ cd ..
$ gzip initramfs.cpio
$ ls -alh initramfs.cpio.gz
-rw-rw-r-- 1 nanbuwks nanbuwks 3.8M 9月 25 21:38 initramfs.cpio.gz
initramfs のイメージファイルは、 U-Boot から読みこまれてメモリ上に展開することになるのですが、U-boot で扱うために mkimage でヘッダを追加して uImage 形式にします。
$ ../../u-boot/tools/mkimage -A arm64 -O linux -T ramdisk -d initramfs.cpio.gz uRamdisk
Image Name:
Created: Sun Sep 25 21:39:46 2022
Image Type: AArch64 Linux RAMDisk Image (gzip compressed)
Data Size: 3905858 Bytes = 3814.31 KiB = 3.72 MiB
Load Address: 00000000
Entry Point: 00000000
できた uRamdisk ファイルを boot パーティションにコピーします。
U-boot を構成する
今回設定した initramfs を使用するように U-boot を設定します。
元ネタの以下のやりかたを採ることにします。
For simplicity, I’ll use the Busybox shell as the init program.
以下の内容の boot_cmd.txt を作ります。
fatload mmc 0:1 ${kernel_addr_r} Image
fatload mmc 0:1 ${ramdisk_addr_r} uRamdisk
setenv bootargs "console=serial0,115200 console=tty1 rdinit=/usr/hello"
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr}
fatload mmc 0:1 ${fdt_addr_r} bcm2710-rpi-3-b.dtb
も試したのですがこれが入ると起動しませんでした。
$ ../../u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot_cmd.txt boot.scr
Image Name:
Created: Sun Sep 25 10:23:06 2022
Image Type: AArch64 Linux Script (uncompressed)
Data Size: 262 Bytes = 0.26 KiB = 0.00 MiB
Load Address: 00000000
Entry Point: 00000000
Contents:
Image 0: 254 Bytes = 0.25 KiB = 0.00 MiB
できた boot.scr を boot パーティションにコピーします。
今まで作成したファイルのうち、現時点で boot パーティションに必要なものは以下のファイルとなります。
- Image
- bcm2711-rpi-3-b.dtb
- boot.scr
- bootcode.bin
- config.txt
- start.elf
- uRamdisk
- u-boot.bin
これを使って起動してみました。
busybox の作成
ここまでで、 U-boot 、 Linux カーネル、 initramfs が動作していることは確認できました。hello の後にKernel Panic が起きてますが exit せずに無限ループし、適切な処理をすることで希望の処理を続けることができます。
まだ成功していませんが、init プロセスとして busybox を動かすことを試してみます。
$ wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
$ tar xf busybox-1.35.0.tar.bz2
$ cd busybox-1.35.0/
$ CROSS_COMPILE=${HOME}/x-tools/aarch64-rpi3-linux-gnu/bin/aarch64-rpi3-linux-gnu-
$ make CROSS_COMPILE="$CROSS_COMPILE" defconfig
ビルド、インストールします
$ make CROSS_COMPILE="$CROSS_COMPILE"
$ sudo make CROSS_COMPILE="$CROSS_COMPILE" install
_install に展開されるので rootfs にコピーします。
$ sudo cp -a _install/* ../rootfs
たくさんのコマンドが展開されてますが
$ ls ../rootfs/bin
arch chown dnsdomainname fsync ipcalc lsattr mpstat printenv run-parts stty vi
ash conspy dumpkmap getopt kbd_mode lzop mt ps scriptreplay su watch
base32 cp echo grep kill makemime mv pwd sed sync zcat
base64 cpio ed gunzip link mkdir netstat reformime setarch tar
busybox cttyhack egrep gzip linux32 mknod nice resume setpriv touch
cat date false hostname linux64 mktemp pidof rev setserial true
chattr dd fatattr hush ln more ping rm sh umount
chgrp df fdflush ionice login mount ping6 rmdir sleep uname
chmod dmesg fgrep iostat ls mountpoint pipe_progress rpm stat usleep
実体はこの1コマンドです。
$ ls -alh ../rootfs/bin/busybox
-rwxr-xr-x 1 root root 1010K 9月 24 19:58 ../rootfs/bin/busybox
多くできていたコマンドは、すべて busybox にリンクされているのがわかります。
$ ls -alh ../rootfs/bin/
合計 1020K
drwxrwxr-x 2 root root 4.0K 9月 24 19:58 .
drwxrwxr-x 14 nanbuwks nanbuwks 4.0K 9月 24 19:58 ..
lrwxrwxrwx 1 root root 7 9月 24 19:58 arch -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 ash -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 base32 -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 base64 -> busybox
-rwxr-xr-x 1 root root 1010K 9月 24 19:58 busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 cat -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 chattr -> busybox
.
.
.
lrwxrwxrwx 1 root root 7 9月 24 19:58 umount -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 uname -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 usleep -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 vi -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 watch -> busybox
lrwxrwxrwx 1 root root 7 9月 24 19:58 zcat -> busybox
ライブラリのインストール
Busybox で使用する共有ライブラリを調べます。
$ readelf -a ../rootfs/bin/busybox | grep -E "(program interpreter)|(Shared library)"
[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
必要な共有ライブラリを sysroot にコピーします。
$ export SYSROOT=$(aarch64-rpi3-linux-gnu-gcc -print-sysroot)
$ echo $SYSROOT
/home/nanbuwks/x-tools/aarch64-rpi3-linux-gnu/aarch64-rpi3-linux-gnu/sysroot
$ sudo cp -L ${SYSROOT}/lib64/ld-linux-aarch64.so.1 ../rootfs/lib64/
デバイスノードを作成します。
Busybox で使うデバイスノードを作成します。
$ cd ../rootfs
$ sudo mknod -m 666 dev/null c 1 3
$ sudo mknod -m 600 dev/console c 5 1
boot_cmd.txt を以下のように変更します。
fatload mmc 0:1 ${kernel_addr_r} Image
setenv bootargs "console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rw rootwait init=/bin/sh"
booti ${kernel_addr_r} - ${fdt_addr}
boot.scr に反映します。
$ ../../u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot_cmd.txt boot.scr
boot.scr を boot パーティションにコピーし、RaspberryPi に装着し起動します。
No working init found となってしまいました。調査中です!