FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-V ハイパーバイザー拡張の勉強 (ハイパーバイザー命令)

5.3 ハイパーバイザー命令

ハイパーバイザー拡張では、仮想マシンのロード・ストア命令と2つの特権付きfence命令が追加される。

5.3.1 ハイパーバイザー仮想マシンロードストア命令

ハイパーバイザー仮想マシンロードストア命令

ハイパーバイザー仮想マシンロードストア命令は、M-Mode, HS-Modeもしくはhstatus.HU=1の場合にU-Modeでのみ有効である。それぞれの命令はV=1であるかのように動作する; つまり、アドレス変換・保護・エンディアンなどがまるでVS-ModeもしくはVU-Modeであるかのように動作する。hstatusSPVPフィールドはアクセス特権レベルを制御する。明示的なメモリアクセスは、SPVP=0の場合にはVU-Modeとして完了し、SPVP=1の時はVS-Modeのように完了する。通常V=1の場合には2ステージのアドレス変換が適用され、HSレベルのsstatus.SUMビットは無視される。HSレベルのsstatus.MXRはアドレス変換の両方のステージ(VSステージとGステージ)に対して実行可能なページのみを読み込み可能とし、一方でvstatus.MXRは最初の変換(VSステージ)にのみ影響を与える。

すべてのRV32IとRV64Iのロード命令、LB, LBU, LH, LHU, LW, LWU, LD命令はそれぞれ以下の仮想マシン命令に相当する: HLV.B, HLV.BU, HLV.H, HLV.HU, HLV.W, HLV.WU, HLV.D。すべてのRV32IとRV64Iのストア命令、SB, SH, SW, SD命令はそれぞれ以下の仮想マシン命令に相当する: HSV.B, HSV.H, HSV.W, HSV.D。もちろん、HLV.WU, HVL.D, HSV.DはRV32では無効である。

HLVX.HUとHLVX.WUはHLVU.HUとHV.WUと同一であるが、アドレス変換中に実行権限が読み込み権限の代わりをすることができる。つまり、アドレス変換中に読みだされるページは実行可能でなければならないが、読み出し可能である必要はないということである。HLVX.WUはRV32で有効であるが、LWUおよびHLV.WUは有効ではないことに注意すること(RV32では、HLVX。WUはHLV.Wの一種であると考えられ、32ビットの値に対して符号拡張は行われない)。

HLVXはマシンレベルの物理メモリ保護(PMP)をオーバーライドすることはできず、PMPにより実行可能のみのメモリ領域に対してはアクセスフォルト例外が発生する。

V=1の時に仮想マシンロード・ストア命令(HLV, HLVX, HSV)を実行しようとすると仮想命令例外が発生する。hstatus.HU=0時のU-Modeで同様の命令を実行しようとすると不正命令例外が発生する。


メモ:SpikeのHLV.Bの実装を確認すると、以下のようになっていた。

  • HLV.B
require_extension('H');
require_novirt();
require_privilege(get_field(STATE.hstatus, HSTATUS_HU) ? PRV_U : PRV_S);
WRITE_RD(MMU.guest_load_int8(RS1));

MMU.guest_load_int8というのが使用されている。mmu.hには以下のように#defineで定義がなされていた。

  // load value from guest memory at aligned address; zero extend to register width
  load_func(uint8, guest_load, RISCV_XLATE_VIRT)
  load_func(uint16, guest_load, RISCV_XLATE_VIRT)
  load_func(uint32, guest_load, RISCV_XLATE_VIRT)
  load_func(uint64, guest_load, RISCV_XLATE_VIRT)
  load_func(uint16, guest_load_x, RISCV_XLATE_VIRT|RISCV_XLATE_VIRT_MXR)
  load_func(uint32, guest_load_x, RISCV_XLATE_VIRT|RISCV_XLATE_VIRT_MXR)

load_func()の実体はdefineであるが、そこではRISCV_XLATE_VIRTで宣言されているフラグでテーブルウォークの制御がなされているようだった。

  #define load_func(type, prefix, xlate_flags) \
    inline type##_t prefix##_##type(reg_t addr, bool require_alignment = false) { \
      if (xlate_flags) \
        flush_tlb(); \
      if (unlikely(addr & (sizeof(type##_t)-1))) { \
 ...
      target_endian<type##_t> res; \
      load_slow_path(addr, sizeof(type##_t), (uint8_t*)&res, (xlate_flags)); \
      if (proc) READ_MEM(addr, size); \

load_slow_path()は以下のような実装になっていた。

void mmu_t::load_slow_path(reg_t addr, reg_t len, uint8_t* bytes, uint32_t xlate_flags)
{
  reg_t paddr = translate(addr, len, LOAD, xlate_flags);

translate()xlate_flagsで挙動を変えるようだ。

reg_t mmu_t::translate(reg_t addr, reg_t len, access_type type, uint32_t xlate_flags)
{
  if (!proc)
    return addr;

  bool mxr = get_field(proc->state.mstatus, MSTATUS_MXR);
  bool virt = (proc) ? proc->state.v : false;
  reg_t mode = proc->state.prv;
  if (type != FETCH) {
    if (!proc->state.debug_mode && get_field(proc->state.mstatus, MSTATUS_MPRV)) {
      mode = get_field(proc->state.mstatus, MSTATUS_MPP);
      if (get_field(proc->state.mstatus, MSTATUS_MPV))
        virt = true;
    }
    if (!proc->state.debug_mode && (xlate_flags & RISCV_XLATE_VIRT)) {
      virt = true;
      mode = get_field(proc->state.hstatus, HSTATUS_SPVP);
      if (type == LOAD && (xlate_flags & RISCV_XLATE_VIRT_MXR)) {
        mxr = true;
      }
    }
  }

  reg_t paddr = walk(addr, type, mode, virt, mxr) | (addr & (PGSIZE-1));
  if (!pmp_ok(paddr, len, type, mode))
    throw_access_exception(virt, addr, type);
  return paddr;
}