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

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 异常向量表定义了在各个异常等级下的处理函数。

  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

    .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);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值