1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

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

image.png

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

tl;dr

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

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

TinyEMU のビルド

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

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 関数を用意することにした。

ブートローダー 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実装はアドレスが決め打ちになっており、ブートローダの方をパッチしてアドレスを伝達している。

#define HTIF_BASE_ADDR 0x40008000
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;

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

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

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

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

HTIFを32bitで使う

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

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

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

かんそう

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?