bitvisorのRISC-V移植? (起動ログが出るとこまで)

先日のbitvisor summitに初参加し，advent calendarの募集があり，
すこしてでも力に慣れればと思いこの記事を書きました．
初心者なので指摘等をお待ちしています．

この記事はBitVisorをRISC-Vへ移植を挑戦したという記録です．
結論からいうと現状では，guestとして何かを動かせる状況にはありませんというところです．

BitVisorはx86への最適化がされていて，ほかのアーキテクチャへの移植はまだされていないため（予定もないのかな？）コア部分とアーキテクチャ依存コードの分離のようなことはされていないようにみえます．
この部分から設計し直して移植は大変だと思いまず，まず動くことを目指しています．
コアは一つだけを使い，デバイスは，uart,virtio-block,割り込みコントローラー，タイマーを使いますが、今はシリアルだけ.

RISC-Vのhypervisor extensionはまだ，draftの状況ですが，WDがQEMU,xvisor,Linuxへの実装をしていて（まだそれぞれupstreamにはとりこまれていない），HostとしてLinuxが動くところまでしてくれています．(hypervisor extensionのdraftもWDを中心に行っているようですが）
QEMU,Linuxは以下のリポジトリに公開されているのでうごかして見たい方は参考にするとよいかと思います．
https://github.com/kvm-riscv/howto

最低限,移植に必要なことを現状わかっていることでまとめると．

* 起動時の，register等の初期化とvmm_mainの呼び出し 
* 各デバイスの初期化
    * UART
    * 割り込みコントローラー (TBD)
    * MMU (TBD)
* pcpu, vcpu (TBD)
* Guestのvmenter,vmexitに対応する実装 (TBD)

TBDが多いですががんばっていきたい
それぞれの実装はできているのでちょこちょこ移していきたい
そっちはこのリポジトリにある
https://github.com/sux2mfgj/rv_ss

#### メモリマップとFirmware

bootloaderとしてのBitVisorのロードがまずあると思いますが
ここで行うRISC-Vの実装では，OpenSBIをFirmwareとして，BitVisor自体は，QEMUのloader deviceで直接メモリに展開された形にしてから動かし始めています．
TargetのQEMU virtのmemory mapは

```qemu/hw/riscv/virt.c
static const struct MemmapEntry {
    hwaddr base;
    hwaddr size;
} virt_memmap[] = {
..
    [VIRT_PLIC] =        {  0xc000000,     0x4000000 },
    [VIRT_UART0] =       { 0x10000000,         0x100 },
    [VIRT_VIRTIO] =      { 0x10001000,        0x1000 },
    [VIRT_FLASH] =       { 0x20000000,     0x4000000 },
    [VIRT_DRAM] =        { 0x80000000,           0x0 },
...
};
```
のように成っているようで．
ramは0x80000000から起動時に設定したサイズになるようです．

OpenSBIは0x80000000からの範囲の使い，処理が終わると0x80200000へjmpしてきます.
ので，linker scriptはriscv用に書くと以下の感じ
initfunc用にdataに入れてやるのを忘れると、初期化ルーチンが走らず悲しいので気をつける

```
ENTRY(entry)

virt = 0x80200000;
phys = 0x80200000;

SECTIONS
{
    . = virt;
    head = .;

    .text :
    {
        *(.text.entry)
        *(.text)
    }
    .data :
    {
        *(.data)
        *(.rodata)
        . = ALIGN(8);
        __initfunc_start = .;
        *(.initfunc)
        __initfunc_end = .;
    }
    .bss :
    {
        *(.bss)
    }

    end = .;
}
```
#### 起動とCのコードに入るまで
これで，無事にbitvisorへ処理がうつりました.
BitVisor自体の起動はentry.sにあるかと思います.
ここには，32bit code, 64bit code,multiboot headerや，UEFIのエントリー等があるのでなかなか読むのが辛いのですが
移植版では，最小限の初期化のみを行って，paging(ストレートマップ)等は，Firmwareが設定したものをとりあえずそのまま使います．
のでコードは簡単であり

```entry.s
.section .text.entry
.globl entry
entry:
    csrw sie, zero
    csrw sip, zero
    li tp, 0
    call vmm_main
```
のようにしました．
sie,sipはexceptionとinterruptに関する制御レジスタで，`zero`を入れることで，両方とも無効にしてあります．
`tp`はthread pointerと呼ばれるレジスタで，thread local storageへのポインタを格納しておくレジスタとして使われるようです．
後はCのコードに入っていきます

#### シリアルデバイスの初期化

初期化に関しては，INITFUNCという機構を導入しているみたいですが，シリアルに関してはこの機構を使わずに，
１つめの文字出力がされるタイミングで初期化を行っているみたいで，それを少しいじりました.
RV64マクロがその部分です．シリアルの各レジスタに関しては，必要があれば以下がシンプルで良さげです．
http://byterunner.com/16550.html

```
static void
serial_init (unsigned int port)
{
#ifdef RV64
    reg_write(INTERRUPT_ENABLE_REGISTER, 0x00);
    reg_write(LINE_CONTROL_REGISTER, 0x80);
    reg_write(LSB_OF_DIVISOR_LATCH, 0x03);
    reg_write(MSB_OF_DIVISOR_LATCH, 0x00);
    reg_write(LINE_CONTROL_REGISTER, 0x03);
    reg_write(FIFO_CONTROL_REGISTER, 0x07);
    reg_write(INTERRUPT_ENABLE_REGISTER, 0x01);

#else
	asm_outb (port + PORT_LINECTL, LINECTL_DLAB_BIT);
	asm_outb (port + PORT_RATELSB, INIT_RATELSB);
	asm_outb (port + PORT_RATEMSB, INIT_RATEMSB);
	asm_outb (port + PORT_LINECTL, INIT_LINECTL);
	asm_outb (port + PORT_MODEMCTL, INIT_MODEMCTL);
	asm_outb (port + PORT_INTR, INIT_INTR);
	asm_outb (port + PORT_FIFOCTL, INIT_FIFOCTL);
#endif
}
```

書き込みと読み込みに関しては以下のようにしました．

```
static void
serial_send (unsigned int port, unsigned char data)
{
#ifdef RV64
    while((reg_read(LINE_STATUS_REGISTER) & (1 << 5)) == 0);
    reg_write(TRANSMIT_HOLDING_REGISTER, data);
#else
	u8 status;

	asm_outb (port + PORT_DATA, data);
	do
		asm_inb (port + PORT_LINESTATUS, &status);
	while (!(status & LINESTATUS_TX_BIT));
#endif
}

static unsigned char
serial_recv (unsigned int port)
{
#ifdef RV64
    while((reg_read(LINE_STATUS_REGISTER) & 0x1) == 0);
    return reg_read(RECEIVE_HOLDING_REGISTER);
#else

	u8 status, data;

	do
		asm_inb (port + PORT_LINESTATUS, &status);
	while (!(status & LINESTATUS_RX_BIT));
	asm_inb (port + PORT_DATA, &data);
	return data;
#endif
}
```

こんな感じにしてやると，printf等が呼び出していい感じにしてくれます．

後はビルドして実行すると

```
$ qemu/riscv64-softmmu/qemu-system-riscv64 \
        -monitor null \
        -cpu rv64,x-h=true \
        -display none \
        -serial mon:stdio \
        -M virt \
        -m 128M \
        -kernel local/platform/qemu/virt/firmware/fw_jump.elf \
        -device loader,file=bitvisor_rv64,addr=0x80200000 \
        -gdb tcp::12345 \


OpenSBI v0.5 (Nov 13 2019 21:36:24)
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name          : QEMU Virt Machine
Platform HART Features : RV64ACDFHIMSU
Platform Max HARTs     : 8
Current Hart           : 0
Firmware Base          : 0x80000000
Firmware Size          : 116 KB
Runtime SBI Version    : 0.2

PMP0: 0x0000000080000000-0x000000008001ffff (A)
PMP1: 0x0000000000000000-0xffffffffffffffff (A,R,W,X)
Starting BitVisor...
Copyright (c) 2007, 2008 University of Tsukuba
All rights reserved.
Processor 0 (BSP)
Using RV64 H extension.
panic%  
```

とりあえず起動できるようになったので、残りもがんばる