QEMUにマシンを追加する(BL1動作)

- [BL1とは](#bl1とは)
- [起動パラメータ](#起動パラメータ)
- [BL1の修正](#bl1の修正)
- [SRAMの実装](#sramの実装)
  - [rza3ul.h](#rza3ulh)
  - [rza3ul.c](#rza3ulc)
- [ビルドと実行](#ビルドと実行)
- [rza3ul.cの説明](#rza3ulcの説明)
  - [cpuの作成](#cpuの作成)
  - [elの設定](#elの設定)
  - [realize](#realize)
  - [メモリ空間にSRAMを設定](#メモリ空間にsramを設定)
  - [bl1のロード](#bl1のロード)

[QEMUにマシンを追加する(枠の追加)](https://qiita.com/t-yano-tryhard/items/820e2d177ddf208fbbe9)に引き続き
マシンの中身を実装していきます。

今回はBL1を動作させます。

# BL1とは
BL1とはブートローダの一種になります。
RZ/A3ULではBL1⇒BL2⇒(BL31⇒BL33⇒)アプリケーションプログラム
という風に実際にユーザが起動させたいプログラムが起動するまでに、
複数のブートローダが起動します。
今回は[評価ボード](https://www.renesas.com/jp/ja/software-tool/rza3ul-software-package)用に無料公開されている
[サンプルプログラム](https://www.renesas.com/jp/ja/document/scd/rza3ul-evk-example-projects?language=en&r=1625071)を動作させることが目的のため
BL1⇒BL2⇒サンプルプログラムという順に動作させます。

作成するマシンは[事前準備編](https://qiita.com/t-yano-tryhard/items/4af976ec113bca3926f0)で記載した通り[RZ/A3UL](https://www.renesas.com/jp/ja/products/microcontrollers-microprocessors/rz-mpus/rza3ul-powerful-1ghz-64-bit-mpus-rtos-support-enables-high-definition-hmi-and-quick-startup)とします。

# 起動パラメータ
実行コマンドは以下の通りとします。
```shell
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/debug/bl1.bin -semihosting
```
cpu はa55なのですが、qemu側で実装が無いためa57で一旦代用します。

bl1.binは[事前準備編](https://qiita.com/t-yano-tryhard/items/4af976ec113bca3926f0)で作成した
[ipl](https://github.com/renesas/rza-initial-program-loader)配下のqemu用bl1.binを使用します。


また、qemu用のbl1はセミホスティング機能を使ってbl2をロードしています。
実機用のBL1を作る方法は一般公開されていないようなので
そのまま流用させてもらいます。

以下コマンド等でbuildディレクトリ内に該当ファイルへのシンボリックリンクを作成してください。
```shell
ln -s ../../ipl/build/qemu/debug/bl2.bin
```
qemu-system-aarch64の実行ディレクトリ内にbl2.binがあると
bl1はbl2をセミホスティング機能を使ってロードしてくれます。
詳細は[この辺](https://trustedfirmware-a.readthedocs.io/en/latest/plat/qemu.html)に記載してあります。

ドキュメントでは`-semihosting-config enable,target=native`を使用するようになっていますが
単なる`-semihosting`だけでも動くようです。
違いについてはよくわかりません。

# BL1の修正
用意されているBL1はvirtマシン用になるため、
今回作成するRZ/A3UL用だと多少の修正が必要になります。

[IPL](https://github.com/renesas/rza-initial-program-loader)をダウンロードしたディレクトリ内で以下を修正します。

<details>
<summary>IPLのqemu用bl1に加える変更</summary>

```diff
diff --git a/bl1/bl1_main.c b/bl1/bl1_main.c
index fd60232..2939617 100644
--- a/bl1/bl1_main.c
+++ b/bl1/bl1_main.c
@@ -99,9 +99,9 @@ void bl1_main(void)
 #else
 	val = read_sctlr();
 #endif
-	assert((val & SCTLR_M_BIT) != 0);
-	assert((val & SCTLR_C_BIT) != 0);
-	assert((val & SCTLR_I_BIT) != 0);
+//	assert((val & SCTLR_M_BIT) != 0);
+//	assert((val & SCTLR_C_BIT) != 0);
+//	assert((val & SCTLR_I_BIT) != 0);
 	/*
 	 * Check that Cache Writeback Granule (CWG) in CTR_EL0 matches the
 	 * provided platform value
diff --git a/drivers/arm/pl011/aarch64/pl011_console.S b/drivers/arm/pl011/aarch64/pl011_console.S
index 861d2ed..95e1824 100644
--- a/drivers/arm/pl011/aarch64/pl011_console.S
+++ b/drivers/arm/pl011/aarch64/pl011_console.S
@@ -93,20 +93,20 @@ endfunc console_pl011_core_init
 	 * -----------------------------------------------
 	 */
 func console_pl011_register
-	mov	x7, x30
-	mov	x6, x3
-	cbz	x6, register_fail
-	str	x0, [x6, #CONSOLE_T_BASE]
-
-	bl	console_pl011_core_init
-	cbz	x0, register_fail
-
-	mov	x0, x6
-	mov	x30, x7
-	finish_console_register pl011 putc=1, getc=1, flush=1
-
-register_fail:
-	ret	x7
+#	mov	x7, x30
+#	mov	x6, x3
+#	cbz	x6, register_fail
+#	str	x0, [x6, #CONSOLE_T_BASE]
+#
+#	bl	console_pl011_core_init
+#	cbz	x0, register_fail
+#
+#	mov	x0, x6
+#	mov	x30, x7
+#	finish_console_register pl011 putc=1, getc=1, flush=1
+#
+#register_fail:
+#	ret	x7
 endfunc console_pl011_register

 	/* --------------------------------------------------------
@@ -153,12 +153,12 @@ endfunc console_pl011_core_putc
 	 * --------------------------------------------------------
 	 */
 func console_pl011_putc
-#if ENABLE_ASSERTIONS
-	cmp	x1, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	ldr	x1, [x1, #CONSOLE_T_BASE]
-	b	console_pl011_core_putc
+##if ENABLE_ASSERTIONS
+#	cmp	x1, #0
+#	ASM_ASSERT(ne)
+##endif /* ENABLE_ASSERTIONS */
+#	ldr	x1, [x1, #CONSOLE_T_BASE]
+#	b	console_pl011_core_putc
 endfunc console_pl011_putc

 	/* ---------------------------------------------
diff --git a/lib/el3_runtime/aarch64/context.S b/lib/el3_runtime/aarch64/context.S
index 7daf30d..0f5d93d 100644
--- a/lib/el3_runtime/aarch64/context.S
+++ b/lib/el3_runtime/aarch64/context.S
@@ -872,6 +872,24 @@ endfunc save_and_update_ptw_el1_sys_regs
  * ------------------------------------------------------------------
  */
 func el3_exit
+#ifdef IMAGE_BL1
+	bl disable_mmu_icache_el3
+	tlbi alle3
+	mov x0, #0x2000
+	movk x0, #1, LSL #16
+	mov x1, #0x3cd
+	msr elr_el3, x0
+	msr spsr_el3, x1
+	mov x0, #0xf000
+	movk x0, #2, LSL #16
+	mov x1, #0
+	mov x2, #0
+	mov x3, #0
+	mov x4, #0
+	mov x5, #0
+	mov x6, #0
+	mov x7, #0
+#else
 #if ENABLE_ASSERTIONS
 	/* el3_exit assumes SP_EL0 on entry */
 	mrs	x17, spsel
@@ -934,6 +952,7 @@ func el3_exit
 #endif
 #ifdef IMAGE_BL31
 	str	xzr, [sp, #CTX_EL3STATE_OFFSET + CTX_IS_IN_EL3]
+#endif
 #endif
 	exception_return

diff --git a/plat/qemu/common/qemu_bl1_setup.c b/plat/qemu/common/qemu_bl1_setup.c
index 67f3327..63a0d0c 100644
--- a/plat/qemu/common/qemu_bl1_setup.c
+++ b/plat/qemu/common/qemu_bl1_setup.c
@@ -49,11 +49,11 @@ void bl1_early_platform_setup(void)

 void bl1_plat_arch_setup(void)
 {
-	QEMU_CONFIGURE_BL1_MMU(bl1_tzram_layout.total_base,
-				bl1_tzram_layout.total_size,
-				BL_CODE_BASE, BL1_CODE_END,
-				BL1_RO_DATA_BASE, BL1_RO_DATA_END,
-				BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END);
+//	QEMU_CONFIGURE_BL1_MMU(bl1_tzram_layout.total_base,
+//				bl1_tzram_layout.total_size,
+//				BL_CODE_BASE, BL1_CODE_END,
+//				BL1_RO_DATA_BASE, BL1_RO_DATA_END,
+//				BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END);
 }

 void bl1_platform_setup(void)
diff --git a/plat/qemu/common/qemu_console.c b/plat/qemu/common/qemu_console.c
index 1f00f8a..85cfebf 100644
--- a/plat/qemu/common/qemu_console.c
+++ b/plat/qemu/common/qemu_console.c
@@ -9,15 +9,15 @@
 #include <drivers/console.h>
 #include <drivers/arm/pl011.h>

-static console_t console;
+//static console_t console;

 void qemu_console_init(void)
 {
-	(void)console_pl011_register(PLAT_QEMU_BOOT_UART_BASE,
-			       PLAT_QEMU_BOOT_UART_CLK_IN_HZ,
-			       PLAT_QEMU_CONSOLE_BAUDRATE, &console);
-
-	console_set_scope(&console, CONSOLE_FLAG_BOOT |
-			  CONSOLE_FLAG_RUNTIME);
+//	(void)console_pl011_register(PLAT_QEMU_BOOT_UART_BASE,
+//			       PLAT_QEMU_BOOT_UART_CLK_IN_HZ,
+//			       PLAT_QEMU_CONSOLE_BAUDRATE, &console);
+//
+//	console_set_scope(&console, CONSOLE_FLAG_BOOT |
+//			  CONSOLE_FLAG_RUNTIME);
 }

diff --git a/plat/qemu/qemu/include/platform_def.h b/plat/qemu/qemu/include/platform_def.h
index fbcaa63..76deee1 100644
--- a/plat/qemu/qemu/include/platform_def.h
+++ b/plat/qemu/qemu/include/platform_def.h
@@ -83,7 +83,8 @@
 #define NS_DRAM0_BASE			0x40000000
 #define NS_DRAM0_SIZE			0x3de00000

-#define SEC_SRAM_BASE			0x0e000000
+//#define SEC_SRAM_BASE			0x0e000000
+#define SEC_SRAM_BASE			0x00000000
 #define SEC_SRAM_SIZE			0x00060000

 #define SEC_DRAM_BASE			0x0e100000
@@ -128,8 +129,10 @@
  */
 #define BL1_RO_BASE			SEC_ROM_BASE
 #define BL1_RO_LIMIT			(SEC_ROM_BASE + SEC_ROM_SIZE)
-#define BL1_RW_BASE			(BL1_RW_LIMIT - 0x12000)
-#define BL1_RW_LIMIT			(BL_RAM_BASE + BL_RAM_SIZE)
+//#define BL1_RW_BASE			(BL1_RW_LIMIT - 0x12000)
+//#define BL1_RW_LIMIT			(BL_RAM_BASE + BL_RAM_SIZE)
+#define BL1_RW_BASE			0x8000
+#define BL1_RW_LIMIT	    0x30000

 /*
  * BL2 specific defines.
@@ -137,7 +140,8 @@
  * Put BL2 just below BL3-1. BL2_BASE is calculated using the current BL2 debug
  * size plus a little space for growth.
  */
-#define BL2_BASE			(BL31_BASE - 0x25000)
+//#define BL2_BASE			(BL31_BASE - 0x25000)
+#define BL2_BASE			0x12000
 #define BL2_LIMIT			BL31_BASE

 /*
@@ -148,7 +152,8 @@
  */
 #define BL31_BASE			(BL31_LIMIT - 0x20000)
 #define BL31_LIMIT			(BL_RAM_BASE + BL_RAM_SIZE)
-#define BL31_PROGBITS_LIMIT		BL1_RW_BASE
+//#define BL31_PROGBITS_LIMIT		BL1_RW_BASE
+#define BL31_PROGBITS_LIMIT		0x40000000


 /*
@@ -191,7 +196,8 @@
 /*
  * PL011 related constants
  */
-#define UART0_BASE			0x09000000
+//#define UART0_BASE			0x09000000
+#define UART0_BASE			0x1004B800
 #define UART1_BASE			0x09040000
 #define UART0_CLK_IN_HZ			1
 #define UART1_CLK_IN_HZ			1
diff --git a/plat/renesas/rz/common/rz_common.mk b/plat/renesas/rz/common/rz_common.mk
index 50b7678..8146f5d 100644
--- a/plat/renesas/rz/common/rz_common.mk
+++ b/plat/renesas/rz/common/rz_common.mk
@@ -54,7 +54,9 @@ ifneq (${ARCH_TYPE}, 0)
 MMU_SOURCE		:= plat/renesas/rz/common/drivers/rza_mmu/ARMv8A/rza_mmu.c
 endif

-BL2_SOURCES		+=	lib/cpus/aarch64/cortex_a55.S						\
+#BL2_SOURCES		+=	lib/cpus/aarch64/cortex_a55.S						\
+
+BL2_SOURCES		+=	lib/cpus/aarch64/cortex_a57.S						\
 					${RZ_TIMER_SOURCES}									\
 					${DYN_CFG_SOURCES}									\
 					common/desc_image_load.c							\
```

</details>

<details>
<summary>修正の意味</summary>

上から順に説明していきます。

- sctlrのassert回避
実機の状態と一致していないため、除外します。
共通部分ですがa3ulのbl1はこのコードで作成することは無いため削除で構いません。
- console_pl011_register
virt用のコンソール出力コマンドなので削除します。
- console_pl011_putc
上記に同じです。
- el3_exit
BL2はel3で動作するのですが、このコードではel1で動作してしまいます。
従って、BL2を抜けるときのコードを持ってきました。
つまり、FSP(サンプルのアプリケーションプログラム)もel3で動作します。
elについての説明は[こちら](https://developer.arm.com/documentation/ddi0488/c/programmers-model/armv8-architecture-concepts/exception-levels)などを参考にしてください。
- bl1_plat_arch_setup
メモリマップはハードウェア側で実装するのでここで設定の必要はありません。
- qemu_console_init
virt用のコンソール処理は削除していきます。
- SEC_SRAM_BASE
実機に併せます。
[ハードウェアユーザマニュアル](https://www.renesas.com/jp/ja/document/mah/rza3ul-group-users-manual-hardware?r=1619651)の`5.2 Area Maps`の通りです。
- BL1_RW_BASE
BL1のサイズが128kb未満のためです。
- BL1_RW_LIMIT
実機の通りです。
- BL2_BASE
実機の通りです。BL2をビルドすると分かるのですが、
BL2のエントリポイントは12000番地になります。
展開先はSPIの0x20000000番地に展開された後、11E00番地へ転送されるのですが、
0x200はプログラムヘッダとなっており、12000番地から開始します。

- BL31_PROGBITS_LIMIT
こちらはビルドエラー回避です。
上手い方法が見つからなかったのでとりあえず大きな値を入れています。
- UART0_BASE
実機の通りです。マニュアルだと`5.2 Area Maps`の`Table 5.1 Detailed Address Space (3/3)`や`22.2 Register Descriptions`などに記載があります。
- BL2_SOURCES
これはQEMUがcortex-a55に対応していないための暫定処置になります。
正直大して変わらないので問題ないと思います。
</details>

修正後は再度ビルドを行ってください。
```
CROSS_COMPILE=aarch64-none-elf- make PLAT=qemu DEBUG=1
```

# SRAMの実装
RZ/A3ULは
[ハードウェアユーザマニュアル](https://www.renesas.com/jp/ja/document/mah/rza3ul-group-users-manual-hardware?r=1619651)の`5.2 Area Maps`
にある通りのメモリマップとなっているため
そのように実装していきます。

qemuディレクトリ内でrza3ul.cを修正します。

ヘッダも一応作っておきます。
## rza3ul.h
ここでマシン管理用の構造体を作ってobjectタイプを宣言しておくと
後々楽になるので作っておきます。

現時点では中身は空です。

ファイルの作成場所は既存のコードに倣って`include/hw/arm`にします。
```c
#ifndef __RZA3UL_H__
#define __RZA3UL_H__

#include "target/arm/cpu.h"

#define TYPE_RZA3UL "rza3ul"
#define RZ_A3UL_NUM_CPUS (1)

OBJECT_DECLARE_SIMPLE_TYPE(RzA3ulState, RZA3UL)

struct RzA3ulState {
};
#endif
```

## rza3ul.c
[前回](https://qiita.com/t-yano-tryhard/items/820e2d177ddf208fbbe9)作成した
rza3ul_init()の中にハードを実装していきます。
と言っても今回はcpuとメモリのみです。

```c
#include "qemu/osdep.h"
#include "qemu/datadir.h"
#include "hw/loader.h"
#include "qemu/units.h"
#include "qemu/error-report.h"
#include "exec/address-spaces.h"
#include "qapi/error.h"
#include "hw/boards.h"
#include "hw/arm/rza3ul.h"

static void rza3ul_init(MachineState *machine)
{
    char *sysboot_filename;
    MemoryRegion *sysmem;
    MemoryRegion *sram;

    Object *cpuobj = object_new(machine->cpu_type);

    if (object_property_find(cpuobj, "has_el3"))
        object_property_set_bool(cpuobj, "has_el3", true, &error_fatal);
    if (object_property_find(cpuobj, "has_el2"))
        object_property_set_bool(cpuobj, "has_el2", true, &error_fatal);

    qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);

    sysmem = get_system_memory();
    sram = g_new(MemoryRegion, 1);

    memory_region_init_ram(sram, NULL, "rza3ul.sram", 0x0100000, &error_fatal);
    memory_region_add_subregion(sysmem, 0, sram);

    if (machine->firmware) {
        sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);
        if (sysboot_filename != NULL) {
            if (load_image_targphys(sysboot_filename, 0, 0x8000) < 0) {
                error_report("%s load failed", sysboot_filename);
                exit(1);
            }
            g_free(sysboot_filename);
        } else {
            error_report("%s load failed", sysboot_filename);
            exit(1);
        }
    }
}

static void rza3ul_machine_init(MachineClass *mc)
{
    mc->desc = "Renesas RZ/A3UL";
    mc->init = &rza3ul_init;
    mc->ignore_memory_transaction_failures = true;
    mc->default_ram_size = 2048 * MiB;
    mc->default_ram_id = "ram";
}

DEFINE_MACHINE("rza3ul", rza3ul_machine_init)
```

# ビルドと実行
```shell
cd ../build
# 1回やれば何度もconfigureする必要はありません。
# ../qemu/configure --target-list=aarch64-softmmu --enable-debug
make
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/debug/bl1.bin 
```

おそらく `VNC server running on 127.0.0.1:5900`などと表示されて何も表示されていないと思います。

元々のbl1ではvirtマシン用のコンソール出力を行っていたのですが、今回削除しているからです。
というのもbl2でrza3ulマシン用のコンソール出力を行いたかったためです。

このままでは動いているのかどうかわからないので、int命令を表示するようにして実行します。

実行コマンドは以下です。
```
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/debug/bl1.bin -d int
```

`Exception return from AArch64 EL3 to AArch64 EL3 PC 0x12000`という文字列が表示されると思います。
これはbl2 のエントリポイントになります。
12000以降の処理に対応していないので
その後例外が発生しているのが見て取れると思いますが
これで、bl1が正常に動作していることが確認出来ました。

# デバッグ
うまく動作しない場合は以下でデバッグを行います。
`target/arm/tcg/translate-a64.c`の`aarch64_tr_translate_insn`で
insn取得後あたりに`g_print`などを仕込むとステップ毎の進捗がわかってデバッグしやすいと思います。
```shell
$ gdb qemu-system-aarch64
```
```gd
(gdb) r -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/debug/bl1.bin
```

# 変更点の説明

## cpuの作成
見たままだと思います。
```c
Object *cpuobj = object_new(machine->cpu_type);
```

## elの設定
bl1やbl2、また公開されているサンプルプログラムも全てEL3で動作するようになっています。
また、`-bios`のオプションを指定すると該当ファイルがEL3で動作するようになります。
cpu側でEL3に対応するように設定してあげます。
EL2は今回は不要なのですがついでに設定しています。
```c
    if (object_property_find(cpuobj, "has_el3"))
        object_property_set_bool(cpuobj, "has_el3", true, &error_fatal); 
    if (object_property_find(cpuobj, "has_el2"))
        object_property_set_bool(cpuobj, "has_el2", true, &error_fatal); 
```

## realize
デバイスの作成です。
これをすることで実際にqemuにデバイスとして認識されます。
```c
    qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
```

## メモリ空間にSRAMを設定
get_system_memoryで全体の仮想メモリ空間を取得します。
g_newで作成した後、0x100000バイトサイズのsram領域を
`memory_region_init_ram`で作成します。
本当は[マニュアル](https://www.renesas.com/jp/ja/document/mah/rza3ul-group-users-manual-hardware?r=1619651)にある通り、0x30000のサイズなのですが
qemuのセミホスティング機能で余分に確保する必要があり
仕方なくサイズを増やしています。

`memory_region_add_subregion`でsramを0番地にマッピングしています。
つまり0～0x100000がsramエリアとして作成されます。
```c
    sysmem = get_system_memory();
    sram = g_new(MemoryRegion, 1);

    memory_region_init_ram(sram, NULL, "rza3ul.sram", 0x0100000, &error_fatal);
    memory_region_add_subregion(sysmem, 0, sram);
```

## bl1のロード
qemu特有の機能です。
qemuのAPIをそのまま用いてbl1をロードします。
`load_image_targphys`の第2引数が実際にロードするアドレスである0番地
第3引数は読み出すサイズです。
ここは厳密である必要は無く、それっぽい感じで大丈夫です。
```c
    if (machine->firmware) {
        sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);
        if (sysboot_filename != NULL) {
            if (load_image_targphys(sysboot_filename, 0, 0x8000) < 0) {
                error_report("%s load failed", sysboot_filename);
                exit(1);
            }
            g_free(sysboot_filename);
        } else {
            error_report("%s load failed", sysboot_filename);
            exit(1);
        }
    }
```

[QEMUにマシンを追加する(BL2動作 FSPロード失敗まで)](https://qiita.com/t-yano-tryhard/items/8726c9986934b2b180c6)に続きます。