🌽

U-BootからHelloWorld

2023/07/29に公開
1

ベアメタルプログラミングの第一歩として。

https://osdev-jp.connpass.com/event/289060/
に参加して、もくもくタイムの間にこの記事を書いています。

U-Bootのサンプルのhello_world

前回の記事でU-Bootのソースコードを取得しました。
examples/standalone/のディレクトリにhello_world.cがあります。

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のサンプルです。
printfgetcなどの関数はジャンプテーブルを通して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のコマンドを拡張することができますね。

次回予告

fatloadgoコマンドで指定した0x41000000 というアドレスがどこから来たか説明していませんでした。次回は hello_worldのビルドの方法を深掘りしてこのあたりの謎を究明したいと思います。

書きました。
https://zenn.dev/tetsu_koba/articles/866fc7b4d459bd

関連

https://zenn.dev/tetsu_koba/articles/5f6cb495fa630f
https://zenn.dev/tetsu_koba/articles/9486a827e286ee
https://zenn.dev/tetsu_koba/articles/866fc7b4d459bd
https://zenn.dev/tetsu_koba/articles/d0a25ea5035b3e

Discussion

rkarsnkrkarsnk

fatloadgoコマンドで指定した0x41000000 というアドレスがどこから来たか説明していませんでした。次回は hello_worldのビルドの方法を深掘りしてこのあたりの謎を究明したいと思います。

riscv64-linux-gnu-readelf -a helloworld

でhelloworldのバイナリを見るとエントリポイントアドレスが0x41000000であることは確認できました.

uboot.cfg を見ると

CONFIG_STANDALONE_LOAD_ADDR 0x41000000

という記述もありました.
hello_worldのビルド時にuboot.cfgを読み込んでいるようなので,ビルド時にCONFIG_STANDALONE_LOAD_ADDR をエントリポイントとして設定しているのではないかと思いました.

ただ,ビルド時のどこでエントリポイントを指定するのかまでは,確認できませんでした…