Linux内核中断的处理分析--MIPS体系结构
Linux内核中断处理流程分析--MIPS体系结构
作者:weirdo-xo
时间:2021-7-5
说明:本次的博客目标子在于通过代码分析MIPS中具有中断向量入口的例外,可以参考我另一篇关于MIPS中断体系结构的文章。这里将会讲解4个常见的例外
参考:《用芯探核--基于龙芯的Linux内核探索解析》
1. 硬复位、软复位、NMI
发生硬复位、软复位、NMI后处理器的PC会被发射到0xBFC00000的地址,这也是处理器启动时默认的寻址地址。这三种异常对于处理器来说都是严重的错误,是解决不了的,最终的结构基本都是导致CPU重启或者死机。当PC被发射到0xBFC00000地址后,可以通过STatus的如下寄存器判断具体的异常类型:
- 硬复位:BEV=1,SR=0,NMI=0
- 软复位:BEV=1,SR=1,NMI=0
- NMI异常:BEV=1,SR=0,NMI=1
当发生复位相关的异常时,Bootlaoder(BIOS)会重新的执行开机的流程; NMI异常发生时,会有处理器的参与,下面来看一下相关的代码:
/*
* NMI debug exception handler for MIPS reference boards.
* The NMI debug exception entry point is 0xbfc00000, which
* normally is in the boot PROM, so the boot PROM must do a
* unconditional jump to this vector.
*/
NESTED(except_vec_nmi, 0, sp)
j nmi_handler
END(except_vec_nmi)
__FINIT
NESTED(nmi_handler, PT_SIZE, sp)
.cfi_signal_frame
.set push
.set noat
/*
* Clear ERL - restore segment mapping
* Clear BEV - required for page fault exception handler to work
*/
mfc0 k0, CP0_STATUS
ori k0, k0, ST0_EXL
li k1, ~(ST0_BEV | ST0_ERL)
and k0, k0, k1
mtc0 k0, CP0_STATUS
_ehb
SAVE_ALL
move a0, sp
jal nmi_exception_handler
/* nmi_exception_handler never returns */
.set pop
END(nmi_handler)
这里主要对Status寄存器进行一些操作,按后跳转到nmi_exception_handler函数进行处理。nmi_exception_handler函数的核心是调用通知链执行关机或重启操作,下面是处理代码:
arch/mips/kernel/traps.c
void __noreturn nmi_exception_handler(struct pt_regs *regs)
{
char str[100];
nmi_enter();
raw_notifier_call_chain(&nmi_chain, 0, regs);
bust_spinlocks(1);
snprintf(str, 100, "CPU%d NMI taken, CP0_EPC=%lx\n",
smp_processor_id(), regs->cp0_epc);
regs->cp0_epc = read_c0_errorepc();
die(str, regs);
nmi_exit();
}
这里的函数标识为__noreturn可以看出函数执行后不会返回。
2. Cache错误异常
Cache如果真的发生了异常,软件一般是处理不了的,最终的命运是内核报告Panic,进入死机状态。下面看一下代码中是如何处理cache错误异常的:
/*
* Game over. Go to the button. Press gently. Swear where allowed by
* legislation.
*/
LEAF(except_vec2_generic)
.set noreorder
.set noat
.set mips0
/*
* This is a very bad place to be. Our cache error
* detection has triggered. If we have write-back data
* in the cache, we may not be able to recover. As a
* first-order desperate measure, turn off KSEG0 cacheing.
*/
mfc0 k0,CP0_CONFIG
li k1,~CONF_CM_CMASK
and k0,k0,k1
ori k0,k0,CONF_CM_UNCACHED
mtc0 k0,CP0_CONFIG
/* Give it a few cycles to sink in... */
nop
nop
nop
j cache_parity_error
nop
END(except_vec2_generic)
从注释我们也可以看出,如果代码执行到这里的话就Game Over
,这里主要的操作是Uncache内存的Kseg0段。然后跳转到cache_parity_error
处理函数中,该函数会向用户报告错误并进入panic()。下面看一下cache_parity_error
函数的处理。
asmlinkage void cache_parity_error(void)
{
const int field = 2 * sizeof(unsigned long);
unsigned int reg_val;
/* For the moment, report the problem and hang. */
printk("Cache error exception:\n");
printk("cp0_errorepc == %0*lx\n", field, read_c0_errorepc());
reg_val = read_c0_cacheerr();
printk("c0_cacheerr == %08x\n", reg_val);
printk("Decoded c0_cacheerr: %s cache fault in %s reference.\n",
reg_val & (1<<30) ? "secondary" : "primary",
reg_val & (1<<31) ? "data" : "insn");
pr_err("Error bits: %s%s%s%s%s%s%s\n",
reg_val & (1<<29) ? "ED " : "",
reg_val & (1<<28) ? "ET " : "",
reg_val & (1<<26) ? "EE " : "",
reg_val & (1<<25) ? "EB " : "",
reg_val & (1<<24) ? "EI " : "",
reg_val & (1<<23) ? "E1 " : "",
reg_val & (1<<22) ? "E0 " : "");
printk("IDX: 0x%08x\n", reg_val & ((1<<22)-1));
if (reg_val & (1<<22))
printk("DErrAddr0: 0x%0*lx\n", field, read_c0_derraddr0());
if (reg_val & (1<<23))
printk("DErrAddr1: 0x%0*lx\n", field, read_c0_derraddr1());
panic("Can't handle the cache error!");
}
的确,该函数主要是向用户报告当前遇到的错误。
3. TLB/XTLB错误异常
TLB/XTLB中只有重填具有特殊的入口向量,并且为了平台兼容代码并没有采用静态代码的方式生成,而是采用动态代码--内核微汇编器--的方式产生。至于内核微汇编其的原理这里就不再讲解。下面献给出相关的代码:
arch/mips/mm/tlbex.c
static void build_loongson3_tlb_refill_handler(void)
{
u32 *p = tlb_handler;
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
memset(tlb_handler, 0, sizeof(tlb_handler));
if (check_for_high_segbits) {
uasm_i_dmfc0(&p, K0, C0_BADVADDR);
uasm_i_dsrl_safe(&p, K1, K0, PGDIR_SHIFT + PGD_ORDER + PAGE_SHIFT - 3);
uasm_il_beqz(&p, &r, K1, label_vmalloc);
uasm_i_nop(&p);
uasm_il_bgez(&p, &r, K0, label_large_segbits_fault);
uasm_i_nop(&p);
uasm_l_vmalloc(&l, p);
}
uasm_i_dmfc0(&p, K1, C0_PGD);
uasm_i_lddir(&p, K0, K1, 3); /* global page dir */
#ifndef __PAGETABLE_PMD_FOLDED
uasm_i_lddir(&p, K1, K0, 1); /* middle page dir */
#endif
uasm_i_ldpte(&p, K1, 0); /* even */
uasm_i_ldpte(&p, K1, 1); /* odd */
uasm_i_tlbwr(&p);
/* restore page mask */
if (PM_DEFAULT_MASK >> 16) {
uasm_i_lui(&p, K0, PM_DEFAULT_MASK >> 16);
uasm_i_ori(&p, K0, K0, PM_DEFAULT_MASK & 0xffff);
uasm_i_mtc0(&p, K0, C0_PAGEMASK);
} else if (PM_DEFAULT_MASK) {
uasm_i_ori(&p, K0, 0, PM_DEFAULT_MASK);
uasm_i_mtc0(&p, K0, C0_PAGEMASK);
} else {
uasm_i_mtc0(&p, 0, C0_PAGEMASK);
}
uasm_i_eret(&p);
if (check_for_high_segbits) {
uasm_l_large_segbits_fault(&l, p);
UASM_i_LA(&p, K1, (unsigned long)tlb_do_page_fault_0);
uasm_i_jr(&p, K1);
uasm_i_nop(&p);
}
uasm_resolve_relocs(relocs, labels);
memcpy((void *)(ebase + 0x80), tlb_handler, 0x80);
local_flush_icache_range(ebase + 0x80, ebase + 0x100);
dump_handler("loongson3_tlb_refill",
(u32 *)(ebase + 0x80), (u32 *)(ebase + 0x100));
}
关于微汇编器的含义,我们可以通过asm_x_中的来进行初步的判断,具体的含义可以查看源码中的定义。
4. 其他通用异常处理
其他通用异常处理可能是离我们最近的一个具有向量地址入口的例外了,在该系列异常中包括了我们通常使用的中断。该函数中一共会处理32个异常事件(实际使用的没有32个,部分保留),所以处理方法是在内存中申请了32个地址的内存exception_handlers
,我们把异常事件处理的函数存放在这个数组中。当CPU执行到这里的代码之后,从Cause寄存器的ExCode中取出索引值,通过索引值跳转到指定的事件处理函数中,下面看一下代码的实现:
/*
* General exception vector for all other CPUs.
*
* Be careful when changing this, it has to be at most 128 bytes
* to fit into space reserved for the exception handler.
*/
NESTED(except_vec3_generic, 0, sp)
.set push
.set noat
mfc0 k1, CP0_CAUSE
andi k1, k1, 0x7c
dsll k1, k1, 1
PTR_L k0, exception_handlers(k1)
jr k0
.set pop
END(except_vec3_generic)
PTR_L k0, exception_handlers(k1)
该条指令便是跳转到目标地址,在目标地址中进行处理。