中断产生流程

 中断产生流程

中断向量表

entry.S (arch\arm64\kernel)

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_invalid        // 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_invalid        // 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_invalid_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)

以el1_irq为例

el1_irq:
    kernel_entry 1
    enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
    bl    trace_hardirqs_off
#endif

    irq_handler

#ifdef CONFIG_PREEMPT
    ldr    w24, [tsk, #TSK_TI_PREEMPT]    // get preempt count
    cbnz    w24, 1f                // preempt count != 0
    ldr    x0, [tsk, #TSK_TI_FLAGS]    // get flags
    tbz    x0, #TIF_NEED_RESCHED, 1f    // needs rescheduling?
    bl    el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
    bl    trace_hardirqs_on
#endif
    kernel_exit 1
ENDPROC(el1_irq)

从上kernel_entry 保存现场, irq_handler是中断处理函数,kernel_exit是恢复现场

中间CONFIG_PREEMPT宏包含一段抢占调度的代码,下面介绍一下irq_handler函数

    .macro    irq_handler
    ldr_l    x1, handle_arch_irq
    mov    x0, sp
    irq_stack_entry  //栈指针切换,切换到中断栈空间
    blr    x1              //跳到handle_arch_irq函数执行
    irq_stack_exit  //栈指针切换,退出中断栈空间
    .endm

handle_arch_irq是通过set_handle_irq赋值的

路径:entry.S (kernel4.14\arch\arm64\kernel)

void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return;

    handle_arch_irq = handle_irq;
}

此set_handle_irq函数是在gic_init_bases函数中调用的 set_handle_irq(gic_handle_irq);

gic_handle_irq函数路径:Irq-gic-v3.c (kernel4.14\drivers\irqchip)

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;

            if (static_key_true(&supports_deactivate))
                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_key_true(&supports_deactivate)) {
                    if (irqnr < 8192)
                        gic_write_dir(irqnr);
                } else {
                    gic_write_eoir(irqnr);
                }
            }
            continue;
        }
        if (irqnr < 16) {//主要用于核间通信的中断
            gic_write_eoir(irqnr);
            if (static_key_true(&supports_deactivate))
                gic_write_dir(irqnr);
#ifdef CONFIG_SMP
            /*
             * Unlike GICv2, we don't need an smp_rmb() here.
             * The control dependency from gic_read_iar to
             * the ISB in gic_write_eoir is enough to ensure
             * that any shared data read by handle_IPI will
             * be read after the ACK.
             */
            handle_IPI(irqnr, regs);
#else
            WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
            continue;
        }
    } while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}

static inline int handle_domain_irq(struct irq_domain *domain,
                    unsigned int hwirq, struct pt_regs *regs)
{
    return __handle_domain_irq(domain, hwirq, true, regs);
}

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;
}‘

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;
}

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
    desc->handle_irq(desc);
}
 

desc->handle_irq通过gic_irq_domain_map初始化的,根据不同的中断号走不同的路径

gic_irq_domain_map’如下:

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                  irq_hw_number_t hw)
{
    struct irq_chip *chip = &gic_chip;

    if (static_key_true(&supports_deactivate))
        chip = &gic_eoimode1_chip;

    /* SGIs are private to the core kernel */
    if (hw < 16)
        return -EPERM;
    /* Nothing here */
    if (hw >= gic_data.irq_nr && hw < 8192)
        return -EPERM;
    /* Off limits */
    if (hw >= GIC_ID_NR)
        return -EPERM;

    /* PPIs *///PPI(Private Peripheral Interrupts)是指专用私有中断,通常用于CPU本地时钟等应用场景
    if (hw < 32) {
        irq_set_percpu_devid(irq);
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_percpu_devid_irq, NULL, NULL);
        irq_set_status_flags(irq, IRQ_NOAUTOEN);
    }
    /* SPIs */ //共享中断源允许多个处理器共享同一个中断源。SPI 中断主要用于处理外部设备引发的中断事件,如网络接口卡、硬盘控制器、串口控制器等外设的中断。
    if (hw >= 32 && hw < gic_data.irq_nr) {
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
        irq_set_probe(irq);
    }
    /* LPIs *///特定于位置的外设中断(lpi)总是基于消息的,可以来自外设,也可以来自PCle根复核。
    if (hw >= 8192 && hw < GIC_ID_NR) {
        if (!gic_dist_supports_lpis())
            return -EPERM;
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
    }

    return 0;
}

 

通过gic_irq_domain_map函数可以看出desc->handle_irq(desc);是handle_percpu_devid_irq和handle_fasteoi_irq二选一

handle_percpu_devid_irq如下:

void handle_percpu_devid_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);
    struct irqaction *action = desc->action;
    unsigned int irq = irq_desc_get_irq(desc);
    irqreturn_t res;

    /*
     * PER CPU interrupts are not serialized. Do not touch
     * desc->tot_count.
     */
    __kstat_incr_irqs_this_cpu(desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    if (likely(action)) {
        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));//此处调用的就是注册中断时传递的中断处理函数
        trace_irq_handler_exit(irq, action, res);
    } else {
        unsigned int cpu = smp_processor_id();
        bool enabled = cpumask_test_cpu(cpu, desc->percpu_enabled);

        if (enabled)
            irq_percpu_disable(desc, cpu);

        pr_err_once("Spurious%s percpu IRQ%u on CPU%u\n",
                enabled ? " and unmasked" : "", irq, cpu);
    }

    if (chip->irq_eoi)
        chip->irq_eoi(&desc->irq_data);
}

handle_fasteoi_irq如下:

void handle_fasteoi_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = desc->irq_data.chip;

    raw_spin_lock(&desc->lock);

    if (!irq_may_run(desc))
        goto out;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
        desc->istate |= IRQS_PENDING;
        mask_irq(desc);
        goto out;
    }

    kstat_incr_irqs_this_cpu(desc);
    if (desc->istate & IRQS_ONESHOT)
        mask_irq(desc);

    preflow_handler(desc);
    handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);

    raw_spin_unlock(&desc->lock);
    return;
out:
    if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
        chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);//设置中断状态
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);//清除中断状态
    return ret;
}
 

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    irqreturn_t retval;
    unsigned int flags = 0;

    retval = __handle_irq_event_percpu(desc, &flags);

    add_interrupt_randomness(desc->irq_data.irq, flags);

    if (!noirqdebug)
        note_interrupt(desc, retval);
    return retval;
}

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int irq = desc->irq_data.irq;
    struct irqaction *action;

    for_each_action_of_desc(desc, action) {//处理中断描述中的每一个action
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);//此处调用的就是注册中断时传递的中断处理函数
        trace_irq_handler_exit(irq, action, res);

        if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
                  irq, action->handler))
            local_irq_disable();

        switch (res) {
        case IRQ_WAKE_THREAD:
            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                warn_no_thread(irq, action);
                break;
            }

            __irq_wake_thread(desc, action);//此处中断线程化唤醒线程

            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            *flags |= action->flags;
            break;

        default:
            break;
        }

        retval |= res;
    }

    return retval;
}
 

handle_IPI后续再写。

你可能感兴趣的:(linux,内核,linux)