ZYBO用U-Bootでブート時にメニューを表示させる







# はじめに


筆者は、次の記事で示すように ZYNQ 用の u-boot をカスタマイズして使っています。

* [「ZYBO用U-Bootを、ブート時にuEnv.txtを読むようにして、かつBOOT.binから起動できるようにする」 @Qiita](https://qiita.com/ikwzm/items/1734676d787e2693df47)

筆者は Linux Kernel をいろいろと取り替えたり boot 時の引数を変更したりするため、よく uEnv.txt を書き換えます。その際、書き間違えたり、元に戻すのを間違えたりしてブートに失敗することがありました。これをもう少しなんとかならないかと思っていたところ、u-boot にはもともと bootmenu という機能があり、bootmenu を使えばブート時にメニューから実行するコマンドを選べることを知りました。残念ながら bootmenu 機能は u-boot をビルドする際にオフになっており通常は使えなくなっています。この記事では bootmenu を使えるように u-boot をビルドする方法と、実際に bootmenu を使ってメニューを表示させる方法を示します。

なお、この改造を施した U-Boot のイメージを次の URL で公開しています。

* https://github.com/ikwzm/FPGA-SoC-U-Boot-ZYBO
* https://github.com/ikwzm/FPGA-SoC-U-Boot-ZYBO-Z7
* https://github.com/ikwzm/FPGA-SoC-U-Boot-PYNQ-Z1
* https://github.com/ikwzm/FPGA-SoC-U-Boot-DE0-Nano-SoC
* https://github.com/ikwzm/FPGA-SoC-U-Boot-DE10-Nano


# U-Boot のブート処理の説明


U-Boot は 次のフローチャートで示す順番でブート処理を行います。


![Fig.1 U-Boot のブート処理のフローチャート](https://qiita-image-store.s3.amazonaws.com/0/24981/9fbb6c2e-8670-0410-fe7a-5f5182b9eeb5.jpeg "Fig.1 U-Boot のブート処理のフローチャート")

Fig.1 U-Boot のブート処理のフローチャート

<br />

bootmenu は上記フローチャートの bootdelay_process の部分で実行されます。具体的には  __bootmenu_(%d)__  変数 (  __(%d)__  には0〜9の数字が入る) が定義されていると、その変数の値に基づいてメニュー画面を表示し、選択されたメニューで定義されているコマンドを実行します。bootmenu 変数に関しては後述します。


# U-Boot のビルドの手順



## U-Boot のダウンロード


この記事では u-boot 本家のリポジトリを使います。


```console
shell$ git clone git://git.denx.de/u-boot.git 
shell$ cd u-boot
```



## U-Boot v2016.03 をチェックアウトして作業用のブランチを作る


この記事では ちょっと古いですが v2016.03 を元に変更します。ここでは ZYBO 用の U-Boot を作る場合の例を示します。


```console
shell$ git checkout -b v2016.03-zynq-zybo refs/tags/v2016.03
```



## CONFIG_EXTRA_ENV_SETTINGS を修正する


include/configs/zynq-common.h にCONFIG_EXTRA_ENV_SETTINGS を定義します。これは u-boot のデフォルトの環境変数群です。


```C:include/configs/zynq-common.h
/* Default environment */
#define CONFIG_EXTRA_ENV_SETTINGS	\\
	"fit_image=fit.itb\\0"		\\
	"load_addr=0x2000000\\0"		\\
	"fit_size=0x800000\\0"		\\
	"flash_off=0x100000\\0"		\\
	"nor_flash_off=0xE2100000\\0"	\\
	"fdt_high=0x20000000\\0"		\\
	"initrd_high=0x20000000\\0"	\\
	"loadbootenv_addr=0x2000000\\0" \\
	"fdt_addr_r=0x1f00000\\0"        \\
	"pxefile_addr_r=0x2000000\\0"    \\
	"kernel_addr_r=0x2000000\\0"     \\
	"scriptaddr=0x3000000\\0"        \\
	"ramdisk_addr_r=0x3100000\\0"    \\
	"norboot=echo Copying FIT from NOR flash to RAM... && "              \\
		"cp.b ${nor_flash_off} ${load_addr} ${fit_size} && " \\
		"bootm ${load_addr}\\0" \\
	"sdboot=echo Copying FIT from SD to RAM... && " \\
		"load mmc 0 ${load_addr} ${fit_image} && " \\
		"bootm ${load_addr}\\0" \\
	"jtagboot=echo TFTPing FIT to RAM... && " \\
		"tftpboot ${load_addr} ${fit_image} && " \\
		"bootm ${load_addr}\\0" \\
	"usbboot=if usb start; then " \\
			"echo Copying FIT from USB to RAM... && " \\
			"load usb 0 ${load_addr} ${fit_image} && " \\
			"bootm ${load_addr}\\0" \\
		"fi\\0" \\
	"bootenv=uEnv.txt\\0" \\
	"bootenv_dev=mmc\\0" \\
	"config_name=" CONFIG_SYS_CONFIG_NAME "\\0" \\
	"loadbootenv=load ${bootenv_dev} 0 ${loadbootenv_addr} ${bootenv}\\0" \\
	"importbootenv=echo Importing environment from ${bootenv_dev} ...; " \\
		"env import -t ${loadbootenv_addr} $filesize\\0" \\
	"bootenv_existence_test=test -e ${bootenv_dev} 0 /${bootenv}\\0" \\
	"setbootenv=if env run bootenv_existence_test; then " \\
			"if env run loadbootenv; then " \\
				"env run importbootenv; " \\
			"fi; " \\
		"fi; \\0" \\
	"sd_loadbootenv=set bootenv_dev mmc && " \\
			"run setbootenv \\0" \\
        "loadbootscript=load ${bootenv_dev} 0 ${load_addr} boot.scr\\0" \\
        "bootscript=echo Running bootscript from mmc ...;" \\
                "source ${load_addr}\\0" \\
	DFU_ALT_INFO

```


 


## CONFIG_PREBOOT を定義する


include/configs/zynq-common.h にCONFIG_PREBOOT を定義します。CONFIG_PREBOOT を定義しておくと、 preboot 変数が定義されます。前述のフローチャートで説明したとおり、preboot 変数が定義されている場合、bootdelay_process の前に preboot 変数の内容が実行されます。


```C:include/configs/zynq-common.h
#define CONFIG_PREBOOT \\
	"if test $modeboot = sdboot; then " \\
		"if mmc rescan; then " \\
			"run sd_loadbootenv; " \\
		"fi;" \\
	"fi;"

```


preboot 変数には、bootenv変数で定義されているファイルをロードして環境変数として取り込むよう、コマンドを定義しておきます。具体的には sd_loadbootenv 変数を実行(run) します。bootenv 変数と sd_loadbootenv 変数は前述のCONFIG_EXTRA_ENV_SETTINGS で定義されています。 




## CONFIG_BOOTCOMMAND を定義する


include/configs/zynq-common.h に CONFIG_BOOTCOMMAND を定義します。U-Boot の boot コマンドは、このCONFIG_BOOTCOMMAND で定義されたコマンドを実行します。


```C:include/configs/zynq-common.h
#define CONFIG_BOOTCOMMAND \\
	"if mmc rescan; then " \\
		"if test -n $uenvcmd; then " \\
			"echo Running uenvcmd ...;" \\
			"run uenvcmd;" \\
		"fi;" \\
		"if run loadbootscript; then " \\
			"run bootscript; " \\
		"fi; " \\
	"fi;" \\
 	"run $modeboot"

```


ここでは、 uenvcmd 変数が定義されていた場合は、まず uenvcmd 変数の内容を実行するようにしています。uenvcmd 変数は、 preload 実行時に読み込むファイル(uEnv.txtなど)で定義しておきます。




## CONFIG_MENU_SHOW 等を定義する


include/configs/zynq-common.h に CONFIG_MENU、CONFIG_MENU_SHOW、CONFIG_CMD_BOOTMENU を定義します。


```C:include/configs/zynq-common.h
#define CONFIG_MENU
#define CONFIG_MENU_SHOW
#define CONFIG_CMD_BOOTMENU

```


また、defconfig ファイルにも CONFIG_MENU、CONFIG_MENU_SHOW、CONFIG_CMD_BOOTMENU を追加します。


```text:configs/zynq_zybo_defconfig
  :
 中略
  :
CONFIG_MENU=y
CONFIG_MENU_SHOW=y
CONFIG_CMD_BOOTMENU=y

```

CONFIG_CMD_BOOTMENU はビルド時に cmd/bootmenu.c をコンパイルするために定義します。cmd/bootmenu.c には、コンソールにメニューを表示させてカーソルキーで選択させるためのコマンドが定義されています。

CONFIG_MENU_SHOW は、autoboot 時に bootmenu を実行するために定義します。

これらの定数を定義することで、U-Boot は bootmenu が使えるようになります。ただ、何故か私が使っている U-Boot v2016.03 では、include/configs/zynq-common.h と configs/zynq_zybo_defconfig の両方に同じように定数を定義しておかないとリンクに失敗しました。新しいバージョンでは改善されているかもしれません。




## U-Boot をビルドする


次のように U-Boot をビルドします。


```console
shell$ export ARCH=arm
shell$ export CROSS_COMPILE=arm-linux-gnueabihf-
shell$ make zynq_zybo_defconfig
shell$ make 
```





# ブートメニューの例



## ブートメニューの例１


次のような uEnv.txt を用意します。


```text:uEnv.txt
linux_kernel_image=zImage-4.14.21-armv7-fpga
linux_fdt_image=devicetree-4.14.21-zynq-zybo-z7.dtb
linux_load_cmd=fatload mmc 0 ${kernel_addr_r} ${linux_kernel_image} && fatload mmc 0 ${fdt_addr_r} ${linux_fdt_image}
linux_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio && bootz ${kernel_addr_r} - ${fdt_addr_r}
uenvcmd=run linux_load_cmd && run linux_boot_cmd
bootmenu_0=Boot Default=boot

```


uEnv.txt には、ブートする Linux のイメージファイル、デバイスツリーファイルを変数として定義しておき、uenvcmd 変数に、これらのファイルを boot するためのコマンドを定義しておきます。

最後の行の  __bootmenu_0__  変数がブートメニュー用の変数です。ブートメニュー用の変数は、  __bootmenu_(%d)=(%title)=(%command)__  の形で定義します。最初の  __(%d)__  には0〜9の数字が入ります。  __(%title)__  にはメニューで表示するタイトルが入ります。  __(%command)__  は実行する U-Boot のコマンドを記述します。

この uEnv.txt により、U-Boot を起動するとほどなく次のような画面になります。


![Fig.2 ブートメニューの例１](https://qiita-image-store.s3.amazonaws.com/0/24981/fa0a8605-5160-caea-830e-f5a1857701b7.gif "Fig.2 ブートメニューの例１")

Fig.2 ブートメニューの例１

<br />



この画面で、何もキーを押さずにいると、 autoboot のカウントダウンが始まり、0になった時点で  __bootmenu_0__  変数で定義されたコマンドが実行されます。何かキーを押すとカウントダウンが停止します。カーソルキーの上下キーを使って U-Boot console を選んで ENTER キーを押すと、U-Boot のコマンド入力モードになります。

コマンド入力モードから boot コマンドを実行すると、boot を開始します。その際、uenvcmd 変数が定義されていると、まずその uenvcmd が実行されます。

コマンド入力モードから bootmenu コマンドを実行することで、再度ブートメニューが表示され、カウントダウンが始まります。


## ブートメニューの例2


次のような uEnv.txt を用意します。


```text:uEnv.txt
linux_kernel_image=zImage-4.14.21-armv7-fpga
linux_fdt_image=devicetree-4.14.21-zynq-zybo-z7.dtb
linux_load_cmd=fatload mmc 0 ${kernel_addr_r} ${linux_kernel_image} && fatload mmc 0 ${fdt_addr_r} ${linux_fdt_image}
linux_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio && bootz ${kernel_addr_r} - ${fdt_addr_r}
uenvcmd=run linux_load_cmd && run linux_boot_cmd
bootmenu_env_existence_test=test -e ${bootenv_dev} 0 ${bootmenu_env_file}
bootmenu_env_load=load ${bootenv_dev} 0 ${loadbootenv_addr} ${bootmenu_env_file}
bootmenu_env_import=env import -t ${loadbootenv_addr} $filesize
bootmenu_env_set=env run bootmenu_env_existence_test && env run bootmenu_env_load && env run bootmenu_env_import
bootmenu_env_boot=if env run bootmenu_env_set; then; boot; else; echo "## Error Read fail " ${bootmenu_env_file}; fi
bootmenu_0=Boot Default=boot
bootmenu_1=Boot Linux 4.12.14-armv7-fpga=env set bootmenu_env_file uEnv-4.12.14.txt && env run bootmenu_env_boot

```


この uEnv.txt では  __bootmenu_0__  の他に   __bootmenu_1__  を定義しています。  __bootmenu_1__  変数は uEnv-4.12.14.txt の内容を新しい環境変数として読み込み、boot コマンドを実行するよう定義しています。そしてuEnv-4.12.14.txt は次のような内容にしています。




```text:uEnv-4.12.14.txt
linux_kernel_image=zImage-4.12.14-armv7-fpga
linux_fdt_image=devicetree-4.12.14-zynq-zybo-z7.dtb
linux_load_cmd=fatload mmc 0 ${kernel_addr_r} ${linux_kernel_image} && fatload mmc 0 ${fdt_addr_r} ${linux_fdt_image}
linux_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio && bootz ${kernel_addr_r} - ${fdt_addr_r}
uenvcmd=run linux_load_cmd && run linux_boot_cmd

```


この uEnv.txt により、U-Boot を起動するとほどなく次のような画面になります。


![Fig.3 ブートメニューの例２](https://qiita-image-store.s3.amazonaws.com/0/24981/ccb262c5-2a92-7718-c288-3fce56da30de.gif "Fig.3 ブートメニューの例２")

Fig.3 ブートメニューの例２

<br />

この画面で、カーソルキーを使って Boot Linux 4.12.14-armv7-fpga を選択してENTER キーを押すと、uEnv-4.12.14.txt が読み込まれ、uEnv-4.14.21.txt で定義されている uenvcmd が実行され、Linux 4.12.14-armv7-fpga がブートします。


# 参考


* https://www.denx.de/wiki/U-Boot/WebHome
* [「ZYBO用U-Bootを、ブート時にuEnv.txtを読むようにして、かつBOOT.binから起動できるようにする」 @Qiita](https://qiita.com/ikwzm/items/1734676d787e2693df47)
* https://github.com/ikwzm/FPGA-SoC-U-Boot-ZYBO
* https://github.com/ikwzm/FPGA-SoC-U-Boot-ZYBO-Z7
* https://github.com/ikwzm/FPGA-SoC-U-Boot-PYNQ-Z1
* https://github.com/ikwzm/FPGA-SoC-U-Boot-DE0-Nano-SoC
* https://github.com/ikwzm/FPGA-SoC-U-Boot-DE10-Nano