LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Raspberry Pi 3 Model B をネットワークブートで起動させる (U-Boot編)

概要

leavatail.hatenablog.com

前回の記事では、Raspberry Pi 3 Model B (Raspberry Pi 3B) のROMによるの起動シーケンスを利用してネットワークブートする手法を記載した。 しかし、Raspberry Pi 3B のROMには幾つかの不具合が報告されており、ROMによるネットワークブートが安定しない。

一方で、Raspberry Pi 3B のROMから別のブートローダをロードし、そのブートローダからネットワークブートする手法が存在する。 Das U-Boot(U-Boot)は組込み系で広く利用されているブートローダの一つであり、Raspberry Pi 3B でも利用することができる。

そこで本記事では、Raspberry Pi 3B からロードされたU-Boot (32bit) を用いて、メインラインカーネル (32bit) をネットワーク経由でブートするための環境を構築する方法を解説する。

f:id:LeavaTail:20200430000544p:plain
Raspberry Pi 3Bのネットワークブート概要図 (U-boot)

Raspberry Pi 3B にはU-Bootのバイナリを格納したSDカードを差し込み、U-Bootを利用してネットワーク上にあるネットワークサーバからブートイメージとルートファイルシステムを取得する。
ネットワークブート用のサーバはUbuntu 18.04 LTS上にdnsmasqとnfs-kernel-serverをインストールし、メインラインカーネル (32bit) *1をビルドしておく。

はじめに

組込みシステムの開発段階やデバッグ段階では、ネットワーク経由でシステムを起動(ネットワークブート)できることが理想的である。 ネットワークブートができる環境が構築されていれば、組込みシステムのファームウェアに再書き込みを行わずに、データやソフトウェアを容易に共有することができる。 Raspberry PiAMDプロセッサを搭載したシングルボードコンピュータの一つで、手軽に入手できる点や世の中に情報が多い点から組込みシステムの入門として用いられることが多い。

一方、Das U-Boot(U-Boot)は組込み系システムで広く利用されており、Raspberry Pi 3Bもサポートしている。 U-Bootには機能として、簡易のコマンドやドライバが組み込まれており、TFTPを利用してネットワークブートすることができる。

U-Bootでネットワークブートを用いたRaspberry Pi 3Bの起動シーケンスを下記に示す。

f:id:LeavaTail:20200430164839p:plain
Raspberry Pi 3Bの起動シーケンス

  1. Raspberry Pi 3Bに電源投入すると、GPUが起動する。
  2. GPUは1段目のブートローダを起動。One-Time Programmable (OTP) メモリのブートモードに従ったメディア(今回はSD card)から、2段目のブートローダbootcode.binを取得する。
  3. bootcode.binを実行する。
  4. bootcode.binSDRAMを有効化する。
  5. bootcode.binファームウェアstart.elfを読み込む。
  6. start.elfを実行する。
  7. start.elfconfig.txtを指定されたメディア(今回はSD card)から取得する。
  8. start.elfcmdline.txtを指定されたメディア(今回はSD card)から取得する。
  9. start.elfconfig.txtのkernelパラメータで指定されたu-boot.binを指定されたメディア(今回はSD card)から取得する。
  10. u-bootを実行する。
  11. u-bootはTFTP経由でzImageを取得する。
  12. 取得したzImageSDRAMの指定場所(${kernel_addr_r})に読み込む。
  13. u-bootはTFTP経由でbcm2837-rpi-3-b.dtbを取得する。
  14. 取得したbcm2837-rpi-3-b.dtbSDRAMの指定場所(${fdt_addr_r})に読み込む。
  15. ARM coreにリセット指令を発行し、${kernel_addr_r}番地からプログラムを実行する。

下記は、Raspberry Pi 3Bが起動するまでに必要となるファイル群である。

ファイル名 格納先 概要
config.txt SD card システム構成パラメータ
cmdline.txt SD card カーネルコマンドライン(もしかしたら不要)
start.elf SD card 一般的なRaspberry Piファームウェア
fixup.dat SD card start.elfのリンカファイル
bootcode.bin SD card SoCに備わっているブートローダ
zImage Server カーネルイメージ
bcm2710-rpi-3-b.dtb Server バイスリーファイル (bcm2837-rpi-3-b.dtb でも代用可能)

そこで本記事では、Raspberry Pi 3B でメインラインカーネルをネットワークブートするための環境を構築する方法を紹介する。

各手法における特徴

前回の記事では、Raspberry Pi 3Bをネットワークブートする手法としてROM内のファームウェアを利用した。 ネットワークブートのための手法はいくつか存在しているが、それぞれ特徴が異なる。

ROM内のファームウェアを利用した場合の特徴としては以下の点があげられる。

  • SDカード周りのデバッグやドライバの開発などに向いている。
  • 運用中はSDカードが不要なので、SDカードを用意するコストを最小限に抑えることができる。

一方、U-Bootを利用した場合の特徴としては以下の点があげられる。

  • 起動対象の差を設定によって吸収できる。
  • (Raspberry Pi 3B の場合) 不具合の影響が小さい。
  • U-Bootに備わっている簡易コマンドを利用することで、別プログラムをロードしたり多段ブートすることを容易である。

実行環境

実験環境は前回構築した環境をそのまま使用する。

f:id:LeavaTail:20200430010924p:plain
ネットワーク構成図

ネットワークブートサーバの詳細は下記のとおりである。

名前 詳細
OS Ubuntu 18.04 LTS
Kernel 5.3.0-40-generic
NIC (ホームセグメント側) eth0
NIC (開発側セグメント) eth1
IPアドレス (eth0) 192.168.1.11
IPアドレス (eth1) 172.16.1.1
ホスト名 server
Raspberry Pi 3Bのブートイメージ格納予定 /srv/boot
Raspberry Pi 3Bのルートファイルシステム格納予定 /srv/rootfs
シリアルデバイスファイル /dev/ttyUSB0
シリアルポートのボーレート 115200

また、使用するRasberry Pi 3Bの詳細は下記のとおりである。

名前 詳細
ファームウェア 4.19.97-v7+
NIC eth0
IPアドレス 172.16.1.2 (DHCP)
ホスト名 raspberry
ストレージ HIDISC microSDHCカード 8GB CLASS10 UHS-1対応
バイスファイル /dev/sdb1

構築手順

本記事が目標とするネットワークブートの構成は下記のとおりである。

f:id:LeavaTail:20200430010512p:plain
ネットワークブートの動作イメージ

Raspberry Pi 3Bに挿入してあるU-Boot(32bit)がネットワークブートを担うこと」と「起動するカーネルはメインラインカーネルのARM(32bit)」以外は前回の記事と同様のことを目指す。

今回使用したソフトウェアのバージョンは下記のとおりである。

名前 バージョン
U-Boot v2020.07-rc1
mainline kernel v5.7-rc3
dnsmasq v2.79-1
nfs-common 1:1.3.4-2.1ubuntu5.2

Raspberry Pi 3Bの設定

ネットワークブートサーバの設定は、「システム構成パラメータの更新」と「U-Bootの構築」の2ステップ必要になる。

システム構成パラメータの更新
  1. config.txtから読み込むカーネルファイルをU-Bootに変更する。

     user@server:~$ mount /dev/sdb1 /mnt/tmp
     user@server:~$ echo 'kernel=u-boot.bin' > /mnt/tmp/config.txt
     user@server:~$ umount /mnt/tmp
    
U-Bootの構築

今回の手順では、別のマシン(ネットワークブートサーバ)でU-Bootをビルドし、バイナリをSDカードにコピーする。

  1. リポジトリをダウンロードする。

      user@server:~$ git clone git://git.denx.de/u-boot.git
      user@server:~$ cd u-boot
    
  2. ARMクロスコンパイラ(32bit)をインストールする。

      user@server:~$ apt-get install gcc-arm-linux-gnueabi
    
  3. Raspberry Pi 3B (32bit) 用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- USE_PRIVATE_LIBGCC=yes rpi_3_32b_defconfig
    
  4. U-Bootをビルドする。

      user@server:~$ make
    
  5. 生成したバイナリ(u-boot.bin)をSDカードにコピーする。

      user@server:~$ mount /dev/sdb1 /mnt/tmp
      user@server:~$ cp u-boot.bin /mnt/tmp/
      user@server:~$ umount /mnt/tmp
    

ネットワークブートサーバの設定

ネットワークブートサーバの設定は、「ブートイメージの作成」、「ルートファイルシステムの取得」、「DHCP/TFTPサーバの構築」、「NFSサーバの構築」の4ステップ必要になる。

「ブートイメージの作成」以外は、前回の記事で構築した環境をそのまま利用する。

ブートイメージの作成
  1. メインラインカーネルをビルドするのに必要なパッケージをインストールする。

      user@server:~$ sudo apt install git bc bison flex libssl-dev make
    
  2. mainlineのkernelのソースコードをクローンする。

      user@server:~$ git clone --depth=1 https://github.com/torvalds/linux.git
      user@server:~$ cd linux
    
  3. ARM用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig
    
  4. カーネルをビルドする。

      user@server:~$ make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage modules dtbs
    
  5. 生成したカーネルイメージとDevice Tree blobsをtftpサーバの公開ディレクトリにコピーする。

      user@server:~$ sudo cp arch/arm/boot/dts/*.dtb /srv/tftpboot/
      user@server:~$ sudo cp arch/arm/boot/zImage /srv/tftpboot/
    

実行結果

  1. minicom経由でアクセスする。

     U-Boot 2020.04-00687-gd16d37bcd4 (Apr 29 2020 - 09:27:20 +0000)
    
     DRAM:  948 MiB
     RPI 3 Model B (0xa32082)
     MMC:   mmc@7e202000: 0, sdhci@7e300000: 1
     Loading Environment from FAT... *** Warning - bad CRC, using default environment
    
     In:    serial
     Out:   vidconsole
     Err:   vidconsole
     Net:   No ethernet found.
     starting USB...
     Bus usb@7e980000: scanning bus usb@7e980000 for devices... 3 USB Device(s) found
            scanning usb for storage devices... 0 Storage Device(s) found
    
  2. Device Tree blobsをメモリ上に展開する。

     U-Boot> dhcp ${fdt_addr_r} ${fdtfile}
     Waiting for Ethernet connection... done.
     BOOTP broadcast 1
     DHCP client bound to address 172.16.1.2 (6 ms)
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1 our IP address is 172.16.1.2
     Filename 'bcm2837-rpi-3-b.dtb'.
     Load address: 0x2600000
     Loading: ##################################################  13.8 KiB
              1 MiB/s
     done
    
  3. カーネルイメージをメモリ上に展開する。

     U-Boot> tftp ${kernel_addr_r} zImage
     Waiting for Ethernet connection... done.
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1; our IP address is 172.16.1.2
     Filename 'zImage'.
     Load address: 0x80000
     Loading: ##################################################  9.3 MiB
              2.7 MiB/s
     done
    
  4. カーネルパラメータを設定する。

     U-Boot> setenv bootargs root=/dev/nfs nfsroot=${serverip}:/srv/rootfs,vers=3,proto=tcp rw rootwait ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off nfsrootdebug
    
  5. 指定番地にあるカーネルを起動させる。

     U-Boot> bootz ${kernel_addr_r} - ${fdt_addr_r}                                                                                                                            
     Kernel image @ 0x080000 [ 0x000000 - 0x953200 ]
     ## Flattened Device Tree blob at 02600000
        Booting using the fdt blob at 0x2600000
        Using Device Tree in place at 02600000, end 02606725
    
     Starting kernel ...
    
     <<< snip >>>
    
     Raspbian GNU/Linux 10 raspberrypi ttyS0
    
     raspberrypi login: 
    

おわりに

本記事では、U-Bootを利用してRaspberry Pi 3Bをネットワークブートする方法を紹介した。 U-Bootを利用したことで、Raspberry Pi 3Bで報告されている不具合を回避しながらも、SDカードの書き込み寿命の浪費を防ぐことができる。 U-Bootにはあらかじめスクリプトを書いておき、起動時に自動で実行する機構もあるので、そちらを利用するとより一層便利になる。

参考

SDカードを使わず、network bootでRaspberry Pi 3Bを起動する

SDカードにU-Bootのみ格納し、network bootでRaspberry Pi 3Bを起動する

SDカードを使わず、network bootでRaspberry Pi 3B+を起動する

Raspberry Pi の起動シーケンス

Raspberry Pi のブートで必要なファイル郡

U-Bootに関する説明資料

*1:Raspbianにあるカーネルはそのままだと起動できなかった