linux IRQ Management (十一) - 龙芯中断

  • 了解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

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/109447234