linux驱动之中断

一、前言

笔者一直对中断只有一个简单粗浅的认识,仅仅停留在处理外部事件或者异步事件上。所以对于中断的认识不够清晰。但最近系统地对中断的硬件及软件做了一个 梳理 后,对中断的认识提升了不少,而这也有助于理解linux驱动中的中断处理,提高驱动编程能力。那本文笔者就带着大家同时在 软件和硬件 方面从头说起,需要注意的是,本文着重梳理思路,不会事无巨细的讲解,所以还需要各位读者在 理清流程后自行配合阅读代码理解,毕竟通过自己学习的才是自己的。
PS:本文需要一定的ARM架构知识,请读者们先提前了解ARM的7种模式及其寄存器

二、异常

2.1 异常与中断

异常中断 这 2 个词相信在各位学习Linux的读者严重不是很陌生。这 2 者在笔者理解来是属于 CPU 的概念,每个 CPU 都会发生 异常中断

  • 异常:在《ARM嵌入式系统开发》中的定义是 需要中止指令正常执行的任何情形。而这样的情形在 ARM 架构中一共有 7 种,分别是 复位中断请求(irq)快速中断请求(fiq)软件中断(swi/svc)数据访问中止预取指中止未定义指令。当 CPU 触发异常时,对自行切换到每种异常相应的 模式,然后跳到 异常向量表 去执行我们相应的异常处理。
  • 中断:我们可以在上面中看到,中断 只是 异常 的一种情况,它可以由 软件硬件 产生,同理的,它也会跳到 异常向量表 中的地方去执行我们的中断处理,只是这个中断处理不像沃我们理解中的 中断处理函数,它处理的比这复杂得多。

2.2 异常向量表

在初步了解完 异常中断 后,我们先别急着去看 中断,我们先看看 异常向量表,这个异常向量表与我们执行中断的时候绕不过去的一个环节。下面我们就来看一看它是怎么样的。
异常向量表 是指 异常发生时,ARM内核跳转地址组成的表,从这个定义中就可以知道,这个表是可以又我们软件指定,而这个 跳转地址 其实也就是我们的 异常处理函数 的地址。
那么在这里可能有几个疑问 异常向量表的跳转地址是多少如何跳转到对应异常的处理函数

2.2.1 异常向量表地址

首先,在 ARM架构 中,异常向量表 是有固定的地址的,每当 CPU 发生异常时,都会自动的跳转到该地址,我们把这个地址简称为 异常地址异常地址 有 2 种模式

  • 低地址模式:在该模式下,异常向量表 的地址为 0x00000000
  • 高地址模式:在该模式下,异常向量表 的地址为 0xffff0000
    模式的设置可以通过 CP15协处理 来设置,相关知识请各位读者自行了解,这里先不多说

2.2.2 异常向量表跳转

我们上面说了异常有多个, 每个异常都有自己的对应的处理函数。而 ARM 的异常向量表跳转地址如下图所示,下图以低地址模式为例。可以看到,每一个 异常 都有自己的跳转地址,而在 CPU 触发异常后,会根据相应的异常而跳转到不同的地址,从而执行不同异常处理函数

异常向量便宜.png

2.2.3 异常向量表代码简析

我们来看看 异常向量表 的初始化。我们先看看代码调用关系视图,从宏观理解代码的调用关系

start_kernel(main.c)
  ->setup_arch(setup.c)
    ->paging_init(mmu.c)
      ->devicemap_init(mmu.c)
        ->early_trap_init(traps.c)

下面我们根据调用关系逐步看一下代码的初始化过程

/* 异常向量表的处理过程 */
void __init setup_arch(char **cmdline_p)
{
    const struct machine_desc *mdesc;
    mdesc = setup_machine_fdt(__atags_pointer);
    ...
    paging_init(mdesc)
    ...
}
void __init paging_init(const struct machine_desc *mdesc)
{
    ...
    devicemaps_init(mdesc);
    ...
}
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
    ...
    void *vectors;
    vectors = early_alloc(PAGE_SIZE * 2);
    early_trap_init(vectors);
    ...
    /*
     * Create a mapping for the machine vectors at the high-vectors
     * location (0xffff0000).  If we aren't using high-vectors, also
     * create a mapping at the low-vectors virtual address.
     */
    map.pfn = __phys_to_pfn(virt_to_phys(vectors));
    map.virtual = 0xffff0000;
    map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
    map.type = MT_HIGH_VECTORS;
#else
    map.type = MT_LOW_VECTORS;
#endif
    create_mapping(&map);

    if (!vectors_high()) {
        map.virtual = 0;
        map.length = PAGE_SIZE * 2;
        map.type = MT_LOW_VECTORS;
        create_mapping(&map);
    }

    /* Now create a kernel read-only mapping */
    map.pfn += 1;
    map.virtual = 0xffff0000 + PAGE_SIZE;
    map.length = PAGE_SIZE;
    map.type = MT_LOW_VECTORS;
    create_mapping(&map);
}
void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
    unsigned long vectors = (unsigned long)vectors_base;
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    unsigned i;

    vectors_page = vectors_base;
    ....
    /*
     * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
     * into the vector page, mapped at 0xffff0000, and ensure these
     * are visible to the instruction stream.
     */
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

    kuser_init(vectors_base);

    flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M */
    /*
     * on V7-M there is no need to copy the vector table to a dedicated
     * memory area. The address is configurable and so a table in the kernel
     * image can be used.
     */
#endif
}

/* 异常向量表在代码中的位置:arch/arm/kernel/entry_armv.S */
.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)  pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq

    .data
    .align  2

    .globl  cr_alignment
cr_alignment:
    .space  4

#ifdef CONFIG_MULTI_IRQ_HANDLER
    .globl  handle_arch_irq
handle_arch_irq:
    .space  4
#endif
/* 异常向量表地址的设置代码 */
/* 代码位于proc-v7.S */
__errata_finish:
    adr r3, v7_crval
    ldmia   r3, {r3, r6}
 ARM_BE8(orr    r6, r6, #1 << 25)       @ big-endian page tables
#ifdef CONFIG_SWP_EMULATE
    orr     r3, r3, #(1 << 10)              @ set SW bit in "clear"
    bic     r6, r6, #(1 << 10)              @ clear it in "mmuset"
#endif
    mrc p15, 0, r0, c1, c0, 0       @ read control register
    bic r0, r0, r3          @ clear bits them
    orr r0, r0, r6          @ set them

/* 代码位于proc-v7-2level.S */
v7_crval:
    crval   clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c

在初始化 异常向量表 的过程中还有很多其他机制的初始化,为了简化思路,笔者将其他的无关代码删去。
我们直接看到 devicemaps_init ,在这里我们看到它分配了 2 个内存页,并把地址赋值给 vectors,然后接着在后面我可以看到对 vectors 进行内存映射的操作,根据内核的宏定义来将 vectors 映射在 0xffff00000x00000000,一般来说是 高地址模式 ,这就是我们说的 异常向量表 内存地址。对的,其实触发异常后我们的pc 指针是跳到 虚拟内存地址0xffff0000

接着我们看看 early_trap_init, 很明显我们在这里可以看到有一个拷贝过程,而这个过程是将 __vectors_start__vectors_end 之间的内容拷贝到 vectors 所指向的内存,其实读者们都想到了,__vectors_start就是 异常向量表 的地址。那么到了这里,我们就知道 异常向量表 的映射问题。

再接着,我们来看看异常向量表长什么样,第二个代码段就给出了 __vectors_start 所在位置的 异常向量表 ,而我们也可以看到它也是按照 CPU 的异常向量表的排布编写的,其中 vector_irqvector_fiq 就是我们今天要说的 中断

打破砂锅问到底,最后我们看看代码中是在哪里设置 向量表地址模式。这里要补充一个知识,向量表的地址模式是由协处理CP15中的决定的 ,有了这个条件,我们就可以看看代码是怎么写的。在代码段 __errata_finish 中会调用 v7_crval,而 v7_crval 则声明了 3 个变量,这 3 个变量就是要用于配置 MMU 的。按照笔者的理解,代码是大意为:将 v7_crval 声明的 3 个变量装载到 r3 指向的地址中,然后再将它们装载到 r3-r6 这几个寄存器,其中 r3 的值就是高低地址模式的掩码。

到了这里,我们就可以解决我们的主要问题了。以上没有很详尽的交代所有的细节,因为牵涉到的知识点比较多,笔者主要是梳理思路,以在学习的过程中有迹可循,在结合其余的知识就可以比较的理解代码。

三、通用中断控制器GIC

上面我们大致讲了一下 异常和中断 是为了让大家在总体上对于 CPU 如何引发中断并进入中断处理函数有个大致的了解。那么了解到这些仅仅是不够,要整体的了解 中断,我们还需要懂点 ARM架构 的知识。同理,GIC 是一个比较复杂的硬件,本文主要讲解常用概念,对于细节不做过多解释

3.1 GIC简介

GIC 的全称是 generate interrupt controller,即 通用中断控制器。它是由 ARM公司 开发的一个 中断硬件IP,主要用于管理外设中断。
中断为什么需要管理呢?要解决这个问题,我们需要知道 ARM核CPU 的一些小知识。在我们的 ARM核 上,其实只有 2根 中断线,也就是 IRQ(普通中断)FIQ(快速中断),我们一个SoC 有许多许多个片上外设,这些片上外设如串口UARTI2C总线 等等都需要使用到中断,但是我们只有 2根 中断线。CPU 要如何才能接收各种各样的外设中断呢?那么这个工作就交给我们的 GIC ,它负责连接到各个外设,接收各个外设的中断,并对这个中断进行仲裁,优先级高的中断的信息能够通过CPU的中断线发送到CPU上,从而引发CPU中断。如下图所示

GIC与CPU

GICCPU 的交互是通过访问对方寄存器,获取对相应的信息,如中断状态或者上报中断信息等,从而实现信息交互的功能,如下图所示:

GIC架构图

3.2 GIC及中断术语

3.2.1 GIC组成

我们从上图可以看到,GIC 主要由 DistributorCPU Interface 这 2 个模块组成

Distributor 的作用是用于中断路由,即对中断进行配置及对中断进行仲裁,其主要工作如下:

  • 使能中断
  • 中断优先级配置
  • 中断触发方式配置
  • 中断CPU路由,即对于多核 CPU,决定中断发往哪个 CPU
  • 记录每个中断的状态,即是否到来,是否处理中,是否处理完,是否在等待发送状态等等。

CPUInterfac作用是与 CPU 进行中断信息的交互,其主要工作是:

  • 使能和发送一个具体的中断信号到特定对应的CPU上,
  • 中断状态确认,包括中断已经被CPU接受,处理,以及处理完成。
  • 中断仲裁。即根据中断优先级发给不同的CPU,或者进行中断抢占等等

中断信号的传送过程为:

  1. 中断信号先到 distributor,
  2. 根据该中断设定的目标CPU,送到CPU对应的 CPU Interface
  3. CPU Interface 进行中断仲裁。具体流程为判断中断是否优先级足够高,是否可以抢断或者打断当前的中断处理。如果可以,那么 CPU Interface 就发送一个 中断物理信号 到CPU的 中断线(IRQ或FIQ) 上。CPU感知到中断信号,从而转到中断模式进行处理。

3.2.2 中断类型

  • SPI: shared peripheral interrupt , 即共享中断,是常见的外部设备中断。共享的意思是说可以多个Cpu或者说Core处理,不限定特定的Cpu。一般定义的硬件中断号范围 31~1019。共享中断的数量可以通过对 GIC 进行配置来设定
  • PPI: private peripheral interrupt,即私有中断。此类中断产生的中断信号只发送给这个特定的cpu进行处理。一般定义的硬件中断号范围 16~31
  • SGI: software generatedinterrupt ,即软件中断,此类中断一般用于多核 CPU 之间的通信,是软件出发产生的中断,中断号范围 0~15

设备树节点的中断号属性 一般要减去 32,笔者的理解是:因为前32个中断不是共享中断,而一般外设中断都是共享中断

3.2.3 中断状态

  • Inactive:当GIC上配置的中断,配置好之后,没有其对应的中断到来。或者正在处理的中断已经结束,并且 CPU 操作了 GICC_EOIR 寄存器(该过程叫中断完成),那么此时中断处于 inactive 状态
  • Pending:如果中断信号到来,GIC获取到了,这个时候要经过一系列的的判断,然后送给对应的CPU来处理。在CPU确认该中断并处理之前,中断将处理 pending 状态
  • active:当 CPU 接收到中断后,并且读取到 GICD_IAR 寄存器中的中断号(该过程叫中断认可),那么中断会进入 active 状态。
  • pending and active:当中断处于 pendingactive 状态时,又产生了一个 同样的 中断信号,此时会进入该状态

下面是 giv-v2 的中断状态机,上面的描述是相对简单一点的,有兴趣的朋友请阅读手册了解

中断状态机.png

3.2.4 中断流程

  1. GIC 决定每个中断的使能状态。不使能的中断,是不能发送中断信号到 CPU
  2. 如果某个中断的中断源有效,GIC将该中断的状态设置为 pending状态 ,然后判断该中断的 目标CPU
  3. 对于每一个 CPUGIC 将当前处于 pending状态 的优先级最高的中断,发送给对应CPUcpu interfacecpu interface 接收 GIC 发送的 中断请求,判断优先级是否满足要求,如果满足,就将中断通过 nFIQnIRQ管脚,发送给 CPU
  4. CPU 响应该中断,通过读取 GICC_IAR寄存器,来认可该中断。读取该寄存器,如果是软中断,返回源处理器ID,否则返回中断号。
  5. CPU 认可该中断后,GIC 将该中断的状态,修改为 active状态
  6. CPU 完成该中断后,通过写 EOIR(end of interrupt register)来实现优先级重置,再通过写 GICC_DIR寄存器,来无效该中断

当然了,GIC 的功能不只上面所说,它还包括了 中断优先级设置中断目标CPU设置中断组设置 等等功能,这些功能的实现都是通过操作 GIC 的寄存器来实现。对于 GIC 有兴趣的同学可以找到官方文档进行阅读

  • 《GIC-400 Generic Interrupt Controller Technical Reference Manual》
  • 《Generic Interrupt Controller Architecture version 2.0 Architecture Specification》

四、中断

前面的预备知识都讲完了,本节我们说一下中断在软件的描述。主要讲述在linux中如何描述中断,包括如何将中断分类,如何组织中断数据结构,中断子系统的主要数据结构,中断处理流程等。

4.1 中断主要数据结构

4.1.1 中断描述符

对于每一个外设的 IRQ 都用 中断描述符struct irq_desc 来描述。当发生中断后,首先获取触发中断的硬件中断号HW interupt ID,然后通过 irq domain 翻译成 中断号IRQ number,然后通过IRQ number就可以获取对应的中断描述符。再调用中断描述符中的highlevel irq-events handler来进行中断处理就OK了。

中断描述符

因为有很多个中断,所以 中断描述符struct irq_desc 也有很多个,那么就需要一种方法来组织和存储这些中断描述符,而存储的数据结构我们暂称之为中断描述符DB(上图中红色框图内)

中断描述符的结构体成员如下:

struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data     irq_data;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t  handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t   preflow_handler;
#endif
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    unsigned int        depth;      /* nested irq disables */
    unsigned int        wake_depth; /* nested wake enables */
    unsigned int        irq_count;  /* For detecting broken IRQs */
    unsigned long       last_unhandled; /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;
    atomic_t        threads_handled;
    int         threads_handled_last;
    raw_spinlock_t      lock;
    struct cpumask      *percpu_enabled;
    const struct cpumask    *percpu_affinity;
#ifdef CONFIG_SMP
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t       pending_mask;
#endif
#endif
    unsigned long       threads_oneshot;
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
    unsigned int        nr_actions;
    unsigned int        no_suspend_depth;
    unsigned int        cond_suspend_depth;
    unsigned int        force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
    struct dentry       *debugfs_file;
#endif
#ifdef CONFIG_SPARSE_IRQ
    struct rcu_head     rcu;
    struct kobject      kobj;
#endif
    struct mutex        request_mutex;
    int         parent_irq;
    struct module       *owner;
    const char      *name;
} ____cacheline_internodealigned_in_smp;

struct irqaction {
    irq_handler_t       handler;
    void            *dev_id;
    void __percpu       *percpu_dev_id;
    struct irqaction    *next;
    irq_handler_t       thread_fn;
    struct task_struct  *thread;
    struct irqaction    *secondary;
    unsigned int        irq;
    unsigned int        flags;
    unsigned long       thread_flags;
    unsigned long       thread_mask;
    const char      *name;
    struct proc_dir_entry   *dir;
} ____cacheline_internodealigned_in_smp;

笔者无法对 中断描述符 的每一个成员都了如指掌,但我们需要关注其中的 action 成员,它就是我们中断注册的处理函数。该成员是一个指针,且 struct irqaction 中有一个成员是 next。很明显,这里的可以形成一个链表,我们知道驱动有共享中断,我们可以在不同驱动中往同一个中断号注册不同的处理函数,那么很明显,所有注册的处理函数都在这里形成一个链表。当中断发生时将遍历链表上的所有处理函数。

在上面我们讲到,中断发生后有一个从 HW interupt IDIRQ number 的翻译过程,既然要 “翻译”,那么就存在映射的过程,将每个中断的 HW interupt ID 一 一映射到 IRQ number 。而映射的方法则取决于 中断描述符(struct irq_desc) 的组织方式。

在 linux 中,有 2 种组织 中断描述符(struct irq_desc) 的数据结构,分别是:

  • 线性数组
  • 基数树

他们的区别在于 映射方式不同及存储结构不同,在这里要说明一点, 基于 基数树 而组织的中断描述符是可以动态分配的, 而 线性数组 的中断描述符是一块静态内存,常驻于内中在,可见该方法是一种简答的线性映射,将IRQ number作为下标直接获取中断描述符。它定义在文件 kernel/irqdesc.c 中,代码如下:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq = handle_bad_irq,
        .depth      = 1,
        .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

根据组织方式的不同,中断描述符的函数和数据结构也不同,当然了,函数命名是一样的,只是实现内容不一样。可以通过定义宏 CONFIG_SPARSE_IRQ 来切换组织方式

本文以线性数组为例进行讲解,有兴趣的小伙伴可以阅读代码了解基于基数树的中断代码

4.1.2 中断域

  • IRQ number:CPU 需要为每一个外设中断编号,我们称之 IRQ Number,它是一个 虚拟的 interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
  • HW interrupt ID:对于 GIC 而言,它收集了多个外设的中断请求并向上传递。因此,GIC 需要对外设中断进行编码。GIC 用 HW interrupt ID 来标识外设的中断。

那么在中断发生的时候,我们从 GIC 的寄存器中读到 硬件中断号HW interrupt ID 后,我们需要将其翻译成 CPU识别的 中断号IRQ Number,而这个工作则交由 中断域struct irq_domain 来完成。
每一个 GIC 都有对应的中断域,这其中涉及到 GIC 级联,对 GIC 级联感兴趣的读者们请自行查阅参考附录中的资料。

struct irq_domain {
    struct list_head link;
    const char *name;
    const struct irq_domain_ops *ops;
    void *host_data;
    unsigned int flags;
    unsigned int mapcount;

    /* Optional data */
    struct fwnode_handle *fwnode;
    enum irq_domain_bus_token bus_token;
    struct irq_domain_chip_generic *gc;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
    struct dentry       *debugfs_file;
#endif

    /* reverse map data. The linear map gets appended to the irq_domain */
    irq_hw_number_t hwirq_max;
    unsigned int revmap_direct_max_irq;
    unsigned int revmap_size;
    struct radix_tree_root revmap_tree;
    unsigned int linear_revmap[];
};

在本文中只讲述 线性映射,下面是几个 struct irq_domain 的成员讲解:

  1. linear_revmap 保存了一个线性的 lookup table(查找表),其下标是 HW interrupt ID,查找表中保存了IRQ number值
  2. revmap_size 等于 线性lookup table 的大小。
  3. hwirq_max 保存了最大的 HW interrupt ID
struct irq_domain_ops {
    int (*match)(struct irq_domain *d, struct device_node *node,
             enum irq_domain_bus_token bus_token);
    int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
              enum irq_domain_bus_token bus_token);
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
    void (*unmap)(struct irq_domain *d, unsigned int virq);
    int (*xlate)(struct irq_domain *d, struct device_node *node,
             const u32 *intspec, unsigned int intsize,
             unsigned long *out_hwirq, unsigned int *out_type);

#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
    /* extended V2 interfaces to support hierarchy irq_domains */
    int (*alloc)(struct irq_domain *d, unsigned int virq,
             unsigned int nr_irqs, void *arg);
    void (*free)(struct irq_domain *d, unsigned int virq,
             unsigned int nr_irqs);
    void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
    void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
    int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
             unsigned long *out_hwirq, unsigned int *out_type);
#endif
};
  • xlate:该回调函数函数负责将指定的设备树中的中断节点中的若干个中断属性翻译成硬件中断号HW interrupt IDtrigger类型
  • map:该回调函数函数用于建立中断号的映射,建立映射时不仅是创建 HW interrupt IDIRQ number 的关系,还需要进行以下三个步骤:
    1.设定该IRQ number对应的中断描述符(struct irq_desc)的 irq_chip
    2.设定该IRQ number对应的中断描述符的highlevel irq-events handler
    3.设定该IRQ number对应的中断描述符的irq_chip_data
  • unmap:该回调函数函数用于消除中断号的映射
  • match:判断一个指定的设备树中的GIC节点参数是否和一个中断域irq_domain匹配

4.1.3 GIC描述结构体

struct gic_chip_data 是 GIC 的软件抽象,它描述了 GIC 的软件信息,结构体代码如下:

struct gic_chip_data {
    struct irq_chip chip;
    union gic_base dist_base;
    union gic_base cpu_base;
    void __iomem *raw_dist_base;
    void __iomem *raw_cpu_base;
    u32 percpu_offset;
#if defined(CONFIG_CPU_PM) || defined(CONFIG_ARM_GIC_PM)
    u32 saved_spi_enable[DIV_ROUND_UP(1020, 32)];
    u32 saved_spi_active[DIV_ROUND_UP(1020, 32)];
    u32 saved_spi_conf[DIV_ROUND_UP(1020, 16)];
    u32 saved_spi_target[DIV_ROUND_UP(1020, 4)];
    u32 __percpu *saved_ppi_enable;
    u32 __percpu *saved_ppi_active;
    u32 __percpu *saved_ppi_conf;
#endif
    struct irq_domain *domain;
    unsigned int gic_irqs;
#ifdef CONFIG_GIC_NON_BANKED
    void __iomem *(*get_base)(union gic_base *);
#endif
};
  • chip:GIC操作函数集,用于操作 GIC 以实现中断设置功能
  • dist_base:disstributor的基地址
  • cpu_base:cpu interface的基地址
  • domain:该 GIC 对应的中断域
struct irq_chip {
    struct device   *parent_device;
    const char  *name;
    unsigned int    (*irq_startup)(struct irq_data *data);
    void        (*irq_shutdown)(struct irq_data *data);
    void        (*irq_enable)(struct irq_data *data);
    void        (*irq_disable)(struct irq_data *data);

    void        (*irq_ack)(struct irq_data *data);
    void        (*irq_mask)(struct irq_data *data);
    void        (*irq_mask_ack)(struct irq_data *data);
    void        (*irq_unmask)(struct irq_data *data);
    void        (*irq_eoi)(struct irq_data *data);

    int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
    int     (*irq_retrigger)(struct irq_data *data);
    int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
    int     (*irq_set_wake)(struct irq_data *data, unsigned int on);

    void        (*irq_bus_lock)(struct irq_data *data);
    void        (*irq_bus_sync_unlock)(struct irq_data *data);

    void        (*irq_cpu_online)(struct irq_data *data);
    void        (*irq_cpu_offline)(struct irq_data *data);

    void        (*irq_suspend)(struct irq_data *data);
    void        (*irq_resume)(struct irq_data *data);
    void        (*irq_pm_shutdown)(struct irq_data *data);

    void        (*irq_calc_mask)(struct irq_data *data);

    void        (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
    int     (*irq_request_resources)(struct irq_data *data);
    void        (*irq_release_resources)(struct irq_data *data);

    void        (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
    void        (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

    int     (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
    int     (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

    int     (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

    void        (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
    void        (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

    unsigned long   flags;
};

4.2 中断初始化

中断的初始化主要函数是 early_irq_init()init_IRQ() ,他们都分别在 start_kernel 这个函数中调用

4.2.1 early_irq_init

int __init early_irq_init(void)
{
    int count, i, node = first_online_node;
    struct irq_desc *desc;

    init_irq_default_affinity();

    printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);

    desc = irq_desc;
    count = ARRAY_SIZE(irq_desc);

    for (i = 0; i < count; i++) {
        desc[i].kstat_irqs = alloc_percpu(unsigned int);
        alloc_masks(&desc[i], node);
        raw_spin_lock_init(&desc[i].lock);
        lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
        desc_set_defaults(i, &desc[i], node, NULL, NULL);
    }
    return arch_early_irq_init();
}
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
                  const struct cpumask *affinity, struct module *owner)
{
    int cpu;

    desc->irq_common_data.handler_data = NULL;
    desc->irq_common_data.msi_desc = NULL;

    desc->irq_data.common = &desc->irq_common_data;
    desc->irq_data.irq = irq;
    desc->irq_data.chip = &no_irq_chip;
    desc->irq_data.chip_data = NULL;
    irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
    irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
    irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
    desc->handle_irq = handle_bad_irq;
    desc->depth = 1;
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;
    desc->name = NULL;
    desc->owner = owner;
    ...
}

该函数比较简单,将 线性数组描述符 的地址赋值给变量,然后进行一次遍历,在遍历中对描述符中的自旋锁进行初始化等一系列操作,其中 desc_set_defaults 函数是对中断描述符中的成员进行值的初始化。总结起来就是初始化所有的 中段描述符。而函数 arch_early_irq_init 按照笔者的在 ARM架构 中则没有实现。

4.2.2 init_IRQ

init_IRQ 函数的调用层次比较多一些,按照笔者自己的理解整理如下,其中 gic_of_init 就是我们最终调用的初始化 gic 的代码

->init_IRQ
  ->irqchip_init
    ->of_irq_init
      ->gic_of_init
void __init init_IRQ(void)
{
    int ret;

    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
        irqchip_init();
    else
        machine_desc->init_irq();  
    ...
}
void __init irqchip_init(void)
{
    of_irq_init(__irqchip_of_table);
    ...
}
void __init of_irq_init(const struct of_device_id *matches)
{
    const struct of_device_id *match;
    struct device_node *np, *parent = NULL;
    struct of_intc_desc *desc, *temp_desc;
    struct list_head intc_desc_list, intc_parent_list;

    INIT_LIST_HEAD(&intc_desc_list);
    INIT_LIST_HEAD(&intc_parent_list);

    for_each_matching_node_and_match(np, matches, &match) {
        if (!of_property_read_bool(np, "interrupt-controller") ||
                !of_device_is_available(np))
            continue;
        ...

        /*
         * Here, we allocate and populate an of_intc_desc with the node
         * pointer, interrupt-parent device_node etc.
         */
        desc = kzalloc(sizeof(*desc), GFP_KERNEL);
        desc->irq_init_cb = match->data;
        desc->dev = of_node_get(np);
        ...
    }

    /*
     * The root irq controller is the one without an interrupt-parent.
     * That one goes first, followed by the controllers that reference it,
     * followed by the ones that reference the 2nd level controllers, etc.
     */
    while (!list_empty(&intc_desc_list)) {
        /*
         * Process all controllers with the current 'parent'.
         * First pass will be looking for NULL as the parent.
         * The assumption is that NULL parent means a root controller.
         */
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
            int ret;
            ...
            ret = desc->irq_init_cb(desc->dev,
                        desc->interrupt_parent);
            ...
        }
    }
}

IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
/* 设备树 */
gic: interrupt-controller@01c81000 {
        compatible = "arm,cortex-a7-gic", "arm,cortex-a15-gic";
        reg = <0x01c81000 0x1000>,
              <0x01c82000 0x1000>,
              <0x01c84000 0x2000>,
              <0x01c86000 0x2000>;
        interrupt-controller;
        #interrupt-cells = <3>;
        interrupts = ;
};

按照老规矩,笔者删减了一些代码,列出笔者认为比较重要的代码进行说明。其余代码各位读者可以按照自己的理解去阅读,下面按照函数调用步骤逐步说明

  1. of_irq_init
    该函数遍历设备树上的所有设备树节点,如果找到有 interrupt-controller 属性的节点,则开辟一下 struct of_intc_desc 结构体,并为这个结构体赋值,其中需要注意的是 irq_init_cb 成员,该成员的值是 match->data 赋予的,很明显这是一个初始化对调,按照笔者的理解,它就是初始化 interrupt-controller 的回调,这里需要说明一点。不是所有的 interrupt-controller 都是 GIC。按照笔者的理解,GIC 是一种 interrupt-controller。我们往下看会看到调用了 desc->irq_init_cb(desc->dev,desc->interrupt_parent) 。那么这个回调是在哪里呢?下面是笔者自己的一家之言,因为在源码中尚为找到相应的逻辑代码,所以仅是笔者的理解。
  2. gic_of_init
    要找到这个回调的出处得看一下 interrupt-controller 的设备树节点是怎么写的。如代码所示,其中 compatible 属性是arm,cortex-a7-gic 。而在代码中查找,又找到了 IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init) 这样的代码,其中函数 gic_of_init 没有找到相应的调用处。所以笔者的理解,它应该就是通过声明并结合设备树的 compatible 属性,然后在 of_irq_init 函数中被匹配到,从而调用该回调。下面将这个函数展开具体讲解

4.2.3 gic_of_init

gic_of_init 的调用层次比较时候深,我们依旧先把主要函数的调用图谱展开一下,方便读者理解

->gic_of_init
  ->__gic_init_bases
    ->set_handle_irq
    ->gic_init_chip
    ->gic_init_bases
      ->irq_domain_create_linear
      ->gic_dist_init
      ->gic_cpu_init
  ->gic_init_physaddr

笔者觉得以往 先上代码再使用另外一段文字进行讲述 的方式在代码比较多的情况下不便于笔者进行文字描述,对读者的理解也比较不利,所以笔者在长代码时会将 文字描述 以注释的方式写在代码片段中,对于重要的代码片段会进行标识和以另外的文字进行描述。
同理的,笔者按照自己的理解省去了一些代码,便于读者们理解。

static int gic_cnt __initdata;
static struct gic_chip_data gic_data[CONFIG_ARM_GIC_MAX_NR] __read_mostly;
int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
    /* struct gic_chip_data 用于描述GIC,即该结构体是GIC的软件抽象 */
    struct gic_chip_data *gic;
    int irq, ret;

    ......
    /* gic_data是一个静态数组,获取第一个结构体元素 */
    gic = &gic_data[gic_cnt];

    /* 获取CPU interface 和 distributor 的寄存器基地址 */
    ret = gic_of_setup(gic, node);
    if (ret)
        return ret;

    ......
    /* 初始化GIC结构体 */
    ret = __gic_init_bases(gic, -1, &node->fwnode);
    if (ret) {
        gic_teardown(gic);
        return ret;
    }
    /* 如果为根GIC,则获取其distributor的寄存器物理地址 */
    if (!gic_cnt) {
        gic_init_physaddr(node);
        ......
    }
    
    /* 如果当前的 gic 为子节点,那么它存在级联关系上的父节点,本文不讲述此情况,有兴趣的读者可查阅参考附录中的资料 */
    if (parent) {
        ......
    }

    if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
        gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);

    gic_cnt++;
    return 0;
}
static int gic_of_setup(struct gic_chip_data *gic, struct device_node *node)
{
    ......
    /* 从设备树节点中获取寄存器地址 */
    gic->raw_dist_base = of_iomap(node, 0);
    ......

    gic->raw_cpu_base = of_iomap(node, 1);
    ......

}
static int __init __gic_init_bases(struct gic_chip_data *gic,
                   int irq_start,
                   struct fwnode_handle *handle)
{
    char *name;
    int i, ret;

    ......

    if (gic == &gic_data[0]) {
        /*
         * Initialize the CPU interface map to all CPUs.
         * It will be refined as each CPU probes its ID.
         * This is only necessary for the primary GIC.
         */
        for (i = 0; i < NR_GIC_CPU_IF; i++)
            gic_cpu_map[i] = 0xff;

        ......
        /* 设置CPU热插拔状态,笔者目前对此并有太多了解,往后有机会再做研究 */
        cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                      "irqchip/arm/gic:starting",
                      gic_starting_cpu, NULL);
      /* 设置中断处理函数gic_handle_irq,一旦中断发生会就调用该回调函数 */
        set_handle_irq(gic_handle_irq);
        if (static_key_true(&supports_deactivate))
            pr_info("GIC: Using split EOI/Deactivate mode\n");
    }

    /* 使用gic_init_chip对GIC的回调函数进行初始化,回调函数主要用于对GIC进行操作,如屏蔽中断等 */
    if (static_key_true(&supports_deactivate) && gic == &gic_data[0]) {
        name = kasprintf(GFP_KERNEL, "GICv2");
        gic_init_chip(gic, NULL, name, true);
    } else {
        name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));
        gic_init_chip(gic, NULL, name, false);
    }
    /* 
      gic_init_bases用于初始化整个与GIC相关的结构体,包括irq_domain等。
      在本文中,gic只有单个,不存在级联或者其他连接方式,所以这里的irq_start等于 -1 
    */
    ret = gic_init_bases(gic, irq_start, handle);
    if (ret)
        kfree(name);

    return ret;
}
static void __init gic_init_physaddr(struct device_node *node)
{
    struct resource res;
    /* 设置 gic_dist_physaddr 为 distributor的物理基地址 */
    if (of_address_to_resource(node, 0, &res) == 0) {
        gic_dist_physaddr = res.start;
        pr_info("GIC physical location is %#lx\n", gic_dist_physaddr);
    }
}
static const struct irq_chip gic_chip = {
    .irq_mask       = gic_mask_irq,
    .irq_unmask     = gic_unmask_irq,
    .irq_eoi        = gic_eoi_irq,
    .irq_set_type       = gic_set_type,
    .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
    .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
    .flags          = IRQCHIP_SET_TYPE_MASKED |
                  IRQCHIP_SKIP_SET_WAKE |
                  IRQCHIP_MASK_ON_SUSPEND,
};
void __init set_handle_irq(void* handle_irq)
{
    if (handle_arch_irq)
        return;
    /* 
      handle_arch_irq是一个全局变量
      回头看一下前面的汇编代码,变量是在汇编代码vector_stub中定义的 
    */
    handle_arch_irq = handle_irq;
}
static void gic_init_chip(struct gic_chip_data *gic, struct device *dev,
              const char *name, bool use_eoimode1)
{
    /* Initialize irq_chip */
    /* gic_chip 是一个全局结构体变量,其成员主要是GIC的各种操作函数,用于操作GIC的寄存器以达到相关的功能 */
    gic->chip = gic_chip;
    gic->chip.name = name;
    gic->chip.parent_device = dev;

    if (use_eoimode1) {
        gic->chip.irq_mask = gic_eoimode1_mask_irq;
        gic->chip.irq_eoi = gic_eoimode1_eoi_irq;
        gic->chip.irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity;
    }
    ......
}
static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
              struct fwnode_handle *handle)
{
    irq_hw_number_t hwirq_base;
    int gic_irqs, irq_base, ret;

    if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) {
        /* Frankein-GIC without banked registers... */
        /* 在SMP下,ditributor和CPU interface的部分寄存器是与每个CPU对应,所以需要为每个CPU设置对应的寄存器基地址 */
        unsigned int cpu;

        gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
        gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
        if (WARN_ON(!gic->dist_base.percpu_base ||
                !gic->cpu_base.percpu_base)) {
            ret = -ENOMEM;
            goto error;
        }

        for_each_possible_cpu(cpu) {
            u32 mpidr = cpu_logical_map(cpu);
            u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
            unsigned long offset = gic->percpu_offset * core_id;
            *per_cpu_ptr(gic->dist_base.percpu_base, cpu) =
                gic->raw_dist_base + offset;
            *per_cpu_ptr(gic->cpu_base.percpu_base, cpu) =
                gic->raw_cpu_base + offset;
        }

        gic_set_base_accessor(gic, gic_get_percpu_base);
    } else {
        /* Normal, sane GIC... */
        /* 同理,为单核情况下的CPU设置 distributor 和 CPU interface 的寄存器基地址 */
        WARN(gic->percpu_offset,
             "GIC_NON_BANKED not enabled, ignoring %08x offset!",
             gic->percpu_offset);
        gic->dist_base.common_base = gic->raw_dist_base;
        gic->cpu_base.common_base = gic->raw_cpu_base;
        gic_set_base_accessor(gic, gic_get_common_base);
    }

    /*
     * Find out how many interrupts are supported.
     * The GIC only supports up to 1020 interrupt sources.
     */
    /* 计算Soc上的GIC应该支持多少个中断,并将该数赋值给 gic_irqs 成员 */
    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
    gic_irqs = (gic_irqs + 1) * 32;
    if (gic_irqs > 1020)
        gic_irqs = 1020;//GIC-V2最多支持1020个中断
    gic->gic_irqs = gic_irqs;

    /* 在一般情况下,我们会使用设备树,而handle在使用设备树的情况不为NULL,所以才使用该分支 */
    if (handle) {       /* DT/ACPI */
        /* 这里是创建线性irq_domain,其中gic_irq_domain_hierarchy_ops就是操作集 */
        gic->domain = irq_domain_create_linear(handle, gic_irqs,
                               &gic_irq_domain_hierarchy_ops,
                               gic);
    } else {        /* Legacy support */
        /*
         * For primary GICs, skip over SGIs.
         * For secondary GICs, skip over PPIs, too.
         */
        /* 也有一些传统的系统没有使用设备树,所以需要使用传统的方式创建irq_domian */  
        ......
        gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,
                    hwirq_base, &gic_irq_domain_ops, gic);
    }

    ......
    /* 初始化 distributor 和 CPU interface */
    gic_dist_init(gic);
    ret = gic_cpu_init(gic);
    ......

    return 0;

    ......
}

static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode,
                     unsigned int size,
                     const struct irq_domain_ops *ops,
                     void *host_data)
{
    return __irq_domain_add(fwnode, size, size, 0, ops, host_data);
}
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
                    irq_hw_number_t hwirq_max, int direct_max,
                    const struct irq_domain_ops *ops,
                    void *host_data)
{
    struct device_node *of_node = to_of_node(fwnode);
    struct irqchip_fwid *fwid;
    struct irq_domain *domain;

    static atomic_t unknown_domains;
    /* 为中断域申请内存空间  */
    domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                  GFP_KERNEL, of_node_to_nid(of_node));
    if (WARN_ON(!domain))
        return NULL;

    ......

    /* Fill structure */
    /* 填充中断域结构体 */
    INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
    domain->ops = ops;//设置操作机,用于创建映射,翻译等
    domain->host_data = host_data;
    domain->hwirq_max = hwirq_max;//设置最大中断硬件号
    domain->revmap_size = size;//设置查找表大小
    domain->revmap_direct_max_irq = direct_max;
    irq_domain_check_hierarchy(domain);//检查中断域是否级联

    mutex_lock(&irq_domain_mutex);
    debugfs_add_domain_dir(domain);
    /* 将中断域链入全局中断域链表irq_domain_list,以便发生中断时能够找到对应的中断域 */
    list_add(&domain->link, &irq_domain_list);
    mutex_unlock(&irq_domain_mutex);

    pr_debug("Added domain %s\n", domain->name);
    return domain;
}

static void gic_dist_init(struct gic_chip_data *gic)
{
    unsigned int i;
    u32 cpumask;
    unsigned int gic_irqs = gic->gic_irqs;
    void __iomem *base = gic_data_dist_base(gic);

    writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);

    /*
     * Set all global interrupts to this CPU only.
     */
    cpumask = gic_get_cpumask(gic);
    cpumask |= cpumask << 8;
    cpumask |= cpumask << 16;
    for (i = 32; i < gic_irqs; i += 4)
        writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);

    gic_dist_config(base, gic_irqs, NULL);

    writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}

static int gic_cpu_init(struct gic_chip_data *gic)
{
    void __iomem *dist_base = gic_data_dist_base(gic);
    void __iomem *base = gic_data_cpu_base(gic);
    unsigned int cpu_mask, cpu = smp_processor_id();
    int i;

    /*
     * Setting up the CPU map is only relevant for the primary GIC
     * because any nested/secondary GICs do not directly interface
     * with the CPU(s).
     */
    if (gic == &gic_data[0]) {
        /*
         * Get what the GIC says our CPU mask is.
         */
        if (WARN_ON(cpu >= NR_GIC_CPU_IF))
            return -EINVAL;

        gic_check_cpu_features();
        cpu_mask = gic_get_cpumask(gic);
        gic_cpu_map[cpu] = cpu_mask;

        /*
         * Clear our mask from the other map entries in case they're
         * still undefined.
         */
        for (i = 0; i < NR_GIC_CPU_IF; i++)
            if (i != cpu)
                gic_cpu_map[i] &= ~cpu_mask;
    }

    gic_cpu_config(dist_base, NULL);

    writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
    gic_cpu_if_up(gic);

    return 0;
}

那么以上就是中断初始化过程中,与 GIC 相关的硬件及软件初始化就完成了。总的来说,初始化过程主要抓住 2 个数据结构:struct gic_chip_datastruct irq_domain,整个初始化过程可以说是对这 2 个数据结构体的初始化。

4.3 中断注册

简单地说完中断系统的初始化后,我们接下来看看中断的注册。我们在驱动代码中主要就是注册中断,然后等待硬件触发中断调用函数。关于中断的注册我们一般需要经过 2 个步骤:

  1. 从设备树中获取硬件中断号并映射
  2. 注册中断处理函数

4.3.1 映射中断

在驱动代码中,我们常常使用接口 irq_of_parse_and_map 来完成中断映射及中断号获取这一工作。我们先看看调用图谱

->irq_of_parse_and_map
  ->irq_create_of_mapping
    ->of_phandle_args_to_fwspec(获取设备树节点中断信息)
    ->irq_create_fwspec_mapping(根据设备树节点中断信息完成映射)
      ->irq_find_matching_fwspec(根据设备树节点中断信息找到对应的中断域)
        ->中断域的select回调或match回调
      ->irq_domain_translate(根据设备树节点中断信息找出出硬件中断号)
        ->中断域的xlate回调
      ->irq_create_mapping(创建映射关系)
        ->irq_domain_alloc_descsf(分配中断号)
        ->irq_domain_associate(创建映射)
          ->中断域的map回调(gic_irq_domain_map)
            ->irq_domain_set_info(执行map的 3 个操作)
          ->irq_domain_set_mapping(填充映射DB以完成映射关系)

下面直接看看源码

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
    struct of_phandle_args oirq;
    /* 从设备树种获取硬件中断号的相关信息并赋值给结构体变量 oirq */
    if (of_irq_parse_one(dev, index, &oirq))
        return 0;
    /* 根据获取到的硬件中断号信息建立映射 */
    return irq_create_of_mapping(&oirq);
}
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
    struct irq_fwspec fwspec;
    /* 按照笔者的理解,该语句同理也是转换结构体,对于变量的传递并无大碍 */
    of_phandle_args_to_fwspec(irq_data, &fwspec);
    /* 建立中断号映射 */
    return irq_create_fwspec_mapping(&fwspec);
}
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
    struct irq_domain *domain;
    struct irq_data *irq_data;
    irq_hw_number_t hwirq;
    unsigned int type = IRQ_TYPE_NONE;
    int virq;
    /* 1. 先根据传递进来的中断信息查找相应的irq_domain */
    if (fwspec->fwnode) {
        domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
        if (!domain)
            domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
    } else {
        domain = irq_default_domain;
    }

    ......
    /* 2. 根据对应的中断域找出中断信息中的硬件中断号并赋值给变量 hwirq */
    if (irq_domain_translate(domain, fwspec, &hwirq, &type))
        return 0;
    ......
    /* 
        irq_find_mapping会根据硬件中断号和中断域进行翻译,并返回中断号 virq
        如果virq已经进行了映射,则不必再进行一次
    */
    virq = irq_find_mapping(domain, hwirq);
    if (virq) {
        ......
    }

    if (irq_domain_is_hierarchy(domain)) {
        virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
        if (virq <= 0)
            return 0;
    } else {
        /* Create mapping */
        /* 如果硬件中断号没有进行过映射,则在这里进行映射,并返回中断号virq */
        virq = irq_create_mapping(domain, hwirq);
        if (!virq)
            return virq;
    }

    ......

    return virq;
}
struct irq_domain *irq_find_matching_fwspec(struct irq_fwspec *fwspec,
                        enum irq_domain_bus_token bus_token)
{
    struct irq_domain *h, *found = NULL;
    struct fwnode_handle *fwnode = fwspec->fwnode;
    int rc;
    /* 
      遍历全局变量irq_domain_list上的每一个中断域,
      并根据其select和match回调进行匹配,如果符合则返回相应的中断域 
    */
    mutex_lock(&irq_domain_mutex);
    list_for_each_entry(h, &irq_domain_list, link) {
        if (h->ops->select && fwspec->param_count)
            rc = h->ops->select(h, fwspec, bus_token);
        else if (h->ops->match)
            rc = h->ops->match(h, to_of_node(fwnode), bus_token);
        else
            rc = ((fwnode != NULL) && (h->fwnode == fwnode) &&
                  ((bus_token == DOMAIN_BUS_ANY) ||
                   (h->bus_token == bus_token)));

        if (rc) {
            found = h;
            break;
        }
    }
    mutex_unlock(&irq_domain_mutex);
    return found;
}
static int irq_domain_translate(struct irq_domain *d,
                struct irq_fwspec *fwspec,
                irq_hw_number_t *hwirq, unsigned int *type)
{
    ......
    /* 调用xlate将设备树节点的中断信息转换为硬件中断号 */
    if (d->ops->xlate)
        return d->ops->xlate(d, to_of_node(fwspec->fwnode),
                     fwspec->param, fwspec->param_count,
                     hwirq, type);

    /* If domain has no translation, then we assume interrupt line */
    *hwirq = fwspec->param[0];
    return 0;
}
unsigned int irq_create_mapping(struct irq_domain *domain,
                irq_hw_number_t hwirq)
{
    struct device_node *of_node;
    int virq;

    pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);

    ......

    of_node = irq_domain_get_of_node(domain);

    /* Check if mapping already exists */
    /* 同理检查硬件中断号是否已经映射过了 */
    virq = irq_find_mapping(domain, hwirq);
    if (virq) {
        pr_debug("-> existing mapping on virq %d\n", virq);
        return virq;
    }

    /* Allocate a virtual interrupt number */
    /* 分配一个中断号virq用以映射,该中断号可以作为索引找到相应的中断描述符 */
    virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
    if (virq <= 0) {
        pr_debug("-> virq allocation failed\n");
        return 0;
    }
    /* 创建硬件中断号hwirq到中断号的映射virq */
    if (irq_domain_associate(domain, virq, hwirq)) {
        irq_free_desc(virq);
        return 0;
    }

    pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
        hwirq, of_node_full_name(of_node), virq);

    return virq;
}
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
             irq_hw_number_t hwirq)
{
    struct irq_data *irq_data = irq_get_irq_data(virq);
    int ret;

    ......

    mutex_lock(&irq_domain_mutex);
    /* 填充对应中断数据的成员 */
    irq_data->hwirq = hwirq;
    irq_data->domain = domain;
    /* 使用map回调进行映射 */
    if (domain->ops->map) {
        ret = domain->ops->map(domain, virq, hwirq);
        if (ret != 0) {
            /*
             * If map() returns -EPERM, this interrupt is protected
             * by the firmware or some other service and shall not
             * be mapped. Don't bother telling the user about it.
             */
            if (ret != -EPERM) {
                pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
                       domain->name, hwirq, virq, ret);
            }
            irq_data->domain = NULL;
            irq_data->hwirq = 0;
            mutex_unlock(&irq_domain_mutex);
            return ret;
        }

        /* If not already assigned, give the domain the chip's name */
        if (!domain->name && irq_data->chip)
            domain->name = irq_data->chip->name;
    }

    domain->mapcount++;
    /* 填充中断号映射查找表 */
    irq_domain_set_mapping(domain, hwirq, irq_data);
    mutex_unlock(&irq_domain_mutex);

    irq_clear_status_flags(virq, IRQ_NOREQUEST);

    return 0;
}
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                irq_hw_number_t hw)
{
    struct gic_chip_data *gic = d->host_data;

    if (hw < 32) {
        irq_set_percpu_devid(irq);
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                    handle_percpu_devid_irq, NULL, NULL);
        irq_set_status_flags(irq, IRQ_NOAUTOEN);
    } else {
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
        irq_set_probe(irq);
        irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
    }
    return 0;
}
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
             irq_hw_number_t hwirq, struct irq_chip *chip,
             void *chip_data, irq_flow_handler_t handler,
             void *handler_data, const char *handler_name)
{
    /* 执行map回调的 3 个操作 */
    irq_set_chip_and_handler_name(virq, chip, handler, handler_name);
    irq_set_chip_data(virq, chip_data);
    irq_set_handler_data(virq, handler_data);
}
static void irq_domain_set_mapping(struct irq_domain *domain,
                   irq_hw_number_t hwirq,
                   struct irq_data *irq_data)
{
    /* 
      对于线性映射来说,一般执行 if 分支,可以看到是将硬件中断号作为下标找到查找表的元素,
      然后将元素填充为我们虚拟的中断号 
    */
    if (hwirq < domain->revmap_size) {
        domain->linear_revmap[hwirq] = irq_data->irq;
    } else {
        mutex_lock(&revmap_trees_mutex);
        radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
        mutex_unlock(&revmap_trees_mutex);
    }
}
  1. 在函数 irq_find_matching_fwspec 中使用到了全局变量 irq_domain_list。我们在前面说到,在创建中断域的时候每一个 irq_domain 结构体都会被链入到该全局变量中。
  2. irq_create_mapping 函数中调用了 irq_domain_alloc_descsirq_domain_alloc_descs 函数不仅只是分配中断号,同时了也分配了一个中断描述符,当然了,关于中断描述符的操作对于上层都是不可见的,都是由中断子系统来操作。
  3. gic_irq_domain_map函数就是中断域的 map回调,我们在前面说了 map回调 会执行 3 个操作,那么这 3 个操作就是在 irq_domain_set_info 中完成的
  4. 实际上,真正完成映射工作的函数并不是在 gic_irq_domain_map,而是在 irq_domain_set_mapping ,该函数会根据中断域的类型是线性映射还是基数树映射从而选择不用的映射方式,我们可以看到,对于 线性映射,它是直接在一个 数组(即查找表) 中直接对元素进行填充,而下标则是 硬件中断号

以上就是 映射中断 的过程了,我们再回头去看我们的调用图谱,相信可以得到一个比较清晰完整的思路

4.3.2 注册中断

在了解中断注册前我们了解一下 中断线程化。为此,我们需要回答 2 个问题:

  • 什么是中断线程化
    中断线程化指将注册的中断处理函数使用一个线程来执行,而不是在硬件的中断上下文中我们去执行该处理函数
  • 为什么要中断线程化
    主要目的是为了提高操作系统的实时性。对那些耗时的中断处理函数进行线程化,在内核的抢占点上,让线程(无论是内核线程还是用户空间创建的线程,还是驱动的interrupt thread)在一个舞台上竞争CPU。

函数调用图谱如下,函数调用过程比较简单

->request_threaded_irq
  ->__setup_irq
    ->irq_setup_forced_threading
      ->setup_irq_thread

下面是源码及讲解

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)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

    ......
    desc = irq_to_desc(irq);
    ......

    /* 
      参数handler和thread_fn不能同时为空,
      如果handler为空,则会使用默认函数irq_default_primary_handler 
    */
    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        handler = irq_default_primary_handler;
    }

    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    ......
    /* 填充中断处理函数的action结构体 */
    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    ......
    /* 设置中断处理函数 */
    retval = __setup_irq(irq, desc, action);
    ......
}

static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
    struct irqaction *old, **old_ptr;
    unsigned long flags, thread_mask = 0;
    int ret, nested, shared = 0;

    ......

    new->irq = irq;

    ......

    nested = irq_settings_is_nested_thread(desc);
    if (nested) {
        /* 关于级联式中断GIC驱动代码本文不做讲述 */
        ......
    } else {
        /* 查看中断是否需要被线程化 */
        if (irq_settings_can_thread(desc)) {
            /* 将中断处理函数进行线程话的前置处理 */
            ret = irq_setup_forced_threading(new);
            if (ret)
                goto out_mput;
        }
    }

    if (new->thread_fn && !nested) {
        /* 开启中断线程,并设置中断处理函数 */
        ret = setup_irq_thread(new, irq, false);
        ......
    }

    ......

    old_ptr = &desc->action;
    old = *old_ptr;
    if (old) {

        ......

        /* add new interrupt at end of irq queue */
        do {
            /*
             * Or all existing action->thread_mask bits,
             * so we can find the next zero bit for this
             * new action.
             */
            thread_mask |= old->thread_mask;
            old_ptr = &old->next;
            old = *old_ptr;
        } while (old);
        shared = 1;
    }

    ......

    if (!shared) {
        /*  初始化wait_for_threads,该等待队列用于同步共享中断处理函数的退出 。*/
        init_waitqueue_head(&desc->wait_for_threads);
        ......

    } else if (new->flags & IRQF_TRIGGER_MASK) {
        ......
    }

    *old_ptr = new;

    ......

    /*
     * Strictly no need to wake it up, but hung_task complains
     * when no hard interrupt wakes the thread up.
     */
    /* 执行创建的中断线程 */
    if (new->thread)
        wake_up_process(new->thread);
    if (new->secondary)
        wake_up_process(new->secondary->thread);
    ......
}
static int irq_setup_forced_threading(struct irqaction *new)
{
    ......
    /* Deal with the primary handler */
    /* 
        当强制中断线程化后, handler将会被赋予thread_fn,
        而thread_fn则会在中断线程中执行,
        handler本身被修改为irq_default_primary_handler,该函数用于唤醒阻塞的中断线程
     */
    set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
    new->thread_fn = new->handler;
    new->handler = irq_default_primary_handler;
    return 0;
}
static int setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
    struct task_struct *t;
    struct sched_param param = {
        .sched_priority = MAX_USER_RT_PRIO/2,
    };

    /* 创建中断线程,其执行函数为irq_thread */
    if (!secondary) {
        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);
    } else {
        t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
                   new->name);
        param.sched_priority -= 1;
    }

    ......
    /* 设置中断线程的优先级 */
    sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
    ......
    /* 获取线程的进程描述符并赋予struct irqaction结构体 */
    get_task_struct(t);
    new->thread = t;
    
    ......
}
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);

    /* irq_forced_thread_fn和irq_thread_fn区别在于有没有关闭软中断 */
    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);

    /* irq_wait_for_interrupt用于等待中断发生 */
    while (!irq_wait_for_interrupt(action)) {
        irqreturn_t action_ret;
        ......
        /* 执行中断处理函数 */
        action_ret = handler_fn(desc, action);
        ......

        /* 唤醒等待队列中的中断线程,并将threads_active标志减 1 */
        wake_threads_waitq(desc);
    }
    ......
    return 0;
}

static irqreturn_t irq_forced_thread_fn(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t ret;
    /* 关闭软中断 */
    local_bh_disable();
    /* 执行中断处理函数 */
    ret = action->thread_fn(action->irq, action->dev_id);
    ......
    /* 开启软中断 */
    local_bh_enable();
    return ret;
}
  • 上面代码中的 irq_thread 为创建的中断线程,用于执行中断处理函数
  • 为什么要初始化等待队列 wait_for_threads
    因为在共享终端中,如果使用 disable_irq 关闭中断,有可能中断处理线程正在执行注册的中断处理函数,所以需要等待所有中断处理函数执行完毕。而此时等待就是使用该等待队列来进行,而等待条件就是标志 threads_active 为 0。wake_threads_waitqirq_thread 中被执行,我们查看上面的 irq_thread 源码会发现每次执行完中断处理函数都会执行一次 wake_threads_waitq。我们看看下面的代码:
void disable_irq(unsigned int irq)
{
    if (!__dis
able_irq_nosync(irq))
        synchronize_irq(irq);
}
void synchronize_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (desc) {
        ......
        /* 在这里使用等待队列进行等待 */
        wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));
    }
}
static void wake_threads_waitq(struct irq_desc *desc)
{
    /* 将 threads_active 标志减 1,如果减 1 后 threads_active 为 0,则唤醒 wait_for_threads 队列 */
    if (atomic_dec_and_test(&desc->threads_active))
        wake_up(&desc->wait_for_threads);
}
  • 当中断开启线程化时,中断处理函数是被赋予在thread_fn成员,而非handler成员。按照笔者的理解, handler 成员用于在 中断上下文 执行,而 thread_fn 成员则是在 中断线程 中执行
  • 中断线程化接口 request_threaded_irq 中的 handler 参数和 thread_fn 参数使用情况如下:
handler thread_fn 描述
NULL NULL 函数出错,返回-EINVAL
设定 设定 正常流程,中断处理被合理的分配到handler和thread_fn中
设定 NULL 中断处理都是在handler中完成
NULL 设定 系统会帮忙设定一个默认的handler(irq_default_primary_handler),用于唤醒thread_fn线程

到这里,注册中断的流程就结束了。我们对注册中断有了个大概的认识。当然了,因为中间删减了一点代码,所以本文省略了对于中断标志的判断及相应代码的讲述 ,有兴趣的读者可以自行阅读代码理解。

最终我们还需要使用 enablie_irq 来使能中断,而这一步就暂时先跳过吧

4.4 执行中断

当我们的中断发生时,PC指针 会跳到中断向量表中对应的中断向量去执行执行。而在我们前面的章节也说到,中断对应的中断向量是 vector_irq,函数调用图谱如下:

->vector_irq
  ->__irq_usr或__irq_svc
    ->irq_handler

下面看看源码,在中断发生时,执行的都是 汇编代码,这里需要读者有一定的汇编基础。


/* 
  下面是一个宏,其展开之后是一个函数,可以在文章后面查看在 vector_stub 的源码 
  关于 vector_stub 源码本文不做讲解,可以查看附录的 《Linux kernel的中断子系统之(六):ARM中断处理过程》
*/
vector_stub irq, IRQ_MODE, 4
/* 
  下面是一个查找表,会根据 vector_stub 代码中的计算根据不同的情况执行不同的函数
  如果中断发生时,代码在用户空间中执行,则会跳转到__irq_usr         
  如果中断发生时,代码在内核空间中执行,则会跳转到__irq_svc     
*/
.long   __irq_usr           @  0  (USR_26 / USR_32)
.long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
.long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
.long   __irq_svc           @  3  (SVC_26 / SVC_32)
.long   __irq_invalid           @  4
.long   __irq_invalid           @  5
.long   __irq_invalid           @  6
.long   __irq_invalid           @  7
.long   __irq_invalid           @  8
.long   __irq_invalid           @  9
.long   __irq_invalid           @  a
.long   __irq_invalid           @  b
.long   __irq_invalid           @  c
.long   __irq_invalid           @  d
.long   __irq_invalid           @  e
.long   __irq_invalid           @  f
/************************************************分割线****************************************************/
__irq_usr:
    usr_entry
    kuser_cmpxchg_check
    irq_handler
    get_thread_info tsk
    mov why, #0
    b   ret_to_user_from_irq
/************************************************分割线****************************************************/
__irq_svc:
    svc_entry
    irq_handler
#ifdef CONFIG_PREEMPT
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    ldr r0, [tsk, #TI_FLAGS]        @ get flags
    teq r8, #0              @ if preempt count != 0
    movne   r0, #0              @ force flags to 0
    tst r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif
/************************************************分割线****************************************************/
.macro  irq_handler
......
ldr r1, =handle_arch_irq
mov r0, sp
badr    lr, 9997f
ldr pc, [r1]
......
  1. __irq_usr__irq_svc 都会执行宏 irq_handler
  2. irq_handler 则调用了handle_arch_irq
  3. handle_arch_irq 被赋予为函数 gic_handle_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 {
        /* 读取GIC寄存器获取硬件中断号 */
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;

        if (likely(irqnr > 15 && irqnr < 1020)) {
            ......
            /* 如果硬件中断号有效就执行 handle_domain_irq */
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
        /* 下面为SGI 中断,本文不做讲述 */
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
            if (static_key_true(&supports_deactivate))
                writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
            ......
            continue;
        }
        break;
    } while (1);
}
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;
    ......
    /* 根据中断域和硬件中断号找出中断号 */
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);
    
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        /* 如果中断号有效则执行 generic_handle_irq */
        generic_handle_irq(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);
}

到了这里,我们就断了线索了。那么我们接下来就要找出中断描述符的 handle_irq 回调。回到之前的代码中去查找,看看哪里有设置了中断描述符的回调,那么经过一番查找最终找到是在函数 irq_domain_set_info 中设置的,其调用图谱如下

->gic_irq_domain_map
  ->irq_domain_set_info
    ->irq_set_chip_and_handler_name
      ->__irq_set_handler
        ->__irq_do_set_handler
          ->desc->handle_irq = handle;

函数 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 gic_chip_data *gic = d->host_data;

    if (hw < 32) {
        irq_set_percpu_devid(irq);
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                    handle_percpu_devid_irq, NULL, NULL);
        irq_set_status_flags(irq, IRQ_NOAUTOEN);
    } else {
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
        irq_set_probe(irq);
        irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
    }
    return 0;
}

可以看到,该函数会根据 硬件中断号 设置不同的 handler,而我们常用的中断号一般大于 32,所以我们一般使用的是 handle_fasteoi_irq,所以我们最终调用的就是该函数,我们看看 handle_fasteoi_irq 的调用图谱,如下所示:

->handle_fasteoi_irq
  ->handle_irq_event
    ->handle_irq_event_percpu
      ->__handle_irq_event_percpu

接下来我们看看源码,如下所示

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

    handle_irq_event(desc);

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

    ......

    ret = handle_irq_event_percpu(desc);

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

    retval = __handle_irq_event_percpu(desc, &flags);
    ......
}
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;
    ......
    /* 
      在这里遍历中断描述符的每一个 action 结构体
    */
    for_each_action_of_desc(desc, action) {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
    /* 这里就执行我们注册的 handler 回调,这里还是在中断上下文中 */
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);
        ......
        switch (res) {
        case IRQ_WAKE_THREAD:

            ......
            /* 唤醒对应 action 的中断线程,执行相关的中断处理函数 */
            __irq_wake_thread(desc, action);

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

        default:
            break;
        }

        retval |= res;
    }

    return retval;
}
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
    return IRQ_WAKE_THREAD;
}

我们看到 __handle_irq_event_percpu 中会执行注册的 handler 回调,而如果我们使用了中断线程化的,那么此时 handlerirq_default_primary_handler,而 irq_default_primary_handler 是直接返回一个值 IRQ_WAKE_THREAD,并赋予变量 res。当我们执行到 switch 语句后,就会跳转到 IRQ_WAKE_THREAD 分支,该分支就是唤醒我们的在等待中的中断线程。

中断线程化 跟我们常用的 上半部分下半部分 很像。按照笔者的理解,上半部分 对应 handler ,而 下半部分 对应 thread_fn。但中断线程化的好处就是我们不在是通过 tasklet线程 来执行下半部分,而是通过专用的 中断线程 来处理中断函数。因为 tasklet 本质上是 软件中断,具有非常高的优先级的,如果此时有比该中断更加紧急的任务需要执行,且该中断占用时间比较长,那么就会影响系统的实时性。而我们把所有中断都编程一个线程,通过设计每个线程的优先级,让其参与 CPU 竞争,这样对于整体的实时性会有所提高。

至此,我们对触发中断到执行中断函数的整个过程有了个大概的轮廓,笔者在汇编代码阶段省略了一些操作,比如如何选择__irq_svc或__irq_usr的,但这些操作不是本文的重点讲述内容,有兴趣的读者可以从 参考附录 中阅读文章了解。

五、后语

总结一下,本文从 硬件软件 方面下手,硬件方面主要简单地讲述了 通用中断控制器GIC 的整体架构及相关功能,软件上讲述了 异常中断 2 个功能的代码及流程,其中 中断 是本文的重点,主要分为 中断初始化中断注册中断执行 这 3 个部分来讲。当然了,本文主要在于梳理真个中断流程的思路,其中还有很多关于中断子系统的代码还没讲解,各位读者有兴趣的话可以自行阅读源码。本文也是按照笔者的理解来编写,如果有错误或者缺漏的地方还请各位海涵指出。

六、参考附录

  1. IRQ number和中断描述符:http://www.wowotech.net/irq_subsystem/interrupt_descriptor.html
  2. IRQ Domain介绍:http://www.wowotech.net/irq_subsystem/irq-domain.html
  3. GIC代码分析:http://www.wowotech.net/irq_subsystem/gic_driver.html
  4. ARM中断处理过程:http://www.wowotech.net/irq_subsystem/irq_handler.html
  5. 驱动申请中断API:http://www.wowotech.net/irq_subsystem/request_threaded_irq.html
  6. GIC相关系列文章:http://www.lujun.org.cn/?s=ARM+GIC&paged=2
  7. ARM协处理器介绍:https://blog.csdn.net/silent123go/article/details/53169783

你可能感兴趣的:(linux驱动之中断)