Linux 中断 —— GIC (高层中断处理)

目录

1. 跳转入口

2. 内核空间中断处理

3. 高层中断处理

3.1 基本中断流程

3.2 唤醒中断内核线程

3.3 中断上下文


在前面,系统初始化阶段 GIC (对应的表达为 irq_domain) 初始化完毕,在驱动层,将对应的中断初始化完毕,为每个需要中断的驱动分配好了 irq_desc,以及相关的结构初始化完毕,下面来看看中断处理的流程。

中断处理流程,首先需要分析 ARM 处理器底层的处理流程,这里参考 WoWo 大神的这篇文章:

《Linux kernel的中断子系统之(六):ARM中断处理过程》

 

1. 跳转入口

中断发生后,软件跳转到中断向量表开始vector_irq执行,vector_irq在结尾的时候根据中断发生点所在模式,决定跳转到__irq_usr或者__irq_svc。

vector_irq在arch/arm/kernel/entry-armv.S由宏 vector_stub 定义。

关于correction==4,需要减去4字节才是返回地址?

vector_stub宏参数correction为4,。

正在执行指令A时发生了中断,由于ARM流水线和指令预取等原因,pc指向A+8B处,那么必须等待指令A执行完毕才能处理该中断,这时PC已经更新到A+12B处。

进入中断响应前夕,pc寄存器的内容被装入lr寄存器中,lr=pc-4,即A+8B地址处。

因此返回时要pc=lr-4,才是被中断时要执行的下一条指令。所以lr要回退4B。

    .section .vectors, "ax", %progbits
__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)    pc, __vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq---------------------------------------------------------------跳转到vector_irq
    W(b)    vector_fiq
      .macro vector_stub, name, mode, correction=0------------------------------------vector_stub宏定义

    .align 5
vector_\name:
    .if \correction
    sub    lr, lr, #\correction-------------------------------------------------------correction==4解释
    .endif

    @
    @ Save r0, lr_ (parent PC) and spsr_
    @ (parent CPSR)
    @
    stmia    sp, {r0, lr}        @ save r0, lr
    mrs    lr, spsr
    str    lr, [sp, #8]        @ save spsr

    @
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs    r0, cpsr
    eor    r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)---------------------------------修改CPSR寄存器的控制域为SVC模式,为了使中断处理在SVC模式下执行。
    msr    spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @
    and    lr, lr, #0x0f--------------------------------------------------------------低4位反映了进入中断前CPU的运行模式,9为USR,3为SVC模式。
 THUMB(    adr    r0, 1f            )
 THUMB(    ldr    lr, [r0, lr, lsl #2]    )-------------------------------------------根据中断发生点所在的模式,给lr寄存器赋值,__irq_usr或者__irq_svc标签处。
    mov    r0, spk
 ARM(    ldr    lr, [pc, lr, lsl #2]    )---------------------------------------------得到的lr就是".long __irq_svc"
    movs    pc, lr            @ branch to handler in SVC mode-------------------------把lr的值赋给pc指针,跳转到__irq_usr或者__irq_svc。
ENDPROC(vector_\name)

 

2. 内核空间中断处理

__irq_svc处理发生在内核空间的中断,主要svc_entry保护中断现场;irq_handler执行中断处理;如果打开抢占功能,检查是否可以抢占;最后svc_exit执行中断退出处理:

__irq_svc:
    svc_entry
    irq_handler

#ifdef CONFIG_PREEMPT-----------------------------------------------------中断处理结束后,发生抢占的地方♥
    get_thread_info tsk
    ldr    r8, [tsk, #TI_PREEMPT]        @ get preempt count--------------获取thread_info->preempt_cpunt变量;preempt_count为0,说明可以抢占进程;preempt_count大于0,表示不能抢占。
    ldr    r0, [tsk, #TI_FLAGS]        @ get flags------------------------获取thread_info->flags变量
    teq    r8, #0                @ if preempt count != 0
    movne    r0, #0                @ force flags to 0
    tst    r0, #_TIF_NEED_RESCHED-----------------------------------------判断是否设置了_TIF_NEED_RESCHED标志位
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception
 UNWIND(.fnend        )
ENDPROC(__irq_svc)

svc_entry 将中断现场保存到内核栈中,主要是struct pt_regs中的寄存器

svc_exit   准备返回中断现场,然后通过ldmia指令从栈中恢复15个寄存器,包括pc内容,至此整个中断完成并返回

irq_handler  进入高层中断处理

 

3. 高层中断处理

3.1 基本中断流程

irq_handler  汇编宏是ARCH层和高层中断处理分割线,在这里从汇编跳转到C进行GIC相关处理。

irq_handler宏调用handle_arch_irq函数,这个函数set_handle_irq注册,GICv2 对应gic_handle_irq:

    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

还记得在 《Linux 中断 —— GIC 初始化》一章,初始化 GIC 的时候,在函数 gic_init_bases :

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
               void __iomem *dist_base, void __iomem *cpu_base,
               u32 percpu_offset, struct device_node *node)
{
...
    if (gic_nr == 0) {
...
        set_handle_irq(gic_handle_irq);
    }
...
}

这里调用了 set_handle_irq :

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

    handle_arch_irq = handle_irq;
}

所以这个 handle_arch_irq 别设置成为了 gic_handle_irq

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);---读取IAR寄存器,表示响应中断。
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;---------------GICC_IAR_INT_ID_MASK为0x3ff,即低10位,所以中断最多从0~1023。

        if (likely(irqnr > 15 && irqnr < 1021)) {
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
        if (irqnr < 16) {-------------SGI类型的中断是CPU核间通信所用,只有定义了CONFIG_SMP才有意义。
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);----直接写EOI寄存器,表示结束中断。
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);------------irqnr表示SGI中断类型
#endif
            continue;
        }
        break;
    } while (1);
}

 gic_handle_irq 对将中断分为两组:

  • SGI
  • PPI/SPI

SGI类型中断交给handle_IPI()处理;

PPI/SPI类型交给 handle_domain_irq 处理。

handle_domain_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();----------------------通过显式增加hardirq域计数,通知Linux进入中断上下文

#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已经是Linux中断号。
    }

    irq_exit();-------------------------------退出中断上下文
    set_irq_regs(old_regs);
    return ret;
}

handle_domain_irq调用__handle_domain_irq,其中lookup置为true。

irq_enter显式告诉Linux内核现在要进入中断上下文了,在处理完中断后调用irq_exit告诉Linux已经完成中断处理过程。

__handle_domain_irq 首先通过上一级传入的硬件中断号 IRQ 来调用 irq_find_mapping 来获取软件中断号,即 irq number:

unsigned int irq_find_mapping(struct irq_domain *domain,
                  irq_hw_number_t hwirq)
{
    struct irq_data *data;
...
    /* Check if the hwirq is in the linear revmap. */
    if (hwirq < domain->revmap_size)
        return domain->linear_revmap[hwirq];----------------linear_revmap[]在__irq_domain_alloc_irqs()->irq_domain_insert_irq()时赋值。
...
}

还记得么,在 《Linux 中断 —— GIC (中断号映射)》 一章,在最后,线性映射就是将映射相关信息存储在了:

domain->linear_revmap[]

这里做反向的查找。

接着,调用到了 generic_handle_irq(irq);,这里的入参,irq 就是 irq number 了:
 

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}

irq_to_desc() 根据 irq number找到对应的struct irq_desc,对应线性映射的话,其实就是数组里面,以 irq number 作为 index,在全局的 irq_desc[NR_IRQ] 找出对应的那个 irq_desc;

最后调用到了:

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

这个中断描述符的 handle_irq 函数在什么地方挂的指针呢?

Linux 中断 —— GIC (数据结构 irq_domain/irq_desc/irq_data/irq_chip)

在这篇文章中,初始化 Driver 要用到的 interrupt 信息的时候,在最后的 irq_domain_set_info 调用中,将这个 irq_desc->handle_irq 赋值成为了:

1. 对应 hwirq < 32 的情况:irq_desc->handle_irq = handle_percpu_devid_irq

2. 对应 hwirq ≥ 32 的情况:irq_desc->handle_irq = handle_fasteoi_irq

handle_fsteoi_irq处理SPI类型的中断,将主要工作交给handle_irq_event()。

void
handle_fasteoi_irq(unsigned int 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);
    kstat_incr_irqs_this_cpu(irq, desc);

    /*
     * 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))) {---如果该中断没有指定action描述符或该中断被关闭了IRQD_IRQ_DISABLED,设置该中断状态为IRQS_PENDING,且mask_irq()屏蔽该中断。
        desc->istate |= IRQS_PENDING;
        mask_irq(desc);
        goto out;
    }

    if (desc->istate & IRQS_ONESHOT)----------------------------------------如果中断是IRQS_ONESHOT,不支持中断嵌套,那么应该调用mask_irq()来屏蔽该中断源。
        mask_irq(desc);

    preflow_handler(desc);--------------------------------------------------取决于是否定义了freflow_handler()
    handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);----------------------------------------根据不同条件执行unmask_irq()解除中断屏蔽,或者执行irq_chip->irq_eoi发送EOI信号,通知GIC中断处理完毕。

    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)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;--------------------------清除IRQS_PENDING标志位
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);---------设置IRQD_IRQ_INPROGRESS标志位,表示正在处理硬件中断。
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);-------清除IRQD_IRQ_INPROGRESS标志位,表示中断处理结束。
    return ret;
}

调用到  handle_irq_event_percpu;

handle_irq_event_percpu()首先处理action->handler,有需要则唤醒中断内核线程,执行action->thread_fn

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

    do {----------------------------------------------------遍历中断描述符中的action链表,依次执行每个action元素中的primary handler回调函数action->handler。
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);---------执行struct irqaction的handler函数。
        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;
        action = action->next;
    } while (action);

    add_interrupt_randomness(irq, flags);

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

3.2 唤醒中断内核线程

__irq_wake_thread唤醒对应中断的内核线程。

void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
    /*
     * In case the thread crashed and was killed we just pretend that
     * we handled the interrupt. The hardirq handler has disabled the
     * device interrupt, so no irq storm is lurking.
     */
    if (action->thread->flags & PF_EXITING)
        return;

    /*
     * Wake up the handler thread for this action. If the
     * RUNTHREAD bit is already set, nothing to do.
     */
    if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))--------------若已经对IRQF_RUNTHREAD置位,表示已经处于唤醒中,该函数直接返回。
        return;

    desc->threads_oneshot |= action->thread_mask;--------------------thread_mask在共享中断中,每一个action有一个比特位来表示。thread_oneshot每个比特位表示正在处理的共享oneshot类型中断的中断线程。

    atomic_inc(&desc->threads_active);-------------------------------活跃中断线程计数

    wake_up_process(action->thread);---------------------------------唤醒action的thread内核线程
}

需要唤醒的内核线程是在什么时候创建的呢?

irq_thread 在中断注册的时候,如果条件满足同时创建:

irq/xx-xx内核中断线程,线程优先级是49(99-50),调度策略是 SCHED_FIFO(实时调度)

static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
...
    /*
     * Create a handler thread when a thread function is supplied
     * and the interrupt does not nest into another interrupt
     * thread.
     */
    if (new->thread_fn && !nested) {
        struct task_struct *t;
        static const struct sched_param param = {
            .sched_priority = MAX_USER_RT_PRIO/2,-------------------------------设置irq内核线程的优先级,在/proc/xxx/sched中看到的prio为MAX_RT_PRIO-1-sched_priority。
        };

        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);--------------------------------------------------创建线程名为irq/xxx-xxx的内核线程,线程执行函数是irq_thread。
...
        sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);----------------------设置进程调度策略为SCHED_FIFO。

        /*
         * We keep the reference to the task struct even if
         * the thread dies to avoid that the interrupt code
         * references an already freed task_struct.
         */
        get_task_struct(t);
        new->thread = t;-------------------------------------------------------将当前线程和irq_action关联起来

        set_bit(IRQTF_AFFINITY, &new->thread_flags);--------------------------对中断线程设置CPU亲和性
    }
...
}

irq_thread是中断线程的执行函数,在irq_wait_for_interrupt()中等待。

static int irq_thread(void *data)
{
    struct callback_head on_exit_work;
    struct irqaction *action = data;
    struct irq_desc *desc = irq_to_desc(action->irq);
    irqreturn_t (*handler_fn)(struct irq_desc *desc,
            struct irqaction *action);

    if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
                    &action->thread_flags))
        handler_fn = irq_forced_thread_fn;
    else
        handler_fn = irq_thread_fn;

    init_task_work(&on_exit_work, irq_thread_dtor);
    task_work_add(current, &on_exit_work, false);

    irq_thread_check_affinity(desc, action);

    while (!irq_wait_for_interrupt(action)) {
        irqreturn_t action_ret;

        irq_thread_check_affinity(desc, action);

        action_ret = handler_fn(desc, action);-----------执行中断内核线程函数
        if (action_ret == IRQ_HANDLED)
            atomic_inc(&desc->threads_handled);----------增加threads_handled计数

        wake_threads_waitq(desc);------------------------唤醒wait_for_threads等待队列
    }

    /*
     * This is the regular exit path. __free_irq() is stopping the
     * thread via kthread_stop() after calling
     * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
     * oneshot mask bit can be set. We cannot verify that as we
     * cannot touch the oneshot mask at this point anymore as
     * __setup_irq() might have given out currents thread_mask
     * again.
     */
    task_work_cancel(current, irq_thread_dtor);
    return 0;
}

这里进一步调用到了 irq_wait_for_interrupt。

irq_wait_for_interrupt()中判断IRQTF_RUNTHREAD标志位,没有置位则schedule()换出CPU,进行睡眠。

直到__irq_wake_thread()置位了IRQTF_RUNTHREAD,并且wake_up_process()后,irq_wait_for_interrupt()返回0。

static int irq_wait_for_interrupt(struct irqaction *action)
{
    set_current_state(TASK_INTERRUPTIBLE);

    while (!kthread_should_stop()) {

        if (test_and_clear_bit(IRQTF_RUNTHREAD,
                       &action->thread_flags)) {------------判断thread_flags是否设置IRQTF_RUNTHREAD标志位,如果设置则设置当前状态TASK_RUNNING并返回0。此处和__irq_wake_thread中设置IRQTF_RUNTHREAD对应。
            __set_current_state(TASK_RUNNING);
            return 0;
        }
        schedule();-----------------------------------------换出CPU,在此等待睡眠
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return -1;
}

当唤醒后,执行 irq_thread_fn:

static irqreturn_t irq_thread_fn(struct irq_desc *desc,
        struct irqaction *action)
{
    irqreturn_t ret;

    ret = action->thread_fn(action->irq, action->dev_id);---执行中断内核线程函数,为request_threaded_irq注册中断参数thread_fn。
    irq_finalize_oneshot(desc, action);---------------------针对oneshot类型中断收尾处理,主要是去屏蔽中断。
    return ret;
}

这里调用了 action->thread_fn,也就是中断注册的时候,传入的 thread_fn 函数。

irq_finalize_oneshot()对ontshot类型的中断进行收尾操作:

static void irq_finalize_oneshot(struct irq_desc *desc,
                 struct irqaction *action)
{
    if (!(desc->istate & IRQS_ONESHOT) ||
        action->handler == irq_forced_secondary_handler)
        return;
again:
    chip_bus_lock(desc);
    raw_spin_lock_irq(&desc->lock);

    /*
     * Implausible though it may be we need to protect us against
     * the following scenario:
     *
     * The thread is faster done than the hard interrupt handler
     * on the other CPU. If we unmask the irq line then the
     * interrupt can come in again and masks the line, leaves due
     * to IRQS_INPROGRESS and the irq line is masked forever.
     *
     * This also serializes the state of shared oneshot handlers
     * versus "desc->threads_onehsot |= action->thread_mask;" in
     * irq_wake_thread(). See the comment there which explains the
     * serialization.
     */
    if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {-----------必须等待硬件中断处理程序清除IRQD_IRQ_INPROGRESS标志位,见handle_irq_event()。因为该标志位表示硬件中断处理程序正在处理硬件中断,直到硬件中断处理完毕才会清除该标志。
        raw_spin_unlock_irq(&desc->lock);
        chip_bus_sync_unlock(desc);
        cpu_relax();
        goto again;
    }

    /*
     * Now check again, whether the thread should run. Otherwise
     * we would clear the threads_oneshot bit of this thread which
     * was just set.
     */
    if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
        goto out_unlock;

    desc->threads_oneshot &= ~action->thread_mask;

    if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
        irqd_irq_masked(&desc->irq_data))
        unmask_threaded_irq(desc);----------------------------------执行EOI或者去中断屏蔽。

out_unlock:
    raw_spin_unlock_irq(&desc->lock);
    chip_bus_sync_unlock(desc);
}

至此一个中断的 handler 执行完毕。

需要注意的是,在中断进入的时候调用了 irq_enter 退出的时候调用了 irq_exit:

/*
 * Enter an interrupt context.
 */
void irq_enter(void)
{
    rcu_irq_enter();
    if (is_idle_task(current) && !in_interrupt()) {
        /*
         * Prevent raise_softirq from needlessly waking up ksoftirqd
         * here, as softirq will be serviced on return from interrupt.
         */
        local_bh_disable();
        tick_irq_enter();
        _local_bh_enable();
    }

    __irq_enter();---------------------------------------------显式增加hardirq域计数
}

#define __irq_enter()                    \
    do {                        \
        account_irq_enter_time(current);    \
        preempt_count_add(HARDIRQ_OFFSET);    \----------------显式增加hardirq域计数
        trace_hardirq_enter();            \
    } while (0)


void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
    local_irq_disable();
#else
    WARN_ON_ONCE(!irqs_disabled());
#endif

    account_irq_exit_time(current);
    preempt_count_sub(HARDIRQ_OFFSET);---------------------------显式减少hardirq域计数
    if (!in_interrupt() && local_softirq_pending())--------------当前不处于中断上下文,且有pending的softirq,进行softirq处理。
        invoke_softirq();

    tick_irq_exit();
    rcu_irq_exit();
    trace_hardirq_exit(); /* must be last! */
}

这里有一些判断中断上下文的标志。

 

3.3 中断上下文

判断当前进程是处于中断上下文,还是进程上下文依赖于preempt_count,这个变量在struct thread_info中。

preempt_count计数共32bit,从低到高依次是:

#define PREEMPT_BITS	8
#define SOFTIRQ_BITS	8
#define HARDIRQ_BITS	4
#define NMI_BITS	1

内核提供了一些函数来判断中断上下文:

#define hardirq_count()    (preempt_count() & HARDIRQ_MASK)-----------------硬件中断计数
#define softirq_count()    (preempt_count() & SOFTIRQ_MASK)-----------------软中断计数
#define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \----包括NMI、硬中断、软中断三者计数
                 | NMI_MASK))

/*
 * Are we doing bottom half or hardware interrupt processing?
 *
 * in_irq()       - We're in (hard) IRQ context
 * in_softirq()   - We have BH disabled, or are processing softirqs
 * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
 * in_serving_softirq() - We're in softirq context
 * in_nmi()       - We're in NMI context
 * in_task()      - We're in task context
 *
 * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
 *       should not be used in new code.
 */
#define in_irq()        (hardirq_count())----------------------------判断是否正在硬件中断上下文
#define in_softirq()        (softirq_count())------------------------判断是否正在处理软中断或者禁止BH。
#define in_interrupt()        (irq_count())--------------------------判断是否处于NMI、硬中断、软中断三者之一或者兼有上下文
#define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)---判断是否处于软中断上下文。
#define in_nmi()        (preempt_count() & NMI_MASK)-----------------判断是否处于NMI上下文
#define in_task()        (!(preempt_count() & \
                   (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))------判断是否处于进程上下文

通过原子的 add 和 sub,来标志当前进入的状态,在通过 int_xxx() 来获取当前的是否处于中断上下文。

整个调用流程:

irq_handler()

  -->handle_arch_irq()-->gic_handle_irq()

    -->handle_domain_irq()-->__handle_domain_irq()-------------读取IAR寄存器,响应中断,获取硬件中断号

      -->irq_find_mapping()------------------------------------------------将硬件中断号转变成Linux中断号

      -->generic_handle_irq()---------------------------------------------之后的操作都是Linux中断号

        -->handle_percpu_devid_irq()-----------------------------------SGI/PPI类型中断处理

        -->handle_fasteoi_irq()--------------------------------------------SPI类型中断处理

          -->handle_irq_event()-->handle_irq_event_percpu()------执行中断处理核心函数

            -->action->handler-----------------------------------------------执行 primary handler。

            -->__irq_wake_thread()----------------------------------------根据需要唤醒中断内核线程

在 wake up 后:

irq_thread()

  -->irq_thread_fn()-------------------------------------------------等待条件满足并执行

      -->action->thread_fn()----------------------------------------注册中断时刻传入的执行函数

 

参考:

https://www.cnblogs.com/arnoldlu/p/8659981.html

http://www.wowotech.net/irq_subsystem/irq_handler.html

你可能感兴趣的:(Linux,内核中断处理)