0%

riscv-hyp-test 测试框架

test_page

测试 2-stage translation 页表的 PTE的集合
vs 为 vs-stage translation 的pte
h 为 g-stage translation 的pte

1
2
3
4
5
6
7
struct {
uint64_t vs;
uint64_t h;
} test_page_perm_table [] = {
# index ----------- vs ---------------------- h ------#
[VSRWX_GRWX]      =   {PTE_V | PTE_RWX,         PTE_V | PTE_RWX},
}

页表属性按照PTE的 7:0 位设置相关的bit位

image-20211126175029553

hspt_init

测试框架执行 hspt_init 对2-stage 页表进行初始化
如 tinst_tests 测试项, 该测试集对指令进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-+ tinst_tests
\ -+ hspt_init
\ - addr = 0x00000000;
| -+ for(int i = 0; i < 4; i++)
\ - hspt[0][i] = PTE_V | PTE_AD | PTE_RWX | (addr >> 2); "设置第一级页表 pgd"
| - addr += PGSIZE << (2 * 9) "设置完后, hspt[0][0] -> hspt[0][3] 为 0 0x4000000 0x80000000 0xc0000000 base 地址"
| - hspt[0][4] = PTE_V | (((uintptr_t)&hspt[1][0]) >> 2); "设置PPN 二级页表"
| - hspt[1][0] = PTE_V | (((uintptr_t)&hspt[2][0]) >> 2); "设置 PPN 三级页表"
| - addr = TEST_PPAGE_BASE; "0x88000000"
| -+ for(int i = 0; i < TEST_PAGE_MAX; i++) "TEST_PAGE_MAX = 512, 一共设置512个PTE"
\ -+ hspt[2][i] = (addr >> 2) | PTE_AD | test_page_perm_table[i].vs;
"设置PTE 物理地址为 0x88000000 - 0x881ff000 / 间隔 1page"
"每个PTE 对应的测试集的权限不同, 取自 test_page_perm_table.vs 权限集"
| - addr += PAGE_SIZE;
| - CSRW(satp, satp); "M-mode 或 Hs-mode 下设置satp 为 hspt 基地址即 hspt[0][0]的基址"
| - goto_priv(PRIV_HS); "进入hs-mode"
| - uintptr_t vaddr_f = hs_page_base(VSI_GI); "VSI_GI=100, vs_page_base 0x100000000 + 100* PGSIZE = 0x100064000"
"访问不了, 应该报错"
| - TEST_SETUP_EXCEPT(); "初始化 except 数据 为 0"
| - value = lb(vaddr_f); "lb load vaddr_f 的数据"
| - TEST_ASSERT(excpt.cause == CAUSE_LPF) "测试应该陷入 M-mode handler, 且mcause 应为 13 load page fault"
" 如果未触发异常, 或mcause 不对, case 报错"

大概回顾下页表结构
可见这个是针对sv39 的页表
回顾下 sv39 页表的查表方法

sv39 in RV64:

当在 satp 寄存器中启用了分页时,S 模式和 U 模式中的虚拟地址会以从根部遍历页表的方式转换为物理地址。

  • satp.PPN 给出了一级页表的基址, VA[38:30]给出了一级页号, 因此处理器会读取位于地址(satp.PPN × 4096 + VA[38:30] × 8)的页目录项
  • 该 PTE 包含二级页表的基址, VA[29:21]给出了二级页号, 因此处理器读取位于地址(PTE.PPN × 4096 + VA[29:21] × 8)的页目录项
  • 该 PTE 包含了三级页表的基址, VA[20:12] 给出了三级页号, 处理器读取位于地址(PTE.PPN × 4096 + VA[20:12] × 8)的页目录项
  • 该页表项的 PTE. PPN 就是物理地址对应的 PPN * 4096 + offset 得到物理地址

PTE 的 [0-9] 位为权限位, [10-53] 的 44 位为 PPN.

sv39 上. 虚拟地址范围 39 位, 0-38 位 , 物理地址范围 56位, 0-55 位

#页表

hpt_init

此项为设置G-stage translation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void hpt_init(){

for(int i = 0; i < 2048; i++){
hpt_root[i] = 0;
}

uintptr_t addr = 0x0;
for(int i = 0; i < 4; i++){
// 设置 hpt_root[0/1/2/3] 大页 addr 为 0x0 0x4000_0000 0x8000_0000 0x10000_0000
hpt_root[i] =
PTE_V | PTE_U | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(0);
}
// 覆盖 hpt_root[2], 该页变为 页表项 指向 hpt[0][0]
hpt_root[MEM_BASE/SUPERPAGE_SIZE(0)] =
PTE_V | (((uintptr_t)&hpt[0][0]) >> 2);

addr = MEM_BASE; // (0x8000_0000)
for(int i = 0; i < 512; i++) hpt[0][i] = 0;
for(int i = 0; i < MEM_SIZE/SUPERPAGE_SIZE(1)/2; i++){ // i < 64
hpt[0][i] =
PTE_V | PTE_U | PTE_AD | PTE_RWX | (addr >> 2); // 设置 2M的PTE 大页
addr += SUPERPAGE_SIZE(1); //0x20_0000 //2M
}
// 覆盖 hpt_root[4] 其变为页表项 2级页表, 指向 hpt[1][0] 3级页表
hpt_root[4] =
PTE_V | (((uintptr_t)&hpt[1][0]) >> 2);

hpt_root[2047] =
PTE_V | (((uintptr_t)&hpt[1][0]) >> 2);

// hpt[1][0] 的3级页表 指向 hpt[2][0] 的pte
hpt[1][0] =
PTE_V | (((uintptr_t)&hpt[2][0]) >> 2);

hpt[1][511] =
PTE_V | (((uintptr_t)&hpt[2][0]) >> 2);

addr = TEST_PPAGE_BASE; //0x8800_0000 开始的物理地址
// 设置hpt[2][0] 到 hpt[2][511] 的PTE, 映射的物理地址从0x8800_0000 开始
for(int i = 0; i < TEST_PAGE_MAX; i++){
hpt[2][i] = (addr >> 2) | PTE_AD |
test_page_perm_table[i].h;
addr += PAGE_SIZE;
}

// hpt[1][1] 的3级页表 指向 hpt[3][0] 的pte
hpt[1][1] =
PTE_V | (((uintptr_t)&hpt[3][0]) >> 2);
addr = TEST_PPAGE_BASE; //0x8800_0000 开始的物理地址
// 设置hpt[3][0] 到 hpt[3][511] 的PTE, 映射的物理地址从0x8800_0000 开始
for(int i = 0; i < 512; i++){
hpt[3][i] = (addr >> 2) |
PTE_V | PTE_U | PTE_AD | PTE_RWX;
addr += PAGE_SIZE;
}

// hpt_root[5] 为2级页表项, 指向 hpt[4][0]
hpt_root[5] =
PTE_V | (((uintptr_t)&hpt[4][0]) >> 2);
addr = TEST_PPAGE_BASE; // 0x8800_0000
// hpt[4][0] - hpt[4][511] 为 大页 PTE, 每个PTE 覆盖 2M 范围
for(int i = 0; i < 512; i++){
hpt[4][i] = (addr >> 2) |
PTE_V | PTE_U | PTE_AD | PTE_RWX;
addr += SUPERPAGE_SIZE(1); // +2M
}

if(curr_priv == PRIV_HS || curr_priv == PRIV_M){
uintptr_t hsatp = (((uintptr_t)hpt_root) >> 12) | (0x8ULL << 60);
CSRW(CSR_HGATP, hsatp);
} else {
ERROR("trying to set hs hgatp from lower privilege");
}
}

再来看下 vs-stage的页表创建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void vspt_init(){

uintptr_t addr;

addr = 0x00000000;
for(int i = 0; i < 4; i++){
vspt[0][i] =
PTE_V | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(0);
}

vspt[0][MEM_BASE/SUPERPAGE_SIZE(0)] = //vspt[0][2] 2级 -> vspt[1][0] 3级
PTE_V | (((uintptr_t)&vspt[1][0]) >> 2);

addr = MEM_BASE;
for(int i = 0; i < 512; i++) vspt[1][i] = 0;
// vspt[1][0] - vspt[1][63] 建立大页, 覆盖地址从 0x8000_0000 到 0x8000_0000 + 2M * 64 地址范围
for(int i = 0; i < MEM_SIZE/SUPERPAGE_SIZE(1)/2; i++){
vspt[1][i] =
PTE_V | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(1);
}

// vspt[0][4] 2级 -> vspt[2][0] 3级
vspt[0][4] =
PTE_V | (((uintptr_t)&vspt[2][0]) >> 2);

// vspt[0][5] =
// PTE_V | PTE_U | PTE_AD | (((uintptr_t)&vspt[2][0]) >> 2);

// vspt[2][0] 3级页表 -> vspt[3][0] PTE
vspt[2][0] =
PTE_V | (((uintptr_t)&vspt[3][0]) >> 2);

addr = TEST_VPAGE_BASE; // 0x10000_0000 PTE
// vspt[3][0] -> vspt[3][511] PTE GPA 地址范围 0x10000_0000 -- 0x10000_0000 + 2M
for(int i = 0; i < TEST_PAGE_MAX; i++){
vspt[3][i] = (addr >> 2) | PTE_AD |
test_page_perm_table[i].vs;
addr += PAGE_SIZE;
}
// vspt[2][1] 3级 -> vspt[4][0]
vspt[2][1] =
PTE_V | (((uintptr_t)&vspt[4][0]) >> 2);

addr = 4 * SUPERPAGE_SIZE(0) + SUPERPAGE_SIZE(1); // 0x10000_0000 + 2M
// vspt[4][0] - vspt[4][511] PTE 地址范围 0x10000_0000 + 2M -- 0x10000_0000 + 4M
for(int i = 0; i < 512; i++){
vspt[4][i] = (addr >> 2) |
PTE_V | PTE_AD | PTE_RWX;
addr += PAGE_SIZE;
}

// vspt[0][5] 二级页表 -> vspt[5][0] 三级页表
vspt[0][5] =
PTE_V | (((uintptr_t)&vspt[5][0]) >> 2);

// vspt[5][0] - vspt[5][511] 为大页 PTE , 覆盖的地址范围为 0x14000_0000 - 0x14000_0000+ 2M*512
addr = 5 * SUPERPAGE_SIZE(0); // 0x14000_0000 5G
for(int i = 0; i < 512; i++){
vspt[5][i] = (addr >> 2) |
PTE_V | PTE_AD | PTE_RWX;
addr += SUPERPAGE_SIZE(1);
}

uintptr_t satp = (((uintptr_t)vspt) >> 12) | (0x8ULL << 60);
if(curr_priv == PRIV_VS){
CSRW(satp, satp);
} else if(curr_priv == PRIV_HS || curr_priv == PRIV_M){
CSRW(CSR_VSATP, satp);
} else {
ERROR("");
}
}
1
2
3
4
uintptr_t addr1 = phys_page_base(SWITCH1); // 0x8800_0000 + 108 * 4k
uintptr_t addr2 = phys_page_base(SWITCH2); // 0x8800_0000 + 109 * 4k
uintptr_t vaddr1 = vs_page_base(SWITCH1); // 0x10000_0000 + 108 * 4k
uintptr_t vaddr2 = vs_page_base(SWITCH2); // 0x10000_0000 + 109 * 4k

先看 vs-stage 的翻译, 0x10000_0000 + 108 * 4k
首先 satp = vsatp. PPN = vspt >> 12, 对应sv39模式,

  • va[38:30] (9位) 给出了一级页号偏移为 4, 在一级页表的物理页中找到二级页表的物理页号 vspt[0][4]

  • va[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 vspt[2][0]

  • va[20:12] (9位) 给出了三级页号偏移为 108, 在三级页表的物理页中找到要访问位置的物理页号 vspt[3][108]

  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址

    vspt[3][108].ppn << 12 + 0 (offset) = 0x10000_0000 + 108*4k + 0

所以 vs-stage 0x10000_0000 - 0x10000_0000 + 4M 的范围是直接映射的.

再来看G-stage的翻译过程, GPA为 0x10000_0000 + 108 * 4k
对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 4, 在一级页表的物理页中找到二级页表的物理页号 hpt_root[4]

  • GPA[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 hpt[1][0]

  • GPA[20:12] (9位) 给出了三级页号偏移为 108, 在三级页表的物理页中找到要访问位置的物理页号 hpt[2][108]

  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址 , 对应的权限是 test_page_perm_table[108].h=PTE_U | PTE_RWX

    hpt[2][108].ppn << 12 + 0 (offset) = 0x8800_0000 + 108*4k + 0

翻译后, addr1 与 vaddr1 正好是对应的 HPA 与 GVA的映射关系

再来看下 second_stage_only_translation 的测试项:

1
vs_page_base_limit(TOP) = 0x1fffffff000  "即 0x20000_0000 前的最后一个 2M "

由于vstap 写了 0, guest os 未启用分页, 所以guest os 直接访问的就是GPA
对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 2047 (11个1), 在一级页表的物理页中找到二级页表的物理页号 hpt_root[2047]

  • GPA[29:21] (9位) 给出了二级页号偏移为 511 (9个1), 在二级页表的物理页中找到三级页表的物理页号 hpt[1][511]

  • GPA[20:12] (9位) 给出了三级页号偏移为 511 (9个1), 在三级页表的物理页中找到要访问位置的物理页号 hpt[2][511]

  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址 , 对应的权限是 test_page_perm_table[511].h=PTE_U | PTE_RWX

    hpt[2][511].ppn << 12 + 0 (offset) = 0x8800_0000 + 511*4k + 0

最后看下VS-mode下可以切换页表, 而页表都是落在原始地址范围 hpt 0x80020000 hspt 0x80032000 vspt 0x8002c000
先看下 vs-stage的转换 0x80020000

首先 satp = vsatp. PPN = vspt >> 12, 对应sv39模式,

  • va[38:30] (9位) 给出了一级页号偏移为 2, 在一级页表的物理页中找到二级页表的物理页号 vspt[0][2]

  • va[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 vspt[1][0], 此处为大页, 所以页目录walk 终止.

  • va[20:12] (9位) 和 va[11:10] 最终给出了页内偏移为 0x20000, 最终的GPA 地址为

    vspt[1][0].ppn << 12 << 9 + 0x20000 = 0x8000_0000 + 0x20000

同理GVA-GPA的映射关系为 0x80032000 - 0x80032000 0x8002c000 - 0x8002c000
所以 0x8000_0000 - 0x8000_0000+64*0x200000 (0x8800_0000) 范围是线性映射的, GVA等同于GPA

再来看G-stage的转换 0x80020000

对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 2, 在一级页表的物理页中找到二级页表的物理页号 hpt_root[2]

  • GPA[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 hpt[0][0], 此处为大页, 所以页目录walk 终止.

  • GPA[20:12] (9位) 和 GPA[11:10] 最终给出了页内偏移为 0x20000, 最终的HPA 地址为

    hpt[0][0].ppn << 12 << 9 + 0x20000 = 0x8000_0000 + 0x20000

同理GPA-HPA的映射关系为 0x80032000 - 0x80032000 0x8002c000 - 0x8002c000
所以 0x8000_0000 - 0x8000_0000+64*0x200000 (0x8800_0000) 范围是线性映射的, GPA等同于HPA

最终 VS-mode下可以切换 hs vs g-stage 的页表项, 页表项所处的地址范围 GVA 和 HPA 是线性映射的, GVA等同于 HPA.

调研 qemu 和 spike 的代码, G-stage 的页表项必须带有 PTE_U 的 flag, G-stage 才能不导致 load/write exception.