tlb异常处理

tlb异常相关的简要概述

该内核源码为linux5.14中的32位loongarch架构下tlb异常相关部分。下面为相关部分的关键源代码和函数。

mm/tlbex-32.S

handle_tlb_load

handle_tlb_store

handle_tlb_modify

heandle_tlb_refill

kernel/traps.c

ebase

eentry+0x4000

LOONGARCH_CSR_EENTRY 例外入口地址

0x200 * 32 = 0x4000

tlbrentr

vec_size = 0x200

1
2
3
4
5
6
7
8
9
10
do_raise_exception_err: 14 0
mips_cpu_do_interrupt enter: PC 0018c16c EPC 0018c024 RFEPC 0x000000000018c024 TLB refill exception
mips_cpu_do_interrupt: PC a0201000 EPC 0x0018c16c cause 63(refill)
EXST 003f0000 EXCFG 0x00071c1c BADVA 0x002134d4 ENV_PGDL 0xa23e1000 BADI 0x00000000 SYS_NUM 2381672 cpu 0 asid 0x1
exception_return: EPC 0x18c16c
do_raise_exception_err: 29 0
mips_cpu_do_interrupt enter: PC 0018c16c EPC 0018c16c RFEPC 0x000000000018c16c TLB load exception
mips_cpu_do_interrupt: PC a0214000 EPC 0x0018c16c cause 1
EXST 00010000 EXCFG 0x00071c1c BADVA 0x002134d4 ENV_PGDL 0xa23e1000 BADI 0x00000000 SYS_NUM 2381672 cpu 0 asid 0x1
do_raise_exception_err: 65538 0

tlb_load *(0xa0210000 + 0x200)

gdb调试:

1
2
b *0xa0210200
x /32i 0xa0210200

trap_init

1
2
3
4
5
6
7
void __init trap_init(void)
{
set_handler(EXCCODE_GENERIC * vec_size , except_vec_vi_handler, vec_size);
...
set_handler(EXCCODE_TLBL * vec_size, handle_tlb_load, vec_size);
...
}

kerne/geneX.S

except_vec_vi_handler

loongarch32/irq.c

plat_irq_dispatch 中断

0x800 时钟中断

arch/loongarch/mm/tlb.c

__update_tlb

arch/loongarch/mm/fault.c

do_page_fault


mm/tlbex-32.S中handle_tlb_load函数

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
SYM_FUNC_START(handle_tlb_load)
/* 保存现场 */
csrwr ra, EXCEPTION_KS2

lu12i.w ra, 0xc
slli.w ra, ra, 0x10
csrrd t0, LOONGARCH_CSR_BADV
bgeu t0, ra, vmalloc_load
csrrd t1, LOONGARCH_CSR_PGD

vmalloc_done_load:
/* Get PGD offset in bytes */
srli.w t0, t0, 0x16
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2 /* 页内偏移为12位,故t0为PGD OFFSET(也为12bit) */
add.w t1, t1, t0 /* PGD基址 + PGD_OFFSET = 该PTE基址 */
ld.w t1, t1, 0 /* 从t1内存中取出数据(页目录号)放到t1中 */

csrrd t0, LOONGARCH_CSR_BADV
srli.w t0, t0, 0xc
andi t0, t0, 0x3ff /* 获取虚拟地址中间的PTE10位 */
slli.w t0, t0, 0x2 /* 获得PTE_OFFSET */
add.w t0, t0, t1 /* 该PTE基址 + PTE_OFFSET = 包含物理页号的物理地址*/

ld.w t1, t0, 0 /* 由物理页号的物理地址取出Page table bits */
tlbsrch /* 使用ASID和TLBEHI查询TLB */
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
/* 判断Page table bits的_PAGE_PRESENT是否为0 */
andi ra, t1, _PAGE_PRESENT
beq ra, zero, nopage_tlb_load

ori t1, t1, _PAGE_VALID /* 将PTEE中_PAGE_VALID置1 */
st.w t1, t0, 0 /* 更新page table bits */

/* tlb一次取两页,t0的第3位为0时,0表示偶数物理页,1表示基数物理页 */
ori t0, t0, 0x4
xori t0, t0, 0x4

/* 只要PTEE的7:0位(flag位)和31:11位(PPN),
* 并且将31:11位(PPN)移到31:8位上(高4位清0),存入TLBELO0(1)寄存器。(见TLB表项寄存器)
*/
ld.w t1, t0, 0 /* 将t0内存的数据(PTE页表项)存到t1 */
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi ra, ra, 0xff
or t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO0

ld.w t0, t0, 0x4
srli.w ra, t0, 0xc
slli.w ra, ra, 0x8
andi t0, t0, 0xff
and.w t0, t0, ra
csrwr t0, LOONGARCH_CSR_TLBELO1

tlbwr /* 将TLBELO0/1写入TLB表项 */
1
2
3
4
5
leave_load:
csrrd t0, EXCEPTION_KS0 /* 恢复现场 */
csrrd t1, EXCEPTION_KS1
csrrd ra, EXCEPTION_KS2
ertn
1
2
3
4
5
6
7
8
9
10
11
vmalloc_load:
la.abs t1, swapper_pg_dir
b vmalloc_done_load

nopage_tlb_load:
dbar 0
csrrd ra, EXCEPTION_KS2
la.abs t0, tlb_do_page_fault_0
jirl $r0, t0, 0

SYM_FUNC_END(handle_tlb_load)

传入的BADV为0x0022614c。其中PGD为0xa24b7000,*0xa24b7000 = 0xa24bc000。
PTE基址 = PGD基址 + PGD_OFFSET = 0xa24bc000 + (0x000(10bits)<<2) = 0xa24bc000
该PTE基址 + PTE_OFFSET = 0xa24bc000 + (0x226(10bits)<<2) = 0xa24bc898 = 包含物理页号、页状态位的物理地址
将(*0xa24bc898)0xff3a09c的page_valid位置1,即改为0xff3a09d,其中0xff3a(高20bits为物理页号)
tlb取0xa24bc898、0xa24bc89c两页表项按规则填入TLBLO0和TLBLO1中
填入规则:31:12(PPN)→8:27(PPN),7:0(page状态位)→7:0(page状态位),最高4位清0

handle_tlb_load执行过程

进入tlb_load过程时,首先保存现场(0x1213b8),即执行csrwr,将r1的地址0x1213b8存到了0x32地址的内存处。

1
2
SYM_FUNC_START(handle_tlb_load)
csrwr ra, EXCEPTION_KS2

csrrd传入出错虚拟地址(BADV)0x2134d4到r12寄存器,0x2134d4比0xc0000000小,故继续顺序执行。

1
2
3
4
lu12i.w ra, 0xc
slli.w ra, ra, 0x10
csrrd t0, LOONGARCH_CSR_BADV
bgeu t0, ra, vmalloc_load

将PGD基址0xa23e1000传入r13寄存器,其内存的值为0xa23de000

1
csrrd   t1, LOONGARCH_CSR_PGD

先获取t0(BADV)0x002134d4的前10bits再偏移2位,得到PGD_OFFSET(0x000),加上PGD(0xa23e1000),存到r13寄存器,为0xa23e1000。

1
2
3
4
5
6
vmalloc_done_load:
/* Get PGD offset in bytes */
srli.w t0, t0, 0x16
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t1, t1, t0

接着获取t0(0x002134d4)的中间10bits再偏移2位,得到PTE_OFFSET(213<<2 = 0x84c),最后加上t1寄存器内存处的值0xa23de000,得到地址0xa23de84c存到t0寄存器。

1
2
3
4
5
6
csrrd   t0, LOONGARCH_CSR_BADV
ld.w t1, t1, 0
srli.w t0, t0, 0xc
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t0, t0, t1

把r12内存处的值(*0xa23de84c= 0)写入r13寄存器,再刷新tlb。

1
2
3
label_smp_pgtable_load:
ld.w t1, t0, 0
tlbsrch

因为_PAGE_PRESENT为0(页不存在),所以跳转到nopage_tlb_load处,即PC跳转到0xa02102b0处执行。

1
2
andi    ra, t1, _PAGE_PRESENT
beq ra, zero, nopage_tlb_load

接着恢复现场(r1=0x1213b8),跳转到tlb_do_page_fault函数处执行。

1
2
3
4
5
nopage_tlb_load:
dbar 0
csrrd ra, EXCEPTION_KS2
la.abs t0, tlb_do_page_fault_0
jirl $r0, t0, 0

第二次tlb_load:

继续(gdb) c之后,可以看到第二次触发tlb_load的BADV也为0x2134d4。

继续(gdb) si调试到PTE+PTE_OFFSET可以得到r12为0x23de84c,此时该内存地址中有值,为0xff2309c,存到r13寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vmalloc_done_load:
/* Get PGD offset in bytes */
srli.w t0, t0, 0x16
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t1, t1, t0
csrrd t0, LOONGARCH_CSR_BADV
ld.w t1, t1, 0
srli.w t0, t0, 0xc
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t0, t0, t1

label_smp_pgtable_load:
ld.w t1, t0, 0
tlbsrch

0xff2309c的_PAGE_PRESENT(第8位,判断该页是否存在)为1,并将_PAGE_VALID(第1位,判断该页是否有效)置1,即0xff2309d,将0xff2309d写回r12寄存器的内存地址处(*0xa23de84c=0xff2309d)。注意:该_PAGE_PRESENT、_PAGE_VALID都为页表的软件位,tlb只有硬件位。

1
2
3
4
5
andi    ra, t1, _PAGE_PRESENT
beq ra, zero, nopage_tlb_load

ori t1, t1, _PAGE_VALID
st.w t1, t0, 0

由于tlb一次存取两页,其中第3位表示奇偶页。故将0xa23de848和0xa23de84c内存地址处的值(包含PPN、页表标志位的地址)按规则存入TLBLO0和TLBLO1中。由于TLBLO0寄存器的31:8位表示PPN,而页表31:12位表示PPN,故需要将该页表*0xa23de848的高20位PPN写入到TLBLO0寄存器的31:8位(其中高4位清0),低8位标志位写入到TLBLO0寄存器的7:0位。所以TLBO0 = ( t1 >>12 << 8) | (t1 & 0xff)。所以通过csrwr写入寄存器,TLBLO0寄存器按0xff229c写入 。TLBLO1寄存器按0xff239d写入,将旧值写回t0,t1。由于TLBLO0寄存器实际第8位只读恒为0,故第8位写忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ori     t0, t0, 0x4
xori t0, t0, 0x4

ld.w t1, t0, 0
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi t1, t1, 0xff
add.w t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO0

ld.w t0, t0, 0x4
srli.w ra, t0, 0xc
slli.w ra, ra, 0x8
andi t0, t0, 0xff
add.w t0, t0, ra
csrwr t0, LOONGARCH_CSR_TLBELO1

csrwr t0, LOONGARCH_CSR_TLBELO1

接着恢复现场,从例外处理现场返回。

1
2
3
4
5
6
tlbwr

csrrd t0, LOONGARCH_CSR_KS0
csrrd t1, LOONGARCH_CSR_KS1
csrrd ra, EXCEPTION_KS2
ertn

总结

BADV: 0x002134d4

*0xa23de848 = 0xff2209c, TLBLO0 = ff229c(实际为ff221c,第8位恒为0,其中第1位页有效位为0);

*0xa23de84c = 0xff2309d, TLBLO0 = ff239d(实际为ff231d,第8位恒为0,其中第1位页有效位为1);

1
2
3
4
do_raise_exception_err: 29 0
mips_cpu_do_interrupt enter: PC 0018c16c EPC 0018c16c RFEPC 0x000000000018c16c TLB load exception
mips_cpu_do_interrupt: PC a0214000 EPC 0x0018c16c cause 1
EXST 00010000 EXCFG 0x00071c1c BADVA 0x002134d4 ENV_PGDL 0xa23e1000 BADI 0x00000000 SYS_NUM 2381672 cpu 0 asid 0x1

参考

四种TLB异常

TLB 异常总共有 4 种:TLB/XTLB 重填异常(TLB Refill Exception,意味着 TLB 中没有对应项),TLB 加载无效异常(TLB Load Invalid Exception,意味着读请求、TLB 中有对应项、但对应项无效),TLB 存储无效异常(TLB Store Invalid Exception,意味着写请求、TLB 中有对应项、但对应项无效),TLB 修改异常(TLB Modify Exception,意味着写请求,TLB 中有对应项、也有效、但对应项只读)。TLB/XTLB 重填异常有专门的入口向量(向量 0 和向量 1),而其他几种异常使用通用入口向量(向量 3)。

BADV 出错虚地址

虚拟地址结构

32位系统的页面大小4KB。PGD和PTE占10位,故页表项4B,页目录表4KB,页目录表中每个有效表项对应一个 4KB 页表。

虚拟地址 10:10:12 (PGD:PTE:PAGE_OFFSET)

PGD 全局目录基址

PGDL+PGDOFFSET = BADV对应PGD物理地址

TLBSRCH

TLBELO0,1

ld.w和st.w指令

1
2
ld.w   rd, rj, 0   /* 将rj内存中的数据装入到rd寄存器 */
st.w rd, rj, 0 /* 将rd寄存器中的数据存入rj内存 */

Load 用于把内存中的数据装载到寄存器中。 rd = *rj
Store用于把寄存器中的数据存入内存。 *rj = rd

DBAR 栅障指令

ERTN

TLBFILL

Page table bits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Page table bits */
......
#define _PAGE_VALID_SHIFT 0
#define _PAGE_DIRTY_SHIFT 1
#define _PAGE_PLV_SHIFT 2 /* 2~3, two bits */
#define _CACHE_SHIFT 4 /* 4~5, two bits */
#define _PAGE_GLOBAL_SHIFT 6
#define _PAGE_HUGE_SHIFT 6 /* HUGE is a PMD bit */
#define _PAGE_PRESENT_SHIFT 7
#define _PAGE_WRITE_SHIFT 8
#define _PAGE_PROTNONE_SHIFT 9
#define _PAGE_SPECIAL_SHIFT 10
#define _PAGE_HGLOBAL_SHIFT 12 /* HGlobal is a PMD bit */
#define _PAGE_PFN_SHIFT 12
#define _PAGE_PFN_END_SHIFT 31
......

虚拟地址划分:最低的2GB(USEG)既缓存又分页;随后的512MB(KSEG0)缓存但不分页,对应物理地址的低 512MB(虚拟地址去掉高三位即为物理地址);接下来的512MB(KSEG1)既不缓存又不分页,同样对应物理地址的低 512MB(虚拟地址去掉高三位即为物理地址);最后的 1GB(KSEG2)对应物理既缓存又分页。

其他

tlb_load

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
SYM_FUNC_START(handle_tlb_store)
csrwr ra, EXCEPTION_KS2

lu12i.w ra, 0xc
slli.w ra, ra, 0x10
csrrd t0, LOONGARCH_CSR_BADV
bgeu t0, ra, vmalloc_store

csrrd t1, LOONGARCH_CSR_PGD

vmalloc_done_store:
/* Get PGD offset in bytes */
srli.w t0, t0, 0x16
andi t0, t0, 0x3ff
slli.w t0, t0, 2
add.w t1, t1, t0
csrrd t0, LOONGARCH_CSR_BADV
ld.w t1, t1, 0
srli.w t0, t0, 0xc
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t1, t1, t0

label_smp_pgtable_store:
ld.w t0, t1, 0
tlbsrch

srli.w ra, t0, _PAGE_PRESENT_SHIFT
andi ra, ra, ((_PAGE_PRESENT | _PAGE_WRITE) >> _PAGE_PRESENT_SHIFT)
xori ra, ra, ((_PAGE_PRESENT | _PAGE_WRITE) >> _PAGE_PRESENT_SHIFT)
bne ra, $r0, nopage_tlb_store

ori t0, t0, (_PAGE_VALID | _PAGE_DIRTY)
st.w t0, t1, 0
// beq t0, $r0, label_smp_pgtable_store

ori t1, t1, 4
xori t1, t1, 4

ld.w t0, t1, 0
srli.w ra, t0, 0xc
slli.w ra, ra, 0x8
andi t0, t0, 0xff
add.w t0, t0, ra
csrwr t0, LOONGARCH_CSR_TLBELO0

ld.w t1, t1, 4
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi t1, t1, 0xff
add.w t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO1

tlbwr

csrrd t0, LOONGARCH_CSR_KS0
csrrd t1, LOONGARCH_CSR_KS1
csrrd ra, EXCEPTION_KS2
ertn

vmalloc_store:
la.abs t1, swapper_pg_dir
b vmalloc_done_store

nopage_tlb_store:
dbar 0
csrrd ra, EXCEPTION_KS2
la.abs t0, tlb_do_page_fault_1
jirl $r0, t0, 0

SYM_FUNC_END(handle_tlb_store)

tlb_modify

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
SYM_FUNC_START(handle_tlb_modify)
csrwr ra, EXCEPTION_KS2

/*
* The vmalloc handling is not in the hotpath.
*/
lu12i.w ra, 0xc
slli.w ra, ra, 0x10
csrrd t0, LOONGARCH_CSR_BADV
bgeu t0, ra, vmalloc_modify

csrrd t1, LOONGARCH_CSR_PGD

vmalloc_done_modify:
/* Get PGD offset in bytes */
srli.w t0, t0, 0x16
andi t0, t0, 0x3ff
slli.w t0, t0, 2
add.w t1, t1, t0
csrrd t0, LOONGARCH_CSR_BADV
ld.w t1, t1, 0
srli.w t0, t0, 0xc
andi t0, t0, 0x3ff
slli.w t0, t0, 0x2
add.w t1, t1, t0

label_smp_pgtable_modify:
ld.w t0, t1, 0
tlbsrch

srli.w ra, t0, _PAGE_WRITE_SHIFT
andi ra, ra, 1
beq ra, $r0, nopage_tlb_modify

ori t0, t0, (_PAGE_VALID | _PAGE_DIRTY)
st.w t0, t1, 0
// beq t0, $r0, label_smp_pgtable_modify

ori t1, t1, 4
xori t1, t1, 4

ld.w t0, t1, 0
srli.w ra, t0, 0xc
slli.w ra, ra, 0x8
andi t0, t0, 0xff
add.w t0, t0, ra
csrwr t0, LOONGARCH_CSR_TLBELO0

ld.w t1, t1, 0x4
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi t1, t1, 0xff
add.w t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO1

tlbwr

csrrd t0, LOONGARCH_CSR_KS0
csrrd t1, LOONGARCH_CSR_KS1
csrrd ra, EXCEPTION_KS2
ertn

vmalloc_modify:
la.abs t1, swapper_pg_dir
b vmalloc_done_modify

nopage_tlb_modify:
dbar 0
csrrd ra, EXCEPTION_KS2
la.abs t0, tlb_do_page_fault_1
jirl $r0, t0, 0

SYM_FUNC_END(handle_tlb_modify)

tlb_refill

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
SYM_FUNC_START(handle_tlb_refill)
csrwr t0, LOONGARCH_CSR_KS0
csrwr t1, LOONGARCH_CSR_KS1
csrwr ra, EXCEPTION_KS2

csrrd t0, LOONGARCH_CSR_PGD
csrrd t1, LOONGARCH_CSR_BADV
srli.w t1, t1, 0x16
slli.w t1, t1, 0x2
add.w t0, t0, t1
ld.w t0, t0, 0

csrrd t1, LOONGARCH_CSR_BADV
srli.w t1, t1, 0xa
andi t1, t1, 0xff8
add.w t0, t0, t1

ld.w t1, t0, 0
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi t1, t1, 0xff
add.w t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO0

ld.w t1, t0, 0x4
srli.w ra, t1, 0xc
slli.w ra, ra, 0x8
andi t1, t1, 0xff
add.w t1, t1, ra
csrwr t1, LOONGARCH_CSR_TLBELO1

tlbfill
csrrd t0, LOONGARCH_CSR_KS0
csrrd t1, LOONGARCH_CSR_KS1
csrrd ra, EXCEPTION_KS2
ertn
SYM_FUNC_END(handle_tlb_refill)