U-BootからHelloWorld
ベアメタルプログラミングの第一歩として。
に参加して、もくもくタイムの間にこの記事を書いています。
U-Bootのサンプルのhello_world
前回の記事でU-Bootのソースコードを取得しました。
examples/standalone/
のディレクトリにhello_world.c
があります。
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2000
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*/
#include <common.h>
#include <exports.h>
int hello_world(int argc, char *const argv[])
{
int i;
/* Print the ABI version */
app_startup(argv);
printf ("Example expects ABI version %d\n", XF_VERSION);
printf ("Actual U-Boot ABI version %d\n", (int)get_version());
printf ("Hello World\n");
printf ("argc = %d\n", argc);
for (i=0; i<=argc; ++i) {
printf ("argv[%d] = \"%s\"\n",
i,
argv[i] ? argv[i] : "<NULL>");
}
printf ("Hit any key to exit ... ");
while (!tstc())
;
/* consume input */
(void) getc();
printf ("\n\n");
return (0);
}
これがまさしくU-Bootから動かすHelloWorldのサンプルです。
printf
や getc
などの関数はジャンプテーブルを通してU-Boot内部のものを呼び出すようになっているようです。
ビルド
ビルド環境は前回の記事と同じです。
make CROSS_COMPILE=riscv64-linux-gnu- examples/standalone/
また、riscv64-unknown-elfのツールチェインを使うこともできます。
sudo apt install gcc-riscv64-unknown-elf
make starfive_visionfive2_defconfig
make CROSS_COMPILE=riscv64-unknown-elf- examples/standalone/
$ ls -l hello_world*
-rwxrwxr-x 1 koba koba 14280 7月 27 18:34 hello_world
-rwxrwxr-x 1 koba koba 4856 7月 27 18:34 hello_world.bin
-rw-rw-r-- 1 koba koba 689 7月 27 09:57 hello_world.c
-rw-rw-r-- 1 koba koba 16400 7月 27 18:34 hello_world.o
-rwxrwxr-x 1 koba koba 2390 7月 27 18:34 hello_world.srec
-rw-rw-r-- 1 koba koba 41 7月 27 18:34 hello_world.su
ここでできたhello_world.bin
をVisionFive2のDebianに持っていって、/boot に置きます。
/boot はブート時にロードするLinuxカーネルが格納されているディレクトリです。
$ mount |grep /boot
systemd-1 on /boot type autofs (rw,relatime,fd=41,pgrp=1,timeout=120,minproto=5,maxproto=5,direct,pipe_ino=10472)
/dev/mmcblk1p3 on /boot type vfat (rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
このディレクトリのストレージデバイスは/dev/mmcblk1p3
です。これはmmcの1番目のデバイスの3番目のパーティションであることを示しています。
動作確認(失敗)
VisionFive2にシリアルポートをつなぎます。接続方法はこの記事を参照してください。
電源投入後のU-Bootの入力待ちになったところでシリアルポートのターミナルにリターンを入力して止めます。
Hit any key to stop autoboot: 0
StarFive #
StarFive #
はこのボードでのU-Bootのプロンプトです。(通常、各ボードごとにカスタマイズされることが多いです。)
SDカード上のファイルの確認。
StarFive # fatls mmc 1:3
4353737 System.map-5.15.0-starfive
148601 config-5.15.0-starfive
extlinux/
9192607 initrd.img-5.15.0-starfive
385 uEnv.txt
8420005 vmlinuz-5.15.0-starfive
dtbs/
4380096 System.map-5.15.0
149066 config-5.15.0
8407297 vmlinuz-5.15.0
4856 hello_world.bin
9 file(s), 2 dir(s)
SDカードからファイルをロード。
StarFive # fatload mmc 1:3 0x41000000 hello_world.bin
4856 bytes read in 7 ms (676.8 KiB/s)
実行。
StarFive # go 0x41000000
## Starting application at 0x41000000 ...
Unhandled exception: Load access fault
EPC: 00000000410000ee RA: 0000000041000026 TVAL: ffffffb032353159
EPC: ffffffff812bb0ee RA: ffffffff812bb026 reloc adjusted
SP: 00000000ff734a30 GP: 00000000ff734e00 TP: 0000000000000001
T0: 0000303032353131 T1: 00000000fff46288 T2: 0000000000000000
S0: 0000000000000001 S1: 0000000000000002 A0: 0000000041000228
A1: 0000000000000009 A2: 00000000ff754328 A3: fffffffffffffffe
A4: 00000000410012f8 A5: 00000000410012f8 A6: 000000000000000f
A7: 00000000fffa8308 S2: 00000000ff754328 S3: 00000000ff754320
S4: 0000000000000002 S5: 00000000ffff04f4 S6: 0000000000000000
S7: 00000000ff75a6a0 S8: 0000000000000000 S9: 0000000000000000
S10: 0000000000000000 S11: 0000000000000000 T3: 0000000000000010
T4: 0000000000000000 T5: 000000000001869f T6: 00000000ff734b20
Code: 8282 b283 0f81 b283 0202 8282 b283 0f81 (b283 0282)
resetting ...
ああ、なんか例外が発生してリセットしてしまいました。
EPCが00000000410000ee
のところでLoad access fault
になってしまったことがわかります。
llvm-objdump -d hello_world |less
00000000410000ea printf:
410000ea: 83 b2 81 0f ld t0, 248(gp)
410000ee: 83 b2 82 02 ld t0, 40(t0)
410000f2: 82 82 jr t0
先ほどのレジスタダンプではt0レジスタの値は以下のようになっていました。
T0: 0000303032353131
これがメモリの範囲を超えていそうですね。
U-Bootのバージョンを合わせて再度チャレンジ
そういえば、動いているU-BootはSPI Flashから起動したもので、ボードを購入してから更新していませんでした。
StarFive # version
U-Boot 2021.10 (Feb 12 2023 - 18:15:33 +0800), Build: jenkins-VF2_515_Branch_SDK_Release-24
riscv64-buildroot-linux-gnu-gcc.br_real (Buildroot VF2_515_v2.10.0) 10.3.0
GNU ld (GNU Binutils) 2.36.1
2023年の2月のバージョンです。
リリースノートを見ると、このときのU-BootのタグはVF2_v2.10.4
だと記載されています。
gitでこのタグのソースをcheckoutして再度ビルドして試してみます。
git checkout -b v2.10.4 VF2_v2.10.4
make starfive_visionfive2_defconfig
make CROSS_COMPILE=riscv64-unknown-elf- examples/standalone/
さきほどのところを逆アセンブルしてみると
00000000410000ea printf:
410000ea: 83 b2 01 0f ld t0, 240(gp)
410000ee: 83 b2 82 02 ld t0, 40(t0)
410000f2: 82 82 jr t0
gpレジスタからのオフセットの値が変わっています。うまくいきそうな予感。
StarFive # fatload mmc 1:3 0x41000000 hello_world.bin
4856 bytes read in 6 ms (790 KiB/s)
StarFive # go 0x41000000
## Starting application at 0x41000000 ...
Example expects ABI version 9
Actual U-Boot ABI version 9
Hello World
argc = 1
argv[0] = "0x41000000"
argv[1] = "<NULL>"
Hit any key to exit ...
## Application terminated, rc = 0x0
StarFive #
今度は正しく動作しました!
goコマンドの機能
パラメータをつけずにgoコマンドを実行すると使い方が出ました。
StarFive # go
go - start application at address 'addr'
Usage:
go addr [arg ...]
- start application at address 'addr'
passing 'arg' as arguments
アドレスの後には任意の引数を渡せるようです。試してみました。
StarFive # go 0x41000000 hello world
## Starting application at 0x41000000 ...
Example expects ABI version 9
Actual U-Boot ABI version 9
Hello World
argc = 3
argv[0] = "0x41000000"
argv[1] = "hello"
argv[2] = "world"
argv[3] = "<NULL>"
Hit any key to exit ...
## Application terminated, rc = 0x0
StarFive #
リターンするとU-Bootのプロンプトに戻ってきます。
このしくみを利用して、U-Bootのコマンドを拡張することができますね。
次回予告
fatload
やgo
コマンドで指定した0x41000000
というアドレスがどこから来たか説明していませんでした。次回は hello_worldのビルドの方法を深掘りしてこのあたりの謎を究明したいと思います。
書きました。
関連
Discussion
でhelloworldのバイナリを見るとエントリポイントアドレスが
0x41000000
であることは確認できました.uboot.cfg を見ると
という記述もありました.
hello_worldのビルド時にuboot.cfgを読み込んでいるようなので,ビルド時に
CONFIG_STANDALONE_LOAD_ADDR
をエントリポイントとして設定しているのではないかと思いました.ただ,ビルド時のどこでエントリポイントを指定するのかまでは,確認できませんでした…