QEMUにマシンを追加する(BL2動作 FSPロード失敗まで)


[前回](https://qiita.com/t-yano-tryhard/items/a0bc5ae19cfce35b8f7d)に引き続き
ハード部分を作成していきます。

今回は[bl2](https://github.com/renesas/rza-initial-program-loader)を動作させます。

- [BL2とは](#bl2とは)
- [起動コマンド](#起動コマンド)
- [bl2の目的](#bl2の目的)
- [動くようになったコード](#動くようになったコード)
- [ビルドと実行](#ビルドと実行)
- [デバッグ](#デバッグ)
- [変更内容の説明(rza3ul.h)](#変更内容の説明rza3ulh)
- [変更内容の説明(rza3ul.c)](#変更内容の説明rza3ulc)
  - [定義値](#定義値)
  - [MemoryRegionOps](#memoryregionops)
  - [グローバル変数類](#グローバル変数類)
  - [デバイス初期化](#デバイス初期化)
    - [メモリマップ](#メモリマップ)
    - [tzc_init](#tzc_init)
    - [spi_init](#spi_init)
    - [bl1のロード](#bl1のロード)
  - [a3ul_mmio_read](#a3ul_mmio_read)
  - [a3ul_mmio_write](#a3ul_mmio_write)
  - [rza3ul_uart](#rza3ul_uart)


# BL2とは
ブートローダの一種です。
[QEMUにマシンを追加する(BL1動作)](https://qiita.com/t-yano-tryhard/items/a0bc5ae19cfce35b8f7d)にも記載した通り、
RZ/A3ULでは複数のブートローダが起動します。

[サンプルプログラム](https://www.renesas.com/jp/ja/document/scd/rza3ul-evk-example-projects?language=en&r=1619666)の起動はBL2から行うので
今回の実装ではBL2が最終段のブートローダになります。

# 起動コマンド
[bl1](https://qiita.com/t-yano-tryhard/items/a0bc5ae19cfce35b8f7d)の時と変わりません。
```
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/debug/bl1.bin
```
# bl2の目的
[bl2](https://github.com/renesas/rza-initial-program-loader)はアプリケーションプログラムをロードして実行することが目的です。
アプリケーションプログラムのロードと実行は[rz_update_descs](https://github.com/renesas/rza-initial-program-loader/blob/f3dbf22abf50d3c0dd37c40fd37d9819613c8c4e/plat/renesas/rz/common/plat_image_load.c#L126)で  
eretの次のアドレスを指定アドレスに設定し、
eretを行う事で実現します。

`rz_update_descs`の手前に記載がある`memcpy`は
SPI領域からDDR領域にアプリケーションプログラムをコピーするものです。

今回はDDR領域を使用せずにSPI領域のまま起動させます。

ではアプリケーションプログラムをSPI領域に書きこむのは
誰がいつどのように行うのでしょうか？

実機では[flash writer](https://github.com/renesas-rz/rzg2_flash_writer)というものを使います。

つまりflash writerで事前にSPI ROMに書きこみます。

qemu上に作成するハードウェアでは
flash writerの代わりに`-kernel`オプションを使用して代替とします。

が、一旦kernelオプションは無しで`rza_load_fsp`内でpanicルートに入るまでを実装していきます。

# 動くようになったコード
先に結論です。
<details>
<summary>rza3ul.c</summary>

```c
#include "qemu/osdep.h"
#include "qemu/datadir.h"
#include "qemu/log.h"
#include "kvm_arm.h"
#include "hw/loader.h"
#include "qemu/units.h"
#include "exec/address-spaces.h"
#include "qapi/error.h"
#include "target/arm/cpregs.h"
#include "hw/boards.h"
#include "hw/qdev-properties.h"
#include "hw/arm/rza3ul.h"
#include "elf.h"

#define RZA_MMIO_BASE               (0x10000000)
#define RZG2L_SCIF0_BASE            (0x1004B800)
#define RZG2L_SPIMULT_BASE          (0x10060000)
#define RZG2L_SYC_BASE              (0x11000000)
#define CPG_BASE                                        (0x11010000)
#define RZG2L_SYSC_BASE             (0x11020000)
#define RZG2L_TZC_SPI_BASE          (0x11060000)
#define RZG2L_TZC_DDR_BASE          (0x11070000)
#define RZG2L_DDR_PHY_BASE          (0x11400000)
#define RZG2L_DDR_MEMC_BASE         (0x11410000)

#define BOOT_MODE_SPI_1_8       (3)
#define CNTFID0         (0x020)

#define PID0_OFF                                        (0xfe0)
#define PID1_OFF                                        (0xfe4)
#define GATE_KEEPER_OFF                         (0x008)

#define CPG_PLL4_STBY                           (CPG_BASE + 0x0010)     /* PLL4 (SSCG) standby control register */
#define CPG_PLL4_CLK1                           (CPG_BASE + 0x0014)     /* PLL4 (SSCG) output clock setting register 1 */
#define CPG_PLL4_MON                            (CPG_BASE + 0x001C)     /* PLL4 (SSCG) monitor register */
#define CPG_PLL6_STBY                           (CPG_BASE + 0x0020)     /* PLL6 (SSCG) standby control register */
#define CPG_PLL6_CLK1                           (CPG_BASE + 0x0024)     /* PLL6 (SSCG) output clock setting register 1 */
#define CPG_PLL6_MON                            (CPG_BASE + 0x002C)     /* PLL6 (SSCG) monitor register */

#define CPG_CLKON_CA55                          (CPG_BASE + 0x0500)     /* Clock ON / OFF register CA55 */
#define CPG_CLKON_OCTA                          (CPG_BASE + 0x05F4)     /* Clock ON / OFF register OCTA */
#define CPG_CLKMON_CA55                         (CPG_BASE + 0x0680)     /* Clock monitor register CA55 */
#define CPG_RST_CA55                            (CPG_BASE + 0x0800)     /* Reset ONOFF register CA55 */
#define CPG_RST_OCTA                            (CPG_BASE + 0x08F4)     /* Reset ONOFF register OCTA */
#define CPG_RSTMON_CA55                         (CPG_BASE + 0x0980)     /* Reset monitor register CA55 */

#define PLL4_STBY_RESETB                                                        (1 << 0)
#define PLL4_STBY_RESETB_WEN                                            (1 << 16)
#define PLL4_MON_PLL4_RESETB                                            (1 << 0)
#define PLL4_MON_PLL4_LOCK                                                      (1 << 4)
#define PLL6_STBY_RESETB                                                        (1 << 0)
#define PLL6_STBY_RESETB_WEN                                            (1 << 16)
#define PLL6_MON_PLL6_RESETB                                            (1 << 0)
#define PLL6_MON_PLL6_LOCK                                                      (1 << 4)

#define DENALI_CTL_00           (0x0000)
#define DENALI_CTL_56           (0x00E0)
#define DENALI_CTL_59           (0x00EC)
#define DENALI_CTL_67           (0x010C)
#define DENALI_CTL_146          (0x0248)
#define DENALI_CTL_147          (0x024C)
#define DDRPHY_R18                      (0x100)
#define DDRPHY_R27                      (0x124)
#define DDRPHY_R36                      (0x148)
#define DDRPHY_R37                      (0x14C)
#define DDRPHY_R42                      (0x160)
#define DDRPHY_R46                      (0x170)

#define DDRMC_R000              DENALI_CTL_00
#define DDRMC_R004              DENALI_CTL_56
#define DDRMC_R005              DENALI_CTL_59
#define DDRMC_R008              DENALI_CTL_67
#define DDRMC_R021              DENALI_CTL_146
#define DDRMC_R022              DENALI_CTL_147

#define SPIM_CMNSR      0x0048

#define SCR                             (0x04)
#define FTDR                    (0x06)
#define FSR                             (0x08)
#define FDR                             (0x0E)
#define FSR_TEND_SHIFT  (6)
#define FSR_TEND                (1<<FSR_TEND_SHIFT)
#define SCR_TIE         0x80
#define FSR_TDFE        0x20

static uint64_t a3ul_mmio_read(void *opaque, hwaddr, unsigned size);
static void a3ul_mmio_write(void *opaque, hwaddr addr, uint64_t value, unsigned size);
static uint64_t rza3ul_uart(void *opaque, uint64_t addr, uint64_t value, unsigned size, int type);
static void tzc_init(void);
static void spi_init(void);

static MemoryRegionOps mmio_op = {
    .read  = a3ul_mmio_read,
    .write = a3ul_mmio_write,
    .endianness = DEVICE_LITTLE_ENDIAN
};

static uint32_t cpg[0x10000] = {};
static uint32_t syc[0x10000] = {};
static uint32_t tzcd[0x10000] = {};
static uint32_t tzcs[0x10000] = {};
static uint32_t ddrp[0x10000] = {};
static uint32_t ddrm[0x10000] = {};
static uint32_t spirega[0x10000] = {};
static volatile unsigned short fsr = 0;

static void rza3ul_init(MachineState *machine) {
    RzA3ulState *s = RZA3UL(machine);
    char *sysboot_filename;
    MemoryRegion *sysmem;
    MemoryRegion *sram;
    MemoryRegion *ram;
    MemoryRegion *mmio;
    MemoryRegion *spi;

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

    MemoryRegionOps *ops = &mmio_op;

    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);
    s->cpus[0] = ARM_CPU(first_cpu);

    sysmem = get_system_memory();
    sram = g_new(MemoryRegion, 1);    
    mmio = g_new(MemoryRegion, 1);    
    spi = g_new(MemoryRegion, 1);    
    ram = g_new(MemoryRegion, 1);
    
    memory_region_init_ram(sram, NULL, "rza3ul.sram", 0x100000, &error_fatal);
    memory_region_add_subregion(sysmem, 0, sram);
    memory_region_init_io(mmio, OBJECT(s), ops, s, "rza3ul.mmio", 0x10000000);
    memory_region_add_subregion(sysmem, RZA_MMIO_BASE, mmio);
    memory_region_init_ram(spi, NULL, "rza3ul.spi", 0x10000000, &error_fatal);
    memory_region_add_subregion(sysmem, 0x20000000, spi);
    memory_region_init_ram(ram, NULL, "rza3ul.ram", 0x80000000, &error_fatal);
    memory_region_add_subregion(sysmem, 0x40000000, ram);

    tzc_init();
    spi_init();
    if (machine->firmware) {
        g_print("has firmware %s\n", 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("BIOS not supported for this machine");
            exit(1);
        }
    }

    uint8_t *ptr = memory_region_get_ram_ptr(sram) + 0x10000;
    *ptr = BOOT_MODE_SPI_1_8;

}

static uint64_t a3ul_mmio_read(void *opaque, hwaddr addr, unsigned size) {
    hwaddr paddr = addr | RZA_MMIO_BASE;
    uint32_t offset = paddr & 0xFFFF;
    uint32_t *tzc = tzcs;
    uint32_t *ddr = ddrm;
    uint64_t value = 0;
    switch (paddr & 0xFFFF0000) {
        case RZG2L_SYC_BASE:
            if (offset == CNTFID0)
                return 24000000;
            return 0;
        case RZG2L_SYSC_BASE:
            if (addr == 0x1020a00)
                return 0x203;
            return 0;
        case CPG_BASE:
            return cpg[offset];
        case RZG2L_TZC_DDR_BASE:
            tzc = tzcd;
            /* Falls through. */
        case RZG2L_TZC_SPI_BASE:
            return tzc[offset];
        case RZG2L_DDR_PHY_BASE:
            ddr = ddrp;
            /* Falls through. */
        case RZG2L_DDR_MEMC_BASE:
            return ddr[offset];
        case RZG2L_SPIMULT_BASE:
            return spirega[offset];
    }
    if ((paddr & 0xFFFFFF00) == RZG2L_SCIF0_BASE) {
        value = rza3ul_uart(opaque, paddr, 0, size, 0);
    }
    return value;
}

static void a3ul_mmio_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) {
    hwaddr paddr = addr | RZA_MMIO_BASE;
    uint32_t offset = paddr & 0xFFFF;

    uint32_t wen;
    uint32_t *tzc = tzcs;
    uint32_t bef;
    uint32_t *p;
    uint32_t *ddr = ddrm;

    switch (paddr & 0xFFFF0000) {
        case CPG_BASE:
            cpg[offset] = (uint32_t)value;
            switch (paddr) {
                case CPG_PLL4_STBY:
                    if (value & PLL4_STBY_RESETB_WEN) {
                        if (value & PLL4_STBY_RESETB)
                            cpg[CPG_PLL4_MON & 0xffff] |= (PLL4_MON_PLL4_RESETB | PLL4_MON_PLL4_LOCK);
                        else
                            cpg[CPG_PLL4_MON & 0xffff] &= ~(PLL4_MON_PLL4_RESETB | PLL4_MON_PLL4_LOCK);
                    }
                    break;
                case CPG_PLL6_STBY:
                    if (value & PLL6_STBY_RESETB_WEN) {
                        if (value & PLL6_STBY_RESETB)
                            cpg[CPG_PLL6_MON & 0xffff] |= (PLL6_MON_PLL6_RESETB | PLL6_MON_PLL6_LOCK);
                        else
                            cpg[CPG_PLL6_MON & 0xffff] &= ~(PLL6_MON_PLL6_RESETB | PLL6_MON_PLL6_LOCK);
                    }
                    break;
                default:
                    break;
            }
            if ((CPG_CLKON_CA55 <= paddr) && (paddr <= CPG_CLKON_OCTA)) {
                wen = ((value & 0xFFFF0000) >> 16);
                if (wen) {
                    p = &cpg[(paddr + (CPG_CLKMON_CA55 - CPG_CLKON_CA55)) & 0xffff];
                    bef = *p;
                    *p = (wen & (value & 0xffff));
                }
            } else if ((CPG_RST_CA55 <= paddr) && (paddr <= CPG_RST_OCTA)) {
                wen = ((value & 0xFFFF0000) >> 16);
                if (wen) {
                    p = &cpg[(paddr + (CPG_RSTMON_CA55 - CPG_RST_CA55)) & 0xffff];
                    bef = *p;
                    *p = (wen ^ (value & 0xffff));
                }
            }
            break;
        case RZG2L_SYC_BASE:
            syc[offset] = (uint32_t)value;
            break;
        case RZG2L_TZC_DDR_BASE:
            tzc = tzcd;
            /* Falls through. */
        case RZG2L_TZC_SPI_BASE:
            if (offset == GATE_KEEPER_OFF) {
                if (value) 
                    tzc[offset] |= (value << 16);
                else
                    tzc[offset] = 0;
            } else {
                tzc[offset] = value;
            }
            break;
        case RZG2L_DDR_PHY_BASE:
            ddr = ddrp;
            if (offset == DDRPHY_R46) {
                ddr[offset] = ((uint32_t)value) << 2;
            } else {
                ddr[offset] = value;
                if (offset == DDRPHY_R27 && (value & (~(uint32_t)0xFBFFFFFF)) == 0) {
                    ddr[DDRPHY_R42] = 3;
                } else if ((offset == DDRPHY_R18) && ((value & 0x10000000) == 0x10000000)) {
                    ddr[DDRPHY_R18] &= ~(uint32_t)0x10000000;
                    if (value == 0x50200000) {
                        ddr[DDRPHY_R36] |= 3;
                        ddr[DDRPHY_R37] &= ~(uint32_t)3;
                    } else if (value == 0x34200000) {
                        ddr[DDRPHY_R18] |= 3;
                    }
                }
            }
            break;
        case RZG2L_DDR_MEMC_BASE:
            ddr[offset] = (uint32_t)value;
            if ((offset == DDRMC_R000) && ((value & 1) == 1)) {
                ddr[DDRMC_R021] |= 0x02000000;
            } else if ((offset == DDRMC_R008) && ((value & 0x02800000) == 0x02800000)) {
                ddr[DDRMC_R022] |= (uint32_t)(1 << 3);
            } else if (offset == DDRMC_R004) {
                bef = ddr[DDRMC_R005];
                if ((value & 0x11) == 0x11) {
                    ddr[DDRMC_R005] = (bef & ~(uint32_t)0x7f000000) | 0x48000000;
                } else if ((value & 0x2) == 0x2) {
                    ddr[DDRMC_R005] = (bef & ~(uint32_t)0x7f000000) | 0x40000000;
                }
            }
            break;
        case RZG2L_SPIMULT_BASE:
            spirega[offset] = (uint32_t)value;
            break;
    }   
    if ((paddr & 0xFFFFFF00) == RZG2L_SCIF0_BASE) {
        rza3ul_uart(opaque, paddr, value, size, 1);
    }
}

static uint64_t rza3ul_uart(void* opaque, uint64_t addr, uint64_t value, unsigned size, int type) {
    uint32_t offset = addr & 0xff;
    char c = (char)value;
    short vs = (short)value;
    uint64_t ret = 0;

//    CPUState *cpu = CPU(ARM_CPU(first_cpu));
//    volatile uint64_t pc = cpu->env_ptr->pc;
    if (type) {
        if (offset == FTDR) {
            if (c != 0xd)
                g_print("%c", c);
        } else if (offset == FSR) {
            fsr = vs;
        }
    } else {
        if (offset == FDR) {
            ret = 0;
        } else if (offset == FSR) {
            ret = fsr;
        }
    }
    return ret;
}

static void tzc_init(void) {
    tzcd[PID0_OFF] = 0x60;
    tzcd[PID1_OFF] = 0xb4;
    tzcs[PID0_OFF] = 0x60;
    tzcs[PID1_OFF] = 0xb4;
}
static void spi_init(void){
    spirega[SPIM_CMNSR] = 1;
}

static void rza3ul_machine_init(MachineClass *mc) {
    mc->desc = "RZ/A3UL (Cortex-A57)";
    mc->init = rza3ul_init;
    mc->block_default_type = IF_SD;
    mc->units_per_default_bus = 1;
    mc->min_cpus = RZ_A3UL_NUM_CPUS;
    mc->max_cpus = RZ_A3UL_NUM_CPUS;
    mc->default_cpus = RZ_A3UL_NUM_CPUS;
    mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a57");
}

DEFINE_MACHINE("rza3ul", rza3ul_machine_init)
```

</details>

<details>
<summary>rza3ul.h</summary>

```c
#ifndef __RZA3UL_H__
#define __RZA3UL_H__

#include "hw/intc/arm_gicv3.h"
#include "target/arm/cpu.h"
#include "hw/cpu/cluster.h"

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

OBJECT_DECLARE_SIMPLE_TYPE(RzA3ulState, RZA3UL)

struct RzA3ulState {
    DeviceState parent_obj;
    CPUClusterState cluster;
    ARMCPU *cpus[1];
    const hwaddr *memmap;
    GICv3State gic;
    MemoryRegion sram;
};

#endif
```

</details>

# ビルドと実行
```shell
make
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/bl1.bin
```
以下の様に表示されて固まると思います。
```
Check Application Header...
ERROR:   The value of the entry point address does not match its inverted value
```
FSP(アプリケーションプログラム)が無いので[ヘッダチェック](https://github.com/renesas/rza-initial-program-loader/blob/main/plat/renesas/rz/common/plat_image_load.c#L118)で弾かれて
`panic()`に入っています。
これでBL2の基本動作を通すことが出来ました。

# デバッグ
hw実装部分の不具合であれば以下でデバッグをするのが良いと思います。
```
./qemu-system-aarch64 -M rza3ul -cpu cortex-a57 -semihosting -L ../qemu/pc-bios/ -bios ../../ipl/build/qemu/bl1.bin -nographic -S -gdb tcp::10000 2>&1 | tee qemu.log
```
別のターミナルから以下コマンドを実行することで普通にbl2のデバッグが出来るので
どこで止まっているかがわかると思います。
```shell
$ cd /path/to/ipl
$ ./aarch64-none-lf-gdb build/a3ul/debug/bl2/bl2.elf

(gdb) target remote localhost:10000
(gdb) c
... # 止まってると思ったら
(gdb) Ctrl + c
(gdb) bt
```

# 変更内容の説明(rza3ul.h)
あまり有効に活用できていないので
`gic`くらいしか使用しているものはありません。
未使用ですがなんとなくそれっぽい感じで実装しています。

# 変更内容の説明(rza3ul.c)
上から順に説明していきます。

## 定義値
殆どすべてルネサスが公開しているコード中に記載のある値を
コピペで貼り付けているだけです。

`CPG_BASE`のみ[cpg_regs.h](https://github.com/renesas/rza-initial-program-loader/blob/main/plat/renesas/rz/common/include/cpg_regs.h)から取ってきているのは
CPGの各レジスタが`CPG_BASE`を元に記載されていたためです。

scifaのレジスタの特定ビットの説明はコードから読み取れると思います。

## MemoryRegionOps
mmioレジスタに値が書きこまれたときの処理を記載します。

ここでは読み込み時に`a3ul_mmio_read`が
書き込み時に`a3ul_mmio_write`が呼ばれるように設定しています。

本来はioレジスタ毎に作成するのがベストでしょうが、
今回作成するものはそこまでの厳密さを求めていないので
とりあえずプログラムが実行できる最低限のものを作ります。

## グローバル変数類
mmioレジスタ用の受け皿を確保します。

## デバイス初期化

### メモリマップ
メモリマップは例によって
[ハードウェアユーザマニュアル](https://www.renesas.com/jp/ja/document/mah/rza3ul-group-users-manual-hardware?r=1619651)の`5.2 Area Maps`の通りです。

mmioだけは`memory_region_init_ram`では無く、
`memory_region_init_io`を使って初期化を行います。
理由はコールバック関数を使うからです。

### tzc_init
意味は分かりませんが、ルネサスのコードを読み解くと
こういう風に設定すると該当部分が通ることがわかります。
ちなみにHW上このようになっている保障はないし、
まず間違いなくそうではないでしょう。

ハードウェアのすべての機能を詳細に実装することは(能力的や時間的に)不可能と思われるので
エミュレータを作る側の目的に応じて、あまり重要ではないと思われる部分は
このようにして簡易実装して削っていきます。

### spi_init
これもコードを通すだけです。

### bl1のロード
[QEMUにマシンを追加する(BL1動作)](https://qiita.com/t-yano-tryhard/items/a0bc5ae19cfce35b8f7d)で記載した通りです。

## a3ul_mmio_read
MMIOレジスタにreadアクセスした時に返す値を設定します。

アドレスは`memory_region_add_subregion`で設定したオフセットを差し引いた値
つまり、デバイスとしての絶対アドレスが渡されるので注意です。

uart(コンソール)は後々1個のデバイスとして切り出したいので
とりあえずここでは処理をまとめておきます。


特に説明はないです。引っかかった時にBL2のソースコードを読んで
通るように修正しただけです。

## a3ul_mmio_write
MMIOレジスタにwriteアクセスした時に行う処理を設定します。
`a3ul_mmio_read`と同様なので特にありません

もちろん実際のハードウェアがこのように動作している保障はないし
実際のハードウェアがどのように動作しているかの詳細はわかりません。

## rza3ul_uart
コンソール関連の処理を切り出しています。

コードを見ればわかると思いますが`FTDR`レジスタへ書きこまれた文字が
コンソールに表示されます。


[QEMUにマシンを追加する(FSP移行まで)](https://qiita.com/t-yano-tryhard/items/3f59a4bf3f28b4411742)に続きます。