FPGA開発日記

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

riscv-testsの仮想アドレスサポートのための実装を読み解く (3. rv64ui-v-simpleでの動作)

これが、rv64ui-v-simpleだとどのように使われているのかな?

rv64ui-v-simpleでは、ユーザコードに移る際に Instruction Page Falut が起きているようだ。 この時にジャンプする先はstvecに設定されている。

void vm_boot(uintptr_t test_addr)
{
/* ... 途中省略 ... */
  // set up supervisor trap handling
  write_csr(stvec, pa2kva(trap_entry));

このvm_boot()はMachine Modeで実行されるので、trap_entryは物理アドレスで表現されているので、これを仮想アドレスに変換して設定しておく。

#define pa2kva(pa) ((void*)(pa) - DRAM_BASE - MEGAPAGE_SIZE)

これは、例えばtrap_entryが物理アドレス0x8000_0144の場合、0x8000_0144 - 0x8000_0000 - 0x20_0000 = 0xffffffffffe00144 となっている。これがカーネル側から見た場合の仮想アドレスとなっている。 計算のからくりとしては、「物理アドレス - DRAMの先頭アドレス」でDRAM中のどこに存在しているのかを識別して、MEGAPAGE_SIZE (PTES_PER_PT * PGSIZEつまり1レベルで用意されるページのサイズ) で引き算することにより、ページテーブルの一番上にアクセスされるようにページが設定される。これがtrap_entryの仮想アドレスとなる。

実際にSRETの際にジャンプするのはユーザコードの存在する仮想アドレスで、これはSEPCに設定される。 test_addr - DRAM_BASEで設定されているので、例えばユーザコードの場所が 0x8000_2988 だと、0x8000_2988 - 0x8000_0000 = 0x2988としてSEPCが設定される。

void vm_boot(uintptr_t test_addr)
/* ... 途中省略 ... */
  trapframe_t tf;
  memset(&tf, 0, sizeof(tf));
  tf.epc = test_addr - DRAM_BASE;
  pop_tf(&tf);

SRETによりユーザコードにジャンプすると、まず 0x2988 を物理アドレスに変換しようとするが、存在しないので例外が発生し、trap_entryに飛ぶ。 trap_entryhandle_trapにジャンプし、例外の要因を判定してhandle_faultに飛ぶ。 handle_fault(addr)の引数は例外を引き起こした仮想アドレス 0x2988が設定される。

そして、ページの作成とページのコピーが行われる。 これは、uva2kva(addr)(ユーザモードでのaddrをカーネルモードでのaddrに変換したもの)から、addr(このアドレスはユーザモード表現)、にコピーしている。 ユーザモードでのaddrは既に上記でテーブルを作成済みなので、例外を起こすことなくコピーすることが可能という訳だ。

  uintptr_t sstatus = set_csr(sstatus, SSTATUS_SUM);
  memcpy((void*)addr, uva2kva(addr), PGSIZE);
  write_csr(sstatus, sstatus);