ARM64 exception 架构
vmlinux.lds
arch/arm64/kernel/vmlinux.lds
SECTIONS
{
.text : ALIGN(0x00010000) {
_stext = .;
. = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .;
. = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .;
. = ALIGN(8); __entry_text_start = .; *(.entry.text) __entry_text_end = .;
}
}
对于 arm64, 其 vectors 对应的 section 需要由 vectors 定义决定。
Vectors 定义
arch/arm64/kernel/entry.S
.pushsection ".entry.text", "ax"
.align 11
SYM_CODE_START(vectors)
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq_invalid // FIQ EL1h
kernel_ventry 1, error // Error EL1h
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
kernel_ventry 0, error // Error 64-bit EL0
#ifdef CONFIG_COMPAT
kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
#else
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endif
SYM_CODE_END(vectors)
vectors 异常向量表定义了在各个异常等级下的处理函数。
- el1下:
- el1_sync_invalid
- el1_irq_invalid
- el1_fiq_invalid
- el1_error_invalid
- el1_sync
- el1_irq
- el1_fiq_invalid
- el1_error_invalid
- el0 下
- el0_sync
- el0_irq
- el0_fiq_invalid
- el0_error_invalid
- ...
当发生异常时,ARM64 处理器根据异常的level 和 异常的 Offset 定位到对应的异常,然后跳转进对应异常处理函数进行处理。
中断异常向量处理定义
arch/arm64/kernel/entry.S
.align 6
SYM_CODE_START_LOCAL_NOALIGN(el1_irq)
kernel_entry 1
el1_interrupt_handler handle_arch_irq
kernel_exit 1
SYM_CODE_END(el1_irq)
其中 kernel_entry 主要用于获取异常下的各个寄存器信息,task 的信息等
el1_interrupt_handler 则是进入中断上下文(irq_stack_entry, irq_stack_exit),调用 handle_arch_irq 进行中断处理。
上面 handle_arch_irq 是在 gic 初始化过程中通过 set_handle_irq 设置的,具体参见 hard irq 中断处理。之后就可以跳转到 gic_handle_irq 进行处理了。
vectors设定 vbar
SYM_FUNC_START_LOCAL(__primary_switched)
adr_l x4, init_task
init_cpu_task x4, x5, x6
adr_l x8, vectors // load VBAR_EL1 with virtual
msr vbar_el1, x8 // vector table address
isb
...
SYM_FUNC_END(__primary_switched)
到这里,设置好了 vectors 的地址到 vbar_el1 和 vbar_el2. 当有异常发生时,会找到 vectors 然后调用相关处理函数处理。
中断处理
hard irq 中断申请
/* kernel/irq/manage.c */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
//分配irq_action
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
//填写irq_action
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
//添加到IRQ链表
struct irq_desc *desc = irq_to_desc(irq);
retval = __setup_irq(irq, desc, action);
...
}
为申请的irq创建 struct irq_desc 结构体,并挂到 radix tree 上,之后设置 irq 和 action.
hard irq 中断处理
当发生中断时,先找到 Vectors, 然后根据 offset 找到 irq 入口,这里是el1h_64_irq_handler。
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
enter_from_kernel_mode(regs);
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
arm64_preempt_schedule_irq();
exit_to_kernel_mode(regs);
}
static void noinstr el1_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
write_sysreg(DAIF_PROCCTX_NOIRQ, daif); /* disable irq */
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
__el1_pnmi(regs, handler);
else
__el1_irq(regs, handler);
}
asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
el1_interrupt(regs, handle_arch_irq);
}
处理流程是:
最终会调用到 do_interrupt_handler, 这里接着调用了 handle_arch_irq。
在 GIC 初始化时,在 gic_init_bases 中会通过 set_handle_irq 设置 handle_arch_irq 为 gic_handle_irq。
soft irq 中断申请
/* 设置irq bh 的 action */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
/* 触发软中断 */
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
void __raise_softirq_irqoff(unsigned int nr)
{
lockdep_assert_irqs_disabled();
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}
在这里,设置 softirq 的 pending 标志,表示 irq 处于 pending 状态,当 irq_exit或者 local_bh_enable 时会检查 pending 状态,如果是 pending 状态会处理软中断
softirq 中断处理
在 __irq_exit_rcu 时,
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
tick_irq_exit();
}
local_irq_disable: 屏蔽中断bh
判断不在中断上下文(!in_interrupt), 且 local_softirq_pending 时,处理 softirq.
Dataabort 处理
el1 的 dataabort 通过 el1_sync 为入口进行处理
.align 6
SYM_CODE_START_LOCAL_NOALIGN(el1_sync)
kernel_entry 1
mov x0, sp
bl el1_sync_handler
kernel_exit 1
SYM_CODE_END(el1_sync)
这里的主入口通过调用 el1_sync_handler 处理的。
/* arch/arm64/kernel/entry-common.c */
asmlinkage void noinstr el1_sync_handler(struct pt_regs *regs)
{
unsigned long esr = read_sysreg(esr_el1);
switch (ESR_ELx_EC(esr)) {
case ESR_ELx_EC_DABT_CUR:
case ESR_ELx_EC_IABT_CUR:
el1_abort(regs, esr);
break;
}
}
通过 el1_abort 最终可以调用到 do_mem_abort 来处理。
/* arch/arm64/mm/fault.c */
void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)
{
const struct fault_info *inf = esr_to_fault_info(esr);
if (!inf->fn(addr, esr, regs))
return;
if (!user_mode(regs)) {
pr_alert("Unhandled fault at 0x%016lx\n", addr);
mem_abort_decode(esr);
show_pte(addr);
}
arm64_notify_die(inf->name, regs,
inf->sig, inf->code, (void __user *)addr, esr);
}