linux中断子系统 - 中断及执行流程

文章系列


linux中断子系统 - 中断及执行流程
linux中断子系统 - 申请中断
linux中断子系统 - irq_desc的创建
linux中断子系统 - 中断控制器的注册

linux中断子系统系列文章计划总共由4篇文章组成,本篇会通过中断的执行流来整体介绍一下中断,并引出其他文章的内容简介,中断的代码基本在kernel/irq目录下,中断控制器的代码在drivers/irqchip目录下

内核版本linux4.6.3

1. 中断相关结构体介绍


linux中断子系统 - 中断及执行流程_第1张图片

通过参照一下ULK3的IRQ描述符图,本图描述了linux4.6.3版本中断中各个结构体所代表的对象,struct irq_desc代表的是一个中断描述符,一个中断所需要的资源都集中在这个结构体中描述,如果没有定义选项CONFIG_SPARSE_IRQ,irq_desc会在系统初始化的时候被分配到一个数组中存放,其中数组下标代表的就是virq(虚拟中断,而不是硬中断),如果定义了选项那么irq_desc会被分配到radix tree中;

struct irq_data代表的是一个具体中断控制器,包括两个结构体,分别是irq_chip和irq_domain,其中irq_chip中的每个字段是描述了具体操作中断控制器的功能,irq_domain这个比较抽象,它主要的功能是对hwirq和virq创建映射,把某些中断所具有的同样功能抽象出来,然后组成一个domain,这样就可以被其他中断控制器所用,要理解这个结构体还是要多看代码仔细体会了;

struct irqaction代表一个中断要做什么,此结构体组成一个链表,因为一个中断线上可能会挂载好几个设备,这几个设备会共用这个中断,具体是哪个设备需要判断。

2. 中断进入执行流


2.1 ARM64

本文以arm64和gic-v3中断控制器为例来进行中断执行流的介绍,我把整个执行流分成三个阶段,第一阶段和第二阶段如下图所示

![这里写图片描述](https://img-blog.csdn.net/20161023172508452)

2.1.1 第一阶段

第一阶段的执行都是用汇编写的,开始进入el1的中断向量入口,然后会调用到handle_arch_irq,此变量为全局的,会在中断控制器初始化的时候设置函数变量,此处以gic-v3为例介绍,最后会调用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;

            if (static_key_true(&supports_deactivate))
                gic_write_eoir(irqnr);

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

2.1.2 第二阶段

第二阶段代码不复杂,首先找到virq,根据virq找到irq_desc,然后调用irq_desc->handle_irq,这里handle_irq是在申请中断的时候被设置,不过不管被设置成哪个函数最后都会调用handle_irq_event,此时进入第三阶段。

2.1.3 第三阶段

linux中断子系统 - 中断及执行流程_第2张图片

此阶段是真正执行申请中断时所要执行的函数,代码如下:

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

    for_each_action_of_desc(desc, 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;
    }

    add_interrupt_randomness(irq, flags);

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

并不是说arm64一定要以此流程来执行,而是因为arm64是后来出的架构,而handle_arch_irq也是后来才出现的,所以arm64就用新的框架来实现,而不会用以前的arch_irq_handler_default来实现,这个框架在arm32中也会使用,不过是要一定条件,如下一节介绍

2.2 ARM32

![这里写图片描述](https://img-blog.csdn.net/20161023175031324) 在以前的版本中有另一种方式来实现第一阶段,不过到第二阶段的时候,就是一样的了。

3. 中断返回


看一下中断返回的汇编

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

    get_thread_info tsk
    irq_handler--------------------------------处理完,进入内核抢占

#ifdef CONFIG_PREEMPT
    ldr w24, [tsk, #TI_PREEMPT]     // get preempt count
    cbnz    w24, 1f             // preempt count != 0
    ldr x0, [tsk, #TI_FLAGS]        // get flags
    tbz x0, #TIF_NEED_RESCHED, 1f   // needs rescheduling?
    bl  el1_preempt
#ifdef CONFIG_PREEMPT
el1_preempt:
    mov x24, lr
1:  bl  preempt_schedule_irq        // irq en/disable is done inside
    ldr x0, [tsk, #TI_FLAGS]        // get new tasks TI_FLAGS
    tbnz    x0, #TIF_NEED_RESCHED, 1b   // needs rescheduling?
    ret x24
#endif

change log

时间 修改
2016.11.5 增加中断返回

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