• ARM64 linux 异常处理 -- 异常向量


    ARM64 exception 架构

    vmlinux.lds

    arch/arm64/kernel/vmlinux.lds

    1. SECTIONS
    2. {
    3. .text : ALIGN(0x00010000) {
    4. _stext = .;
    5. . = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .;
    6. . = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .;
    7. . = ALIGN(8); __entry_text_start = .; *(.entry.text) __entry_text_end = .;
    8. }
    9. }

    对于 arm64, 其 vectors 对应的 section 需要由 vectors 定义决定。

    Vectors 定义

    arch/arm64/kernel/entry.S

    1. .pushsection ".entry.text", "ax"
    2. .align 11
    3. SYM_CODE_START(vectors)
    4. kernel_ventry 1, sync_invalid // Synchronous EL1t
    5. kernel_ventry 1, irq_invalid // IRQ EL1t
    6. kernel_ventry 1, fiq_invalid // FIQ EL1t
    7. kernel_ventry 1, error_invalid // Error EL1t
    8. kernel_ventry 1, sync // Synchronous EL1h
    9. kernel_ventry 1, irq // IRQ EL1h
    10. kernel_ventry 1, fiq_invalid // FIQ EL1h
    11. kernel_ventry 1, error // Error EL1h
    12. kernel_ventry 0, sync // Synchronous 64-bit EL0
    13. kernel_ventry 0, irq // IRQ 64-bit EL0
    14. kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
    15. kernel_ventry 0, error // Error 64-bit EL0
    16. #ifdef CONFIG_COMPAT
    17. kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
    18. kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
    19. kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
    20. kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
    21. #else
    22. kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
    23. kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
    24. kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
    25. kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
    26. #endif
    27. SYM_CODE_END(vectors)

    vectors 异常向量表定义了在各个异常等级下的处理函数。

    1. el1下:
      1. el1_sync_invalid
      2. el1_irq_invalid
      3. el1_fiq_invalid
      4. el1_error_invalid
      5. el1_sync
      6. el1_irq
      7. el1_fiq_invalid
      8. el1_error_invalid
    2. el0 下
      1. el0_sync
      2. el0_irq
      3. el0_fiq_invalid
      4. el0_error_invalid
      5. ...

    当发生异常时,ARM64 处理器根据异常的level 和 异常的 Offset 定位到对应的异常,然后跳转进对应异常处理函数进行处理。

    中断异常向量处理定义

    arch/arm64/kernel/entry.S

    1. .align 6
    2. SYM_CODE_START_LOCAL_NOALIGN(el1_irq)
    3. kernel_entry 1
    4. el1_interrupt_handler handle_arch_irq
    5. kernel_exit 1
    6. 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

    1. SYM_FUNC_START_LOCAL(__primary_switched)
    2. adr_l x4, init_task
    3. init_cpu_task x4, x5, x6
    4. adr_l x8, vectors // load VBAR_EL1 with virtual
    5. msr vbar_el1, x8 // vector table address
    6. isb
    7. ...
    8. SYM_FUNC_END(__primary_switched)

    到这里,设置好了 vectors 的地址到 vbar_el1 和 vbar_el2. 当有异常发生时,会找到 vectors 然后调用相关处理函数处理。

    中断处理

    hard irq 中断申请

    1. /* kernel/irq/manage.c */
    2. int request_threaded_irq(unsigned int irq, irq_handler_t handler,
    3. irq_handler_t thread_fn, unsigned long irqflags,
    4. const char *devname, void *dev_id)
    5. {
    6. //分配irq_action
    7. action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    8. if (!action)
    9. return -ENOMEM;
    10. //填写irq_action
    11. action->handler = handler;
    12. action->thread_fn = thread_fn;
    13. action->flags = irqflags;
    14. action->name = devname;
    15. action->dev_id = dev_id;
    16. //添加到IRQ链表
    17. struct irq_desc *desc = irq_to_desc(irq);
    18. retval = __setup_irq(irq, desc, action);
    19. ...
    20. }

    为申请的irq创建 struct irq_desc 结构体,并挂到 radix tree 上,之后设置 irq 和 action.

    hard irq 中断处理

    当发生中断时,先找到 Vectors, 然后根据 offset 找到 irq 入口,这里是el1h_64_irq_handler。

    1. static __always_inline void __el1_irq(struct pt_regs *regs,
    2. void (*handler)(struct pt_regs *))
    3. {
    4. enter_from_kernel_mode(regs);
    5. irq_enter_rcu();
    6. do_interrupt_handler(regs, handler);
    7. irq_exit_rcu();
    8. arm64_preempt_schedule_irq();
    9. exit_to_kernel_mode(regs);
    10. }
    11. static void noinstr el1_interrupt(struct pt_regs *regs,
    12. void (*handler)(struct pt_regs *))
    13. {
    14. write_sysreg(DAIF_PROCCTX_NOIRQ, daif); /* disable irq */
    15. if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
    16. __el1_pnmi(regs, handler);
    17. else
    18. __el1_irq(regs, handler);
    19. }
    20. asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
    21. {
    22. el1_interrupt(regs, handle_arch_irq);
    23. }

    处理流程是:

    最终会调用到 do_interrupt_handler, 这里接着调用了 handle_arch_irq。

    在 GIC 初始化时,在 gic_init_bases 中会通过 set_handle_irq 设置 handle_arch_irq 为 gic_handle_irq。 

     soft irq 中断申请

    1. /* 设置irq bh 的 action */
    2. void open_softirq(int nr, void (*action)(struct softirq_action *))
    3. {
    4. softirq_vec[nr].action = action;
    5. }
    6. /* 触发软中断 */
    7. void raise_softirq(unsigned int nr)
    8. {
    9. unsigned long flags;
    10. local_irq_save(flags);
    11. raise_softirq_irqoff(nr);
    12. local_irq_restore(flags);
    13. }
    14. void __raise_softirq_irqoff(unsigned int nr)
    15. {
    16. lockdep_assert_irqs_disabled();
    17. trace_softirq_raise(nr);
    18. or_softirq_pending(1UL << nr);
    19. }

    在这里,设置 softirq 的 pending 标志,表示 irq 处于 pending 状态,当 irq_exit或者 local_bh_enable 时会检查 pending 状态,如果是 pending 状态会处理软中断

    softirq 中断处理

    在 __irq_exit_rcu 时,

    1. static inline void __irq_exit_rcu(void)
    2. {
    3. #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
    4. local_irq_disable();
    5. #else
    6. lockdep_assert_irqs_disabled();
    7. #endif
    8. account_hardirq_exit(current);
    9. preempt_count_sub(HARDIRQ_OFFSET);
    10. if (!in_interrupt() && local_softirq_pending())
    11. invoke_softirq();
    12. tick_irq_exit();
    13. }

    local_irq_disable: 屏蔽中断bh

    判断不在中断上下文(!in_interrupt), 且 local_softirq_pending 时,处理 softirq.

    Dataabort 处理

    el1 的 dataabort 通过 el1_sync 为入口进行处理

    1. .align 6
    2. SYM_CODE_START_LOCAL_NOALIGN(el1_sync)
    3. kernel_entry 1
    4. mov x0, sp
    5. bl el1_sync_handler
    6. kernel_exit 1
    7. SYM_CODE_END(el1_sync)

    这里的主入口通过调用 el1_sync_handler 处理的。

    1. /* arch/arm64/kernel/entry-common.c */
    2. asmlinkage void noinstr el1_sync_handler(struct pt_regs *regs)
    3. {
    4. unsigned long esr = read_sysreg(esr_el1);
    5. switch (ESR_ELx_EC(esr)) {
    6. case ESR_ELx_EC_DABT_CUR:
    7. case ESR_ELx_EC_IABT_CUR:
    8. el1_abort(regs, esr);
    9. break;
    10. }
    11. }

    通过 el1_abort 最终可以调用到 do_mem_abort 来处理。

    1. /* arch/arm64/mm/fault.c */
    2. void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)
    3. {
    4. const struct fault_info *inf = esr_to_fault_info(esr);
    5. if (!inf->fn(addr, esr, regs))
    6. return;
    7. if (!user_mode(regs)) {
    8. pr_alert("Unhandled fault at 0x%016lx\n", addr);
    9. mem_abort_decode(esr);
    10. show_pte(addr);
    11. }
    12. arm64_notify_die(inf->name, regs,
    13. inf->sig, inf->code, (void __user *)addr, esr);
    14. }
  • 相关阅读:
    Vitalik详解5种类型的ZK-EVM
    【精选】自学网络安全的三个必经阶段(含路线图)
    Vue13 监视属性
    共享单车之数据存储
    一:Spring源码解析之prepareRefresh()
    基于Pytorch的从零开始的目标检测
    python异步编程之asyncio低阶API
    基于Quartz的可视化UI操作组件GZY.Quartz.MUI更新说明(附:在ABP中集成GZY.Quartz.MUI可视化操作组件)
    英语恶补ing
    每日三题 6.30(2)
  • 原文地址:https://blog.csdn.net/kakaBack/article/details/126573706