专栏目录:专栏目录传送门

set_handle_irq(gic_handle_irq);

set_handle_irq

这个函数的功能很简单,将gic_handle_irq设置为中断处理函数。在发生中断异常后,内核就会切入到这个中断处理函数中.

void (*handle_arch_irq)(struct pt_regs *) __ro_after_init = default_handle_irq;

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq != default_handle_irq)
		return -EBUSY;

	handle_arch_irq = handle_irq;
	pr_info("Root IRQ handler: %ps\n", handle_irq);
	return 0;
}

hard中断入口

EL0和EL1会分别用到这里的handle_arch_irq函数。下面会各针对EL1和EL0进行分析。

static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
{
	el0_interrupt(regs, handle_arch_irq);
}

asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
	el1_interrupt(regs, handle_arch_irq);
}

中断代码路由过程如下图所示。

i.MX8MP平台开发分享(gicv3篇)-- set_handle_irq及中断路由过程分析-LMLPHP

arm64所有的异常向量存储在一下所示的vectors中。

SYM_CODE_START(vectors)
	kernel_ventry	1, t, 64, sync		// Synchronous EL1t
	kernel_ventry	1, t, 64, irq		// IRQ EL1t
	kernel_ventry	1, t, 64, fiq		// FIQ EL1h
	kernel_ventry	1, t, 64, error		// Error EL1t

	kernel_ventry	1, h, 64, sync		// Synchronous EL1h
	kernel_ventry	1, h, 64, irq		// IRQ EL1h
	kernel_ventry	1, h, 64, fiq		// FIQ EL1h
	kernel_ventry	1, h, 64, error		// Error EL1h

	kernel_ventry	0, t, 64, sync		// Synchronous 64-bit EL0
	kernel_ventry	0, t, 64, irq		// IRQ 64-bit EL0
	kernel_ventry	0, t, 64, fiq		// FIQ 64-bit EL0
	kernel_ventry	0, t, 64, error		// Error 64-bit EL0

	kernel_ventry	0, t, 32, sync		// Synchronous 32-bit EL0
	kernel_ventry	0, t, 32, irq		// IRQ 32-bit EL0
	kernel_ventry	0, t, 32, fiq		// FIQ 32-bit EL0
	kernel_ventry	0, t, 32, error		// Error 32-bit EL0
SYM_CODE_END(vectors)

如何设置这个异常向量vectors?在__primary_switched函数中使用msr指令将vectors异常向量表的地址设置到vbar_el1寄存器中。

adr_l x8, vectors
msr vbar_el1, x8

如何正确看待每一个异常向量?这里涉及的代码比较复杂,以kernel_ventry 1, h, 64, irq这个异常向量为例,kernel_ventry通过 el\el\ht\()_\regsize\()_\label展开为了el1h_64_irq这个函数,然后跳转到这个函数。

这个el1h_64_irq函数是如何定义的?使用SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)定义每种异常函数。在这个函数中,会跳转到具体的el\el\ht\()_\regsize\()_\label\()_handler

针对每一个异常向量,使用entry_handler将其信息展开,一开始就定义了上面的el\el\ht\()_\regsize\()_\label函数。通过el\el\ht\()_\regsize\()_\label\()_handler展开为内核C代码中各自的handler,例如这里就是el1h_64_irq_handler,也就是arch/arm64/kernel/entry-common.c里的el1h_64_irq_handler

在el1h_64_irq函数一开始使用kernel_entry存储和清除EL0/1中的通用寄存器。通过 kernel_exit来退出异常,恢复刚才保存的通用寄存器,执行 ERET 来恢复 PC 和 PSTATE。如果是el0,则使用ret_to_user返回用户态,el1是ret_to_kernel返回内核态。

.macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
	kernel_entry \el, \regsize
	mov	x0, sp
	bl	el\el\ht\()_\regsize\()_\label\()_handler
	.if \el == 0
	b	ret_to_user
	.else
	b	ret_to_kernel
	.endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
	.endm
	
	entry_handler	1, t, 64, sync
	entry_handler	1, t, 64, irq
	entry_handler	1, t, 64, fiq
	entry_handler	1, t, 64, error

	entry_handler	1, h, 64, sync
	entry_handler	1, h, 64, irq
	entry_handler	1, h, 64, fiq
	entry_handler	1, h, 64, error

	entry_handler	0, t, 64, sync
	entry_handler	0, t, 64, irq
	entry_handler	0, t, 64, fiq
	entry_handler	0, t, 64, error

	entry_handler	0, t, 32, sync
	entry_handler	0, t, 32, irq
	entry_handler	0, t, 32, fiq
	entry_handler	0, t, 32, error
04-13 22:41