中断处理流程梳理

Posted Loopers

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了中断处理流程梳理相关的知识,希望对你有一定的参考价值。

在之前的ARMv8-A的异常文章中提到,ARMv8-A将中断也当做一种异常,中断分为IRQ和FIQ

 

假设当前在EL0运行一个64位的应用程序,触发了一个EL0的IRQ中断,则处理器会做如下的操作

  • 将CPU的状态PSTATE保存到SPSR_EL1寄存器中,PC保存到ELR_EL1寄存器中
  • 跳到异常的处理函数处

则就会跳到ARM64对应的异常向量表

/*
 * Exception vectors.
 */
    .pushsection ".entry.text", "ax"
 
    .align  11
ENTRY(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
END(vectors)

因为是EL0触发的异常,而且异常状态是IRQ,则会根据异常向量表的基址跳大EL0_irq处, kernel_ventry 0, irq // IRQ 64-bit EL0

el0_irq:
    kernel_entry 0
el0_irq_naked:
    enable_da_f
 
    irq_handler
    b   ret_to_user
ENDPROC(el0_irq)

kernel_entry 0,其中kernel_entry是一个宏,此宏会保存当前的线程,也就是进程的寄存器

stp     x0, x1, [sp, #16 * 0]
stp     x2, x3, [sp, #16 * 1]
stp     x4, x5, [sp, #16 * 2]
stp     x6, x7, [sp, #16 * 3]
stp     x8, x9, [sp, #16 * 4]
stp     x10, x11, [sp, #16 * 5]
stp     x12, x13, [sp, #16 * 6]
stp     x14, x15, [sp, #16 * 7]
stp     x16, x17, [sp, #16 * 8]
stp     x18, x19, [sp, #16 * 9]
stp     x20, x21, [sp, #16 * 10]
stp     x22, x23, [sp, #16 * 11]
stp     x24, x25, [sp, #16 * 12]
stp     x26, x27, [sp, #16 * 13]
stp     x28, x29, [sp, #16 * 14]

enable_da_f 这个在ARM处理器状态由描述,DAIF。这里是关闭IRQ中断

/* IRQ is the lowest priority flag, unconditionally unmask the rest. */
.macro enable_da_f
msr daifclr, #(8 | 4 | 1)
.endm
  • 跳转到irq_handler去处理中断
  • 当中断处理完毕后,就会通过ret_to_user返回到用户空间
/*
 * Interrupt handling.
 */
        .macro  irq_handler
        ldr_l   x1, handle_arch_irq
        mov     x0, sp
        irq_stack_entry
        blr     x1
        irq_stack_exit
        .endm
 
        .text
  • 将handle_arch_irq设置到x1, 而handle_arch_irq就是上一篇在gic驱动中设置的。set_handle_irq(gic_handle_irq);
  • 从进程的内核栈切换到中断栈
        .macro  irq_stack_entry
        mov     x19, sp                 // preserve the original sp
 
        /*
         * Compare sp with the base of the task stack.
         * If the top ~(THREAD_SIZE - 1) bits match, we are on a task stack,
         * and should switch to the irq stack.
         */
        ldr     x25, [tsk, TSK_STACK]
        eor     x25, x25, x19
        and     x25, x25, #~(THREAD_SIZE - 1)
        cbnz    x25, 9998f
 
        ldr_this_cpu x25, irq_stack_ptr, x26
        mov     x26, #IRQ_STACK_SIZE
        add     x26, x25, x26
 
        /* switch to the irq stack */
        mov     sp, x26
9998:
        .endm

其中irq_stack_ptr就是中断栈,每一个cpu都会存在一个中断栈的。

DEFINE_PER_CPU_ALIGNED(unsigned long [IRQ_STACK_SIZE/sizeof(long)], irq_stack);
 
static void init_irq_stacks(void)

    int cpu;
 
    for_each_possible_cpu(cpu)
        per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack, cpu);

当切换到中断栈之后,则会跳到handle_arch_irq,则就会跳到之前设置的gic_handle_irq中。

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)

    u32 irqnr;
 
    do 
        irqnr = gic_read_iar();
 
        if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) 
            int err;
 
            uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);
            if (static_branch_likely(&supports_deactivate_key))
                gic_write_eoir(irqnr);
            else
                isb();
 
            err = handle_domain_irq(gic_data.domain, irqnr, regs);           //处理中断
            if (err) 
                WARN_ONCE(true, "Unexpected interrupt received!\\n");
                if (static_branch_likely(&supports_deactivate_key)) 
                    if (irqnr < 8192)
                        gic_write_dir(irqnr);
                 else 
                    gic_write_eoir(irqnr);
                
            
            continue;
        

假设当前的中断类型是SPI,则会进入到hanle_domian_irq中去

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)

    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;
 
    irq_enter();
 
#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);
#endif
 
    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) 
        ack_bad_irq(irq);
        ret = -EINVAL;
     else 
        generic_handle_irq(irq);
    
 
    irq_exit();
    set_irq_regs(old_regs);
    return ret;

#endif
  • irq_enter则是代表进入中断上下文
  • 然后根据hwirq和domain,其中此domain是root domain,则寻找到hwirq对应的softirq
  • 如果此softirq 属于非法irq,则发回bad ack
  • 否则调用generic_handle_irq函数
  • 当处理完毕后,会退出中断上下文
int generic_handle_irq(unsigned int irq)

    struct irq_desc *desc = irq_to_desc(irq);
 
    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(desc);
    return 0;

根据softirq 获取此中断的中断描述符,调用generic_handle_irq_desc去处理中断,后面会涉及到irq domain的知识了。后面详细说

以上是关于中断处理流程梳理的主要内容,如果未能解决你的问题,请参考以下文章

中断注册函数与卸载函数

CPU中断的硬件实现原理

Linux驱动实践:一起来梳理中断的前世今生(附代码)

Linux驱动实践:一起来梳理中断的前世今生(附代码)

Linux驱动实践:一起来梳理中断的前世今生(附代码)

2.中断处理程序