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 定义决定。
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 异常向量表定义了在各个异常等级下的处理函数。
当发生异常时,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 进行处理了。
- 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 然后调用相关处理函数处理。
- /* 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.
当发生中断时,先找到 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。
- /* 设置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 状态会处理软中断
在 __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.
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);
- }