- 了解loongson irq
1.MIPS CPU总共定义了5种异常。分别为:
-
冷重启、热重启异常
-
TBL充填异常
-
XTBL充填异常
-
cache错误异常
-
其他异常
当发生上面异常后,CPU会跳转到相应位置执行异常处理程序。对于中断,CPU会跳转到其他类型的异常位置开始执行异常处理程序。当MIPS CPU发生中断后,CPU的状态变化到如下状态,总结如下列表:
- EPC寄存器保存了发生中断是程序执行指令的地址
- CP0中STATUS寄存器 EXL置位为1,表示正在异常状态,这个时候CPU不能再响应中断(不管IE是否开启),自动进入核心状态。
- CP0中CAUSE寄存器中ExcCode为0,表示是中断异常
- CP0中CAUSE寄存器IP被设置为不同的中断号
2.中断流程分析
arch/mips/kernel/traps.c:
2300 void __init trap_init(void)
2301 {
2302 extern char except_vec3_generic;
2303 extern char except_vec4;
2304 extern char except_vec3_r4000;
2305 unsigned long i;
2306
2307 check_wait(); //判断cpu的类型,然后赋值cpu_wait
2308 per_cpu_trap_init(true);
2365 set_handler(0x180, &except_vec3_generic, 0x80);
2366
2367 /*
2368 * Setup default vectors
2369 */
2370 for (i = 0; i <= 31; i++)
2371 set_except_vector(i, handle_reserved);
2386 /*
2387 * Initialise interrupt handlers
2388 */
2389 if (cpu_has_veic || cpu_has_vint) {
2390 int nvec = cpu_has_veic ? 64 : 8;
2391 for (i = 0; i < nvec; i++)
2392 set_vi_handler(i, NULL);
2393 }
2394 else if (cpu_has_divec)
2395 set_handler(0x200, &except_vec4, 0x8);
2396
2410
2411 set_except_vector(EXCCODE_INT, using_rollback_handler() ?
2412 rollback_handle_int : handle_int);
2413 set_except_vector(EXCCODE_MOD, handle_tlbm);
2414 set_except_vector(EXCCODE_TLBL, handle_tlbl);
2415 set_except_vector(EXCCODE_TLBS, handle_tlbs);
2416
2417 set_except_vector(EXCCODE_ADEL, handle_adel);
2418 set_except_vector(EXCCODE_ADES, handle_ades);
2419
2420 set_except_vector(EXCCODE_IBE, handle_ibe);
2421 set_except_vector(EXCCODE_DBE, handle_dbe);
2422
2423 set_except_vector(EXCCODE_SYS, handle_sys);
2424 set_except_vector(EXCCODE_BP, handle_bp);
2425
2426 if (rdhwr_noopt)
2427 set_except_vector(EXCCODE_RI, handle_ri);
2428 else {
2429 if (cpu_has_vtag_icache)
2430 set_except_vector(EXCCODE_RI, handle_ri_rdhwr_tlbp);
2431 else if (current_cpu_type() == CPU_LOONGSON3 ||
2432 current_cpu_type() == CPU_LOONGSON3_COMP)
2433 set_except_vector(EXCCODE_RI, handle_ri_rdhwr_tlbp);
2434 else
2435 set_except_vector(EXCCODE_RI, handle_ri_rdhwr);
2436 }
2437
2438 set_except_vector(EXCCODE_CPU, handle_cpu);
2439 set_except_vector(EXCCODE_OV, handle_ov);
2440 set_except_vector(EXCCODE_TR, handle_tr);
2441 set_except_vector(EXCCODE_MSAFPE, handle_msa_fpe);
2442
2443 if (board_nmi_handler_setup)
2444 board_nmi_handler_setup();
2445
2446 if (cpu_has_fpu && !cpu_has_nofpuex)
2447 set_except_vector(EXCCODE_FPE, handle_fpe);
2448
2449 #ifdef CONFIG_CPU_LOONGSON3
2450 set_except_vector(LS64_EXCCODE_GSEX, handle_gsex);
2451 #else
2452 set_except_vector(MIPS_EXCCODE_TLBPAR, handle_ftlb);
2453 #endif
2454
2455 if (cpu_has_rixiex) {
2456 set_except_vector(EXCCODE_TLBRI, tlb_do_page_fault_0);
2457 set_except_vector(EXCCODE_TLBXI, tlb_do_page_fault_0);
2458 }
2459
2460 set_except_vector(EXCCODE_MSADIS, handle_msa);
2461 set_except_vector(EXCCODE_MDMX, handle_mdmx);
2487 }
重点分析:
2.1.TLB Refill初始化:
2308 per_cpu_trap_init(true);
内核启动过程中,会对TLB Refill异常进行初始化,设置相应的处理接口。主要流程如下:
->per_cpu_trap_init -> tlb_init -> build_tlb_refill_handler -> build_loongson3_tlb_refill_handler
1556 static void build_loongson3_tlb_refill_handler(void)
1557 {
1558 u32 *p = tlb_handler;
1559 struct uasm_label *l = labels;
1560 struct uasm_reloc *r = relocs;
1561
1562 memset(labels, 0, sizeof(labels));
1563 memset(relocs, 0, sizeof(relocs));
1564 memset(tlb_handler, 0, sizeof(tlb_handler));
1565
1566 if (check_for_high_segbits) {
1567 uasm_i_dmfc0(&p, K0, C0_BADVADDR);
1568 uasm_i_dsrl_safe(&p, K1, K0, PGDIR_SHIFT + PGD_ORDER + PAGE_SHIFT - 3);
1569 uasm_il_beqz(&p, &r, K1, label_vmalloc);
1570 uasm_i_nop(&p);
1571
1572 uasm_il_bgez(&p, &r, K0, label_large_segbits_fault);
1573 uasm_i_nop(&p);
1574 uasm_l_vmalloc(&l, p);
1575 }
1576
1577 uasm_i_dmfc0(&p, K1, C0_PGD);
1578
1579 uasm_i_lddir(&p, K0, K1, 3); /* global page dir */
1580 #ifndef __PAGETABLE_PMD_FOLDED
1581 uasm_i_lddir(&p, K1, K0, 1); /* middle page dir */
1582 #endif
1583 uasm_i_ldpte(&p, K1, 0); /* even */
1584 uasm_i_ldpte(&p, K1, 1); /* odd */
1585 uasm_i_tlbwr(&p);
1586
1587 /* restore page mask */
1588 if (PM_DEFAULT_MASK >> 16) {
1589 uasm_i_lui(&p, K0, PM_DEFAULT_MASK >> 16);
1590 uasm_i_ori(&p, K0, K0, PM_DEFAULT_MASK & 0xffff);
1591 uasm_i_mtc0(&p, K0, C0_PAGEMASK);
1592 } else if (PM_DEFAULT_MASK) {
1593 uasm_i_ori(&p, K0, 0, PM_DEFAULT_MASK);
1594 uasm_i_mtc0(&p, K0, C0_PAGEMASK);
1595 } else {
1596 uasm_i_mtc0(&p, 0, C0_PAGEMASK);
1597 }
1598
1599 uasm_i_eret(&p);
1600
1601 if (check_for_high_segbits) {
1602 uasm_l_large_segbits_fault(&l, p);
1603 UASM_i_LA(&p, K1, (unsigned long)tlb_do_page_fault_0);
1604 uasm_i_jr(&p, K1);
1605 uasm_i_nop(&p);
1606 }
1607
1608 uasm_resolve_relocs(relocs, labels);
1609 memcpy((void *)(ebase + 0x80), tlb_handler, 0x80);
1610 local_flush_icache_range(ebase + 0x80, ebase + 0x100);
1611 dump_handler("loongson3_tlb_refill",
1612 (u32 *)(ebase + 0x80), (u32 *)(ebase + 0x100));
1613 }
2.2.初级中断处理函数rollback_handle_int 或者handle_int
选择哪个取决于using_rollback_handler()返回值,而using_rollback_handler()又是根据cpu_wait的取值来判断,如果是 r4k_wait,则返回1,初级中断处理函数是rollback_handle_int();如果不是,则using_rollback_handler()返回0,初级中断处理函数是handle_int()。
2411 set_except_vector(EXCCODE_INT, using_rollback_handler() ?
2412 rollback_handle_int : handle_int);
13 static inline int using_rollback_handler(void)
14 {
15 return cpu_wait == r4k_wait;
16 }
2.2.1.rk4_wait 实现功能:
- 打开本地中断;
- 调用__r4k_wait,主要实现:
- 先从当前进程的thread_info中取出进程标志变量到t0,然后判断 _TIF_NEED_RESCHED有没有置位。如果置位,说明有进程需要调度,跳转到标号1处,__r4k_wait返回;如果没有置位,执行wait进入空闲等待状态(停止执行指令流,进入节能状态,直到被中断唤醒再继续执行指令流)。
arch/mips/kernel/idle.c:
50 void __cpuidle r4k_wait(void)
51 {
52 local_irq_enable();
53 __r4k_wait();
54 }
arch/mips/kernel/genex.S:
110 .align 5 /* 32 byte rollback region */
111 LEAF(__r4k_wait)
112 .set push
113 .set noreorder
114 /* start of rollback region */
115 LONG_L t0, TI_FLAGS($28)
116 nop
117 andi t0, _TIF_NEED_RESCHED
118 bnez t0, 1f
119 nop
120 nop
121 nop
122 #ifdef CONFIG_CPU_MICROMIPS
123 nop
124 nop
125 nop
126 nop
127 #endif
128 .set MIPS_ISA_ARCH_LEVEL_RAW
129 wait
130 /* end of rollback region (the region size must be power of two) */
131 1:
132 jr ra
133 nop
134 .set pop
135 END(__r4k_wait)
rollback_handle_int 或者handle_int定义如下:
arch/mips/kernel/genex.S:
137 .macro BUILD_ROLLBACK_PROLOGUE handler
138 FEXPORT(rollback_\handler)
139 .set push
140 .set noat
141 MFC0 k0, CP0_EPC
142 PTR_LA k1, __r4k_wait
143 ori k0, 0x1f /* 32 byte rollback region */
144 xori k0, 0x1f
145 bne k0, k1, \handler
146 MTC0 k0, CP0_EPC
147 .set pop
148 .endm
151 BUILD_ROLLBACK_PROLOGUE handle_int
152 NESTED(handle_int, PT_SIZE, sp)
185 SAVE_ALL docfi=1
186 CLI
189 LONG_L s0, TI_REGS($28)
190 LONG_S sp, TI_REGS($28)
225 jal plat_irq_dispatch
230 j ret_from_irq
234 END(handle_int)
异常处理总是意味着上下文切换,典型例子:应用程序在用户态运行时发生异常,则通过上下文切换进入内核态,进行异常处理; 处理完之后,再通过上下文切换到用户态。从用户态切换到内核态时,需要将当前寄存器上下文保存到内核栈; 从内核态切换到用户态时,需要将内核栈中的寄存器上下文恢复。
需要保存/恢复的寄存器上下文包括大部分通用寄存器和少数协处理器0寄存器的内容,内核提供宏完成这些操作:
- SAVE_AT: 将AT 寄存器保存到内核栈。
- SAVE_TEMP: 将T 系列寄存器保存到内核栈。
- SAVE_STATIC: 将S 系列寄存器保存到内核栈。
- SAVE_SOME:将ZERO 寄存器,GP寄存器, SP寄存器,RA寄存器,A系列寄存器,V系列寄存器以及CP0的- TATUS,CAUSE,EPC寄存器保存到内核栈。
- SAVE_ALL: 等同于SAVE_AT,SAVE_TEMP,SAVE_STATIC,SAVE_SOME。
- RESTORE_AT: 将内核栈恢复AT 寄存器。
- …
2.2.2.中断处理的分派:
中断处理是根据类型进行分派。
- 第一、二级分派: plat_irq_dispatch
38 asmlinkage void plat_irq_dispatch(void)
39 {
40 unsigned int pending;
41
42 pending = read_c0_cause() & read_c0_status() & ST0_IM;
43
44 /* machine-specific plat_irq_dispatch */
45 mach_irq_dispatch(pending);
46 }
取出CAUSE寄存器和STATUS寄存器的值做与运算,然后取出IP位部分(IP0-IP7)赋值给pending变量。然后将该变量传递给函数 mach_irq_dispatch,执行第二级分派。
209 void mach_irq_dispatch(unsigned int pending)
210 {
211 if(cpu_guestmode)
212 return mach_guest_irq_dispatch(pending);
213
214 if (pending & CAUSEF_IP7)
215 do_IRQ(LOONGSON_TIMER_IRQ);
216 #if defined(CONFIG_SMP)
217 if (pending & CAUSEF_IP6)
218 loongson3_ipi_interrupt(NULL);
219 #endif
220 if (pending & CAUSEF_IP3)
221 loongson_pch->irq_dispatch();
222 if (pending & CAUSEF_IP2)
223 do_IRQ(LOONGSON_UART_IRQ);
224 if (pending & UNUSED_IPS) {
225 pr_err("%s : spurious interrupt\n", __func__);
226 spurious_interrupt();
227 }
228 }
ret_from_irq:
进程从中断返回时,调用ret_from_irq。
arch/mips/kernel/entry.S:
35 FEXPORT(ret_from_irq)
36 LONG_S s0, TI_REGS($28)
2.3. tlb_do_page_fault_0:
设置EXCCODE_TLBRI和EXCCODE_TLBXI异常处理函数 tlb_do_page_fault_0.
2455if (cpu_has_rixiex) {
2456 set_except_vector(EXCCODE_TLBRI, tlb_do_page_fault_0);
2457 set_except_vector(EXCCODE_TLBXI, tlb_do_page_fault_0);
2458 }
其中 tlb_do_page_fault_0 定义如下:
arch/mips/mm/tlbex-fault.S:
13 .macro tlb_do_page_fault, write
14 NESTED(tlb_do_page_fault_\write, PT_SIZE, sp)
15 .cfi_signal_frame
16 SAVE_ALL docfi=1
17 MFC0 a2, CP0_BADVADDR
18 KMODE
19 move a0, sp
20 REG_S a2, PT_BVADDR(sp)
21 li a1, \write
22 jal do_page_fault
23 j ret_from_exception
24 END(tlb_do_page_fault_\write)
25 .endm
26
27 tlb_do_page_fault 0 //tlb_do_page_fault_0 读操作触发异常
28 tlb_do_page_fault 1 //tlb_do_page_fault_1 写操作触发异常
tlb_do_page_fault_0/tlb_do_page_fault_1 最终都调用do_page_fault进行缺页异常处理,建立有效页表项(或者处理权限问题)后再进行TLB 装填。
refer to
- http://happyseeker.github.io/kernel/2016/12/27/mips-TLB-miss.html