TinyEMU をビルドしてEmscripten上でLinuxカーネルを起動するメモ

前回( [RISC-V 開発環境をbuildrootとcrosstool-ngで用意する](https://qiita.com/okuoku/items/3133c75d26c57394fd1a) )はカーネルをqemuで起動したので、今度はWebブラウザ上で動作するRISC-VエミュレータであるところのTinyEMU( https://bellard.org/tinyemu/ )で起動してみる。

- https://sophisticated-overjoyed-treatment.glitch.me/run.html
  - ビルドしたTinyEMUとLinuxカーネル(rootfsが無いので直ぐpanicする)
- dmesg: https://gist.github.com/okuoku/23c2fbd2400d21949fbb5e80c686149d#file-dmesg-txt

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/3f00ef5f-a32a-acfa-78cc-d63265ca44cb.png)

親のカーネルpanicより見た。

# tl;dr

今回はTinyEMU標準のI/Oライブラリは一切使用せず、EmscriptenのC言語側からVMを構成して起動する形を取ってみた。

- **ブートローダはTinyEMU付属のものを使用する必要がある** 。付属のローダには改造が施されていて、普通のローダではコンソールが出ない。
- ビルド自体はEmscripten 2.0.2 の `emcc` で単にビルドしてEmscripten標準のHTML出力でも動作する。

# TinyEMU のビルド

ビルド自体は特に難しいところはなく、適当に `*.c` をコンパイルして出力すれば動くものができる。

- https://github.com/okuoku/patched-tinyemu-proto/blob/302211483e374b61dd686e1662b59a33eb5fc501/runbuild.bat

```shell
emcc jsemu.c softfp.c virtio.c fs.c fs_net.c fs_wget.c fs_utils.c simplefb.c pci.c ^
json.c block_net.c iomem.c cutils.c aes.c sha256.c riscv_cpu.c riscv_machine.c machine.c ^
--llvm-opts 2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing ^
-DCONFIG_FS_NET -O3 --memory-init-file 0 --closure 0 -s NO_EXIT_RUNTIME=1 ^
-s "EXPORTED_FUNCTIONS=['_console_queue_char','_vm_start','_fs_import_file','_display_key_event','_display_mouse_event','_display_wheel_event','_net_write_packet','_net_set_carrier','_main']" ^
-s "EXTRA_EXPORTED_RUNTIME_METHODS=[\"ccall\", \"cwrap\"]" ^
--js-library js/lib.js  -s WASM=1 -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1  ^
-DMAX_XLEN=32 -DCONFIG_RISCV_MAX_XLEN=32 -s ASSERTIONS=1 --emrun -g4 ^
--source-map-base http://localhost:6931/ --preload-file kernel --preload-file bbl32.bin emmain.c -o run.html
```

## 初期設定ルーチン(いわゆる `main`)

通常のTinyEMUでは、VMの設定はJavaScript側から実施するが、ちょっとネイティブ版と統一の上で不便だったので `main` 関数を用意することにした。

- https://github.com/okuoku/patched-tinyemu-proto/blob/302211483e374b61dd686e1662b59a33eb5fc501/emmain.c
  - 作成した `emmain.c`

ブートローダー `bbl32.bin` と Linuxカーネル `kernel` は一旦Emscriptenの `--preload-file` アプリ側に埋め込み、C言語のファイルI/Oで読み込んでいる。

`kernel` は前回( https://qiita.com/okuoku/items/3133c75d26c57394fd1a )buildrootでビルドしたものがそのまま使用できるが、ブートローダ `bbl32.bin` はTinyEMU付属( https://bellard.org/tinyemu/diskimage-linux-riscv-2018-09-23.tar.gz )のものを使用しなければならない。

# TinyEMU固有(?)のHTIF実装

TinyEMUはいわゆるUARTデバイスをエミュレートしておらず、Spike(RISC-V公式のエミュレータ)が実装していたHTIF(Host-Target IF)を実装している。

ただ、qemu等の他の実装と異なり、TinyEMUのHTIF実装はアドレスが決め打ちになっており、ブートローダの方をパッチしてアドレスを伝達している。

- https://github.com/okuoku/patched-tinyemu-proto/blob/302211483e374b61dd686e1662b59a33eb5fc501/riscv_machine.c#L69
  - TinyEMU内では、物理アドレス `0x40008000` にHTIFのレジスタを配置している

```c
#define HTIF_BASE_ADDR 0x40008000
```

- https://gist.github.com/okuoku/23c2fbd2400d21949fbb5e80c686149d#file-riscv-pk-diff-L14-L33
  - TinyEMU付属のBBLでは使用されるシンボルを直接パッチしている(TinyEMU付属のパッチ)

```diff
diff --git a/bbl/bbl.lds b/bbl/bbl.lds
index 26f5816..615c3dc 100644
--- a/bbl/bbl.lds
+++ b/bbl/bbl.lds
@@ -43,15 +43,10 @@ SECTIONS
   _etext = .;
 
   /*--------------------------------------------------------------------*/
-  /* HTIF, isolated onto separate page                                  */
+  /* HTIF I/Os                                                          */
   /*--------------------------------------------------------------------*/
-  . = ALIGN(0x1000);
-  .htif :
-  {
-    PROVIDE( __htif_base = .);
-    *(.htif)
-  }
-  . = ALIGN(0x1000);
+  tohost = 0x40008000;
+  fromhost = 0x40008008;
```

このリンカスクリプトへのパッチで、 `tohost` と `fromhost` のアドレスを固定化している。

- https://github.com/riscv/riscv-pk/blob/ac2c910b18c3e36cfd85080472e78ad2fe484325/machine/htif.c#L11-L12
  - ブートローダ内での `tohost` `fromhost` の宣言

```c
volatile uint64_t tohost __attribute__((section(".htif")));
volatile uint64_t fromhost __attribute__((section(".htif")));
```

本来、これらのアドレスはエミュレータがロードしたタイミングで取得される。

- https://github.com/qemu/qemu/blob/c47edb8dda0660180f86df4defae2a1f60e345db/hw/riscv/riscv_htif.c#L45-L63
  - qemuのSpikeマシンモデルで使用されるパッチコード

そもそもHTIF自体が旧い仕様なので今となってはどうでも良いのかもしれないが、もうちょっと真面目なプロトコルが欲しいところでもある。

## HTIFを32bitで使う

HTIFは64bit巾のインターフェースであるため、本来32bitアーキテクチャでは使用できない。(書き込みが2つ以上のCPUで競合すると安全に処理できない)

TinyEMUではマルチプロセッサをサポートしていないためこの辺は特に気にしていないようで、 **レジスタを32bit巾で宣言し、上位ワードが書き込まれたタイミングで発動する** コードとすることで32bit/64bit両対応としている。

- TinyEmu: https://github.com/okuoku/patched-tinyemu-proto/blob/302211483e374b61dd686e1662b59a33eb5fc501/riscv_machine.c#L163-L166
- qemu: https://github.com/qemu/qemu/blob/c47edb8dda0660180f86df4defae2a1f60e345db/hw/riscv/riscv_htif.c#L210-L214

... qemuはマルチプロセッサをサポートしているので同じ方針ではダメな気もするが。。

# かんそう

RISC-V 32bitの環境が揃ったのが最近すぎて64bitに比べて色々遅れているというのは覚悟していたけど、HTIFのような純粋なデバッグ用I/Fが32bit非サポートというのはちょっと予想外だった。

TODO: npmで配る用に `-s SINGLE_FILE=1` して、かつ、カーネルやブートローダは外部から与えられるようにしないといけない。