カテゴリー別アーカイブ: QEMU

[QEMU] Hello world on QEMU

QEMU で Hello world を動かす試みは探してみるといくつか実施されています。

いずれも versatilepb モデルで実施されているので、vexpress や virt でもできるかどうか試してみました。toolchain は Linaro toolchain を使いましたが、外部関数を呼び出さないので baremetal toolchain でなくても使えます。

プログラムソース init.c、スタートコード startup.s、リンカスクリプト link.ld を用意します。

init.c は “Hello, world!” を一文字ずつ UART I/O に書き込むだけのプログラムです。

/* init.c */
static void print(const char *str)
{
	while (*str)
		*(volatile unsigned char *)UART0_PTR = *str++;
}
int init()
{
	print("Hello, world!\n");
}

link.ld は外部定義された START からセクション定義を行います。スタックを 0x800 byte 分空けて スタックポインタの開始位置 sp_top を定義します。

/* link.ld */
ENTRY(_start)
SECTIONS {
	. = START;
	startup : { startup.o(.text)}
	.data   : {*(.data)}
	.bss    : {*(.bss)}
	. = . + 0x800;
	sp_top = .;

vexpress(aarch32) の場合

aarch32 とaarch64 で命令体系が変わるので、startup.s はそれぞれのものを用意します。aarch32 向けはスタックポインタに直接 sp_top を設定して init 関数を呼び出します。最後は無限ループです。

/* startup.s */
	.global _start
_start:
	ldr	sp,=sp_top
	bl	init
	b	.

vexpress-a9 モデルの場合のビルド・実行例です。UART0 I/O と SDRAM がモデルで異なるので、コマンドラインで設定しています。

$ arm-linux-gnueabihf-as  -mcpu=cortex-a9 -o startup.o startup.s
$ arm-linux-gnueabihf-gcc -mcpu=cortex-a9 -c init.c -DUART0_PTR=0x10009000
$ arm-linux-gnueabihf-ld -T link.ld -defsym START=0x60010000 -o output.elf startup.o init.o
$ arm-linux-gnueabihf-objcopy -O binary output.elf output.bin
$ qemu-system-aarch64 -M vexpress-a9 -cpu cortex-a9 -nographic -kernel output.bin
Hello, world!

vexpress-a15 モデルの場合は以下のようにします。

$ arm-linux-gnueabihf-as  -mcpu=cortex-a15 -o startup.o startup.s
$ arm-linux-gnueabihf-gcc -mcpu=cortex-a15 -c init.c -DUART0_PTR=0x1c090000
$ arm-linux-gnueabihf-ld -T link.ld -defsym START=0x80010000 -o output.elf startup.o init.o
$ arm-linux-gnueabihf-objcopy -O binary output.elf output.bin
$ qemu-system-aarch64 -M vexpress-a15 -cpu cortex-a15 -nographic -kernel output.bin
Hello, world!

virt(aarch64) の場合

aarch64 向けのスタートコード startup64.s を用意します。sp は直接代入できないので、x0 を間接的に使います。

/* startup64.s */
	.global _start
_start:
	ldr	x0,=sp_top
	mov	sp,x0
	bl	init
	b	.

virt モデルの場合の実行例です。toolchain は aarch64 のものを使います。

$ aarch64-linux-gnu-as -mcpu=cortex-a53 startup64.s
$ aarch64-linux-gnu-gcc -mcpu=cortex-a53 -DUART0_PTR=0x09000000 -c init.c
$ aarch64-linux-gnu-ld -T link.ld -o output.elf startup64.o init.o
$ aarch64-linux-gnu-objcopy -O binary output.elf output.bin
$ qemu-system-aarch64 -M vexpress-a53 -cpu cortex-a53 -nographic -kernel output.bin
Hello, world!

上記のソースは Makefile を使う形で github に置く予定です。

[QEMU][Linux] boot upstream Linux from U-boot on QEMU

U-boot on arm QEMU にて QEMU で U-boot を立ち上げて 外から tftp でメモリに転送しました。次にビルドした Linux kernel を転送して起動してみます。

Linux mainline の repository から clone してビルドします。

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/qemu.org/torvalds/linux.git
$ cd linux
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- multi_v7_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs

ビルドでできたイメージファイルを tftp サーバのディレクトリ (/tftpboot) にコピーします。

$ cp arch/arm/boot/zImage /tftpboot/
$ cp arch/arm/boot/vexpress-v2p-ca9.dtb /tftpboot/

root ファイルシステムを用意します。busybox で作るのが楽ですが、ここでは Linaro が提供している最小構成イメージを使うことにします。サイトから U-boot で読める形式のものをダウンロードします(時期によりファイル名が変更されるので先に web で確認してください)。

$ wget http://releases.linaro.org/openembedded/images/minimal-initramfs-armv7a/latest/linaro-image-minimal-initramfs-genericarmv7a-20170127-460.rootfs.cpio.gz.u-boot
$ cp linaro-image-minimal-initramfs-genericarmv7a-20170127-460.rootfs.cpio.gz.u-boot /tftpboot/rootfs.cpio.gz.u-boot

U-boot on arm QEMU  の手順で U-boot を起動します。起動したら、ネットワークの設定を行います。

=> setenv ipaddr 192.168.1.200     # target(QEMU) の IP アドレス
=> setenv serverip 192.168.1.1     # tftp サーバの IP アドレス
=> setenv netmask 255.255.255.0

bootargs に Linux の command line を設定します。

=> setenv bootargs root=/dev/ram0 rw console=ttyAMA0

tftp で先ほどサーバに置いたファイルの転送を行います。vexpress-a9 は 0x60000000 から SDRAM が実装されています。しかし先頭は kernel の実際の配置先に使用されるため、少し離して配置します。devicetree と rootfs も衝突しない場所に配置します。

=> tftp 0x62000000 zImage
=> tftp 0x63000000 vexpress-v2p-ca9.dtb
=> tftp 0x63008000 rootfs.cpio.gz.u-boot

転送した場所を指定して kernel を起動します。今回は圧縮イメージを使用しているので bootz を使用します。引数は「zImage の配置先」、「rootfs.cpio.gz.u-boot の配置先」、「vexpress-v2p-ca9.dtb の配置先」の順で指定します。

=> bootz 0x62000000 0x63008000 0x63000000
Kernel image @ 0x62000000 [ 0x000000 - 0x774fd8 ]
## Loading init Ramdisk from Legacy Image at 63008000 ...
Image Name:   linaro-image-minimal-initramfs-g
Image Type:   ARM Linux RAMDisk Image (gzip compressed)
Data Size:    8170953 Bytes = 7.8 MiB
Load Address: 00000000
Entry Point:  00000000
Verifying Checksum ... OK
## Flattened Device Tree blob at 63000000
Booting using the fdt blob at 0x63000000
Loading Ramdisk to 67710000, end 67edadc9 ... OK
Loading Device Tree to 67709000, end 6770f963 ... OK

Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 4.13.0-rc4 (kunih@ubuntu) (gcc version 6.3.1 20170404 (Linaro GCC 6.3-2017.05)) #1 SMP Fri Aug 11 10:47:38 JST 2017
[    0.000000] CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
...
[    3.598945] Freeing unused kernel memory: 2048K
linaro-test [rc=0]#

無事 shell まで起動できました。

[QEMU][boot] upstream U-boot on arm QEMU

QEMU では kernel が直接起動するので bootloader は必要ないのですが、手元に Das U-boot が動作する環境がないので、とりあえず動かした例がないか探していたところ、結構古い記事が多くてトレースできなかったので、最新環境で手元でやり直してみました。

arm ではマシンに versatilepb を使うことが多いのですが、こちらの記事でも言及されている通り、U-boot では versatile が削除されてしまっています。今だと vexpress でやればいい気もするので、とりあえずメモということで qemu のビルドから始めてみます。

QEMU と U-boot のビルドと起動

QEMU の repository から clone してビルド。もちろん package でインストールでもよいです。

$ git clone git://git.qemu.org/qemu.git
$ cd qemu
$ ./configure --target-list=arm-softmmu,aarch64-softmmu
$ make
$ sudo make install

U-boot も同様に最新版をビルド。マシンは vexpress_ca9x4 を指定します。

$ git clone git://git.denx.de/u-boot.git
$ cd u-boot
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_ca9x4_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

そして QEMU を起動…あれ何やらエラーが。バイナリが正しく読めない?

$ qemu-system-arm -M vexpress-a9 -nographic -kernel u-boot/u-boot.bin

qemu-system-arm: Trying to execute code outside RAM or ROM at 0x04000000
This usually means one of the following happened:

(1) You told QEMU to execute a kernel for the wrong machine type, and it crashed on startup (eg trying to run a raspberry pi kernel on a versatilepb QEMU machine)
(2) You didn't give QEMU a kernel or BIOS filename at all, and QEMU executed a ROM full of no-op instructions until it fell off the end
(3) Your guest kernel has a bug and crashed by jumping off into nowhere

This is almost always one of the first two, so check your command line and that you are using the right type of kernel for this machine.
If you think option (3) is likely then you can try debugging your guest with the -d debug options; in particular -d guest_errors will cause the log to include a dump of the guest register state at this point.

Execution cannot continue; stopping here.

ELF バイナリを指定したら動きました。何故だろう…

$ qemu-system-arm -M vexpress-a9 -nographic -kernel u-boot/u-boot

U-Boot 2017.07-00001-g13e3ef2 (Aug 13 2017 - 00:26:23 +0900)

DRAM:  128 MiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC:   MMC: 0
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   smc911x-0
Hit any key to stop autoboot:  0
=>

Network を使う

このままの U-boot だと何もできないので network を設定します。QEMU の vexpress モデルは SMC911X NIC をモデル化しているので U-boot でも使えます。当然ですが I/O を経由しない virtio-net は使えません。

QEMU のネットワークは仮想 I/F である tap と bridge を通して host PC の物理 I/F (eth0) に通します。例として以下のように設定します。

$ sudo tunctl -g `id -u`          # tap 作成
Set 'tap0' persistent and owned by gid 1000
$ sudo brctl addbr br12345        # bridge 作成
$ sudo brctl addif br12345 eth0   # host I/F を bridge に参加
$ sudo brctl addif br12345 tap0   # tap を bridge に参加
$ sudo ifconfig tap0 up
$ sudo ifconfig br12345 up

tap0 と eth0 が bridge で接続されたので、それを使用して U-boot を起動します。

$ qemu-system-arm -M vexpress-a9 -nographic -kernel u-boot/u-boot -net nic -net tap,ifname=tap0

自分の環境に合わせて hostPC と target(QEMU) の IP アドレスを設定します。

=> setenv ipaddr 192.168.1.200     # target(QEMU) の IP アドレス
=> setenv serverip 192.168.1.1     # tftp サーバの IP アドレス

ロードするバイナリを tftp サーバにコピーして、ロードするアドレスを指定してメモリに転送します。

=> tftp 0x62000000 image
smc911x: MAC 52:54:00:12:34:56
smc911x: detected LAN9118 controller
smc911x: phy initialized
smc911x: MAC 52:54:00:12:34:56
Using smc911x-0 device
TFTP from server 192.168.1.1; our IP address is 192.168.1.200
Filename 'image'.
Load address: 0x62000000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ##############
         2.7 MiB/s
done
Bytes transferred = 7833916 (77893c hex)

無事転送できました。試し終わったら、先のネットワーク設定の後片付けを忘れずに。

$ sudo ifconfig br12345 down
$ sudo ifconfig tap0 down
$ sudo brctl delif br12345 tap0
$ sudo brctl delif br12345 eth0
$ sudo brctl delbr br12345
$ sudo tunctl -d tap0 

arm64 の場合

では arm64 でも同じことができるかというと、QEMU には arm64 は virt モデルが主流のため vexpress モデルはありません。同様に U-boot は virt モデルには対応していません。つまり arm64 では U-boot が適合するモデルの組み合わせは今のところなさそうです。

後者の virt モデルをちょっと試しに(見よう見まねで) U-boot に追加してみたものの、flash のところでハマったのでやはり簡単ではなさそうです。また、この時も ELF でないと起動しなかったのが気になるところです。