最近 RISCV のエミュレータを書いているのだが、その時に riscv-tests (https://github.com/riscv/riscv-tests) メモリマップを調べたのでメモ。
手元で使っている riscv-tests は数か月以上前にチェックアウトしたものなので、今は変わっているかもしれない。
物理アドレス空間
アドレスは RV32 のもので、RV64 では符号拡張したアドレスが使われる。
アドレス | サイズ | 名称 | 備考 |
---|---|---|---|
0x80000000 | 0x80000000 | RAM | |
0x80001000 | 0x4 | tohost | riscv-tests の一部では、アドレスの下位 16 bit が 0x2000 / 0x3000 になる |
カーネル論理アドレス空間
VM 有効のテストにおける、カーネルアドレス空間。
RV32
アドレス | サイズ | 内容 | 備考 |
---|---|---|---|
0xffc00000 | 0x400000 | RAM | |
0xffc01000 | 0x4 | tohost | 一部のテストでは、アドレスの下位 16 bit が 0x2000 / 0x3000 になる |
RV64
アドレス | サイズ | 内容 | 備考 |
---|---|---|---|
0xffffffff_ffe00000 | 0x200000 | RAM | |
0xffffffff_ffe01000 | 0x4? | tohost | 一部のテストでは、アドレスの下位 16 bit が 0x2000 / 0x3000 になる |
ユーザー論理アドレス空間
VM 有効のテストにおける、ユーザー論理アドレス空間。
RV32
アドレス | サイズ | 内容 | 備考 |
---|---|---|---|
0x00000000 | 0x400000 | RAM | |
0x00001000 | 0x4 | tohost | riscv-tests の一部では、アドレスの下位 16 bit が 0x2000 / 0x3000 になる |
RV64
アドレス | サイズ | 内容 | 備考 |
---|---|---|---|
0x00000000_00000000 | 0x200000 | RAM | |
0x00000000_00001000 | 0x4? | tohost | riscv-tests の一部では、アドレスの下位 16 bit が 0x2000 / 0x3000 になる |
ソースコード
アドレス変換は以下のマクロで行われる。uva2kva の引数が pa でややこしいけど、たぶん引数は uva が正しいのだと解釈。
ソースコードはいつぞやの https://github.com/riscv/riscv-tests からの引用だが、コメントは自分で入れている。
// vm.c
#define pa2kva(pa) ((void*)(pa) - DRAM_BASE - MEGAPAGE_SIZE)
#define uva2kva(pa) ((void*)(pa) - MEGAPAGE_SIZE) // i.e. uva = kva + MEGAPAGE_SIZE
DRAM_BASE, MEGAPAGE_SIZE の値を当てはめると以下のようになる。
// encoding.h
// RV32
#define pa2kva(pa) ((void*)(pa) - 0x80400000)
#define uva2kva(pa) ((void*)(pa) - 0x400000)
// RV64
#define pa2kva(pa) ((void*)(pa) - 0x80200000)
#define uva2kva(pa) ((void*)(pa) - 0x200000)
DRAM_BASE, MEGAPAGE_SIZE の定義は以下の通り。
// riscv_test.h
#define DRAM_BASE 0x80000000
// riscv_test.h
#if __riscv_xlen == 64
# define MSTATUS_SD MSTATUS64_SD
# define SSTATUS_SD SSTATUS64_SD
# define RISCV_PGLEVEL_BITS 9
# define SATP_MODE SATP64_MODE
#else
# define MSTATUS_SD MSTATUS32_SD
# define SSTATUS_SD SSTATUS32_SD
# define RISCV_PGLEVEL_BITS 10
# define SATP_MODE SATP32_MODE
#endif
// riscv_test.h
#define PGSHIFT 12
#define PGSIZE (1UL << PGSHIFT) // 0x1000
// riscv_test.h
#define PTES_PER_PT (1UL << RISCV_PGLEVEL_BITS) // RV32: 0x400, RV64:0x200
#define MEGAPAGE_SIZE (PTES_PER_PT * PGSIZE) // RV32: 0x400000 (4MB), RV64: 0x200000 (2MB)
ページテーブル
ページテーブルは以下のように定義されている。
#define l1pt pt[0]
#define user_l2pt pt[1]
#if __riscv_xlen == 64
# define NPT 4
#define kernel_l2pt pt[2]
# define user_l3pt pt[3]
#else
# define NPT 2
# define user_l3pt user_l2pt
#endif
pte_t pt[NPT][PTES_PER_PT] __attribute__((aligned(PGSIZE)));
// PTES_PER_PT is (RV32: 0x400, RV64:0x200)
// NPT is (RV32: 2, RV64:4)
// sizeof(pte_t) is (RV32: 4, RV64: 8)
// sizeof(pt[0]) is (RV32: 0x1000, RV64: 0x1000)
// sizeof(pt) is (RV32: 0x2000, RV64: 0x4000)
ページテーブルは2次元配列 pt であるが、これはアドレスがコードで指定されているわけではない(リンカスクリプトでも指定されていないようだ)。
リンカが決定するのだと思われる。
参考までに、先日 rv64ui-v-add を動かしたときは以下のようになっていた。
物理アドレス | |
---|---|
pt | 0x80004000 |
l1pt | 0x80004000 |
user_l2pt | 0x80005000 |
kernel_l2pt | 0x80006000 |
user_l3pt | 0x80007000 |