程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux设备树-中断控制器驱动

----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

我们在linux驱动移植-中断子系统执行流程 介绍了中断的执行流程,以及在没有使用设备树的情景下中断控制器的注册流程,其主要流程:

  • S3C2440中断资源抽象为一个主中断控制器、两个子中断控制器,一个用于管理外部中断源、另一个管理带有子中断的内部中断源;
  • 采用基于数组方式分配中断描述符(struct irq_desc),数组长度为NR_IRQS;
  • 为每个中断控制器态分配struct s3c_irq_intc,初始化s3c_irq_intc中断相关寄存器:源挂起寄存器、中断屏蔽寄存器、中断挂起寄存器,清除中断挂起状态
  • 为每一个中断控制器分配irq_domain,并进行初始化,追加到全局链表irq_domain_list;中断域存储了硬件中断号到IRQ编号的映射,对于非设备树的场景,硬件中断号和IRQ编号是固定的;
  • 设置每个中断描述符的handle_irq回调和irq_chip指针;irq_desc->handle_irq根据中断触发类型设置为了不同的流控处理函数,比如handle_edge_irq、handle_level_irq、s3c_irq_demux;

这一节我们将介绍在使用设备树的情景下,中断控制器的注册流程。其流程与没有设备树情境下大体相同,主要就是中断控制器的初始化入口函数略有差异。

一、中断资源

S3C2440包含:32个主中断源、24个外部中断,以及15个内部子中断。

1.1 主中断

32个主中断:

INT Source offset INT Source offset
INT_ADC  31 INT_UART2  15
INT_RTC  30 INT_TIMER4  14
INT_SPI1  29 INT_TIMER3  13
INT_UART0  28 INT_TIMER2  12
INT_IIC  27 INT_TIMER1  11
INT_USBH  26 INT_TIMER0  10
INT_USBD  25 INT_WDT_AC97  9
INT_NFCON  24 INT_TICK  8
INT_UART1  23 nBATT_FLT  7
INT_SPI0  22 INT_CAM  6
INT_SDI  21 EINT8_23  5
INT_DMA3  20 EINT4_7  4
INT_DMA2  19 EINT3  3
INT_DMA1  18 EINT2  2
INT_DMA0  17 EINT1  1
INT_LCD  16 EINT0  0

寄存器功能介绍:

  • SRCPND: 地址0x4A000000, 每一位代表一个主中断,置1表示有对应的主中断请求,对应位写入1可以清除中断;

  • INTMOD:地址0x4A000004, 设置对应的主中断为IRQ还是FIQ, 置1表示FIQ;

  • INTMSK:地址0x4A000008 ,置1表示对应的主中断被屏蔽(不会影响SRCPND);

  • INTPND: 地址0x4A000010,表示对应的主中断被请求,只可能有一位被置位,写入1可以清除中断;

  • INTOFFSET: 地址0x4A000014,存放的是发生中断请求的主中断号;

1.2 内部子中断

内部子中断15个,几个内部子中断对应一个主中断,对应表如下:

Manin Int Source offset Sub Int Source offset
INT_UART0 28 INT_RXD0 0
INT_TXD0 1
INT_ERR0 2
INT_UART1 23 INT_RXD1 3
INT_TXD1 4
INT_ERR2 5
INT_UART2 15 INT_RXD2 6
INT_TXD2 7
INT_ERR2 8
INT_ADC 31 INT_TC 9
INT_ADC_S 10
INT_CAM 6 INT_CAM_C 11
INT_CAM_P 12
INT_WDT_AC97 9 INT_WDT 13
INT_AC97 14

寄存器功能介绍:

  • SUBSRCPND:地址0x4A000018,每一位代表一个子中断,置一表示对应子中断请求,对应位写入1清除子中断请求;
  • INTSUBMSK:地址0x4A00001C,置1表示对应的子中断被屏蔽;

1.3 外部中断

外部中断24个,几个外部中断对应同一个主中断,对应表如下:

Manin Int Source offset Ext Int Source offset
EINT0 0 EINT0 0
EINT1 1 EINT1 1
EINT2 2 EINT2 2
EINT3 3 EINT3 3
EINT4~7 4 EINT4 4
EINT5 5
EINT6 6
EINT7 7
EINT8~23 5 EINT8 8
...... .....
EINT23 23

EINT0~7对应的GPIO是GPF0~7;EINT8~23对应的GPIO是GPG0~15。

二、设备树

2.1 中断控制器节点

在arch/arm/boot/dts/s3c24xx.dtsi定义了中断控制器节点:

intc:interrupt-controller@4a000000 {
     compatible = "samsung,s3c2410-irq";
     reg = <0x4a000000 0x100>;
     interrupt-controller;
     #interrupt-cells = <4>;
};

由于这个中断控制器位于最顶层,即没有interrupt-parent属性,因此也称为根中断控制器。

从中断控制器的#interrupt-cells属性知道,要描述一个中断需要4个参数,每一个参数的含义需要由中断控制器的驱动来解释,具体是由中断控制器的irq_domain_ops中的xlate来解释,对于s3c2440就是drivers/irqchip/irq-s3c24xx.c中的s3c24xx_irq_xlate_of,这个后面来介绍。

此外,在arch/arm/boot/dts/s3c2440-pinctrl.dtsi还定义了两个GPIO中断控制器:

interrupt-parent = <&intc>;
pinctrl_0: pinctrl@56000000 {
        compatible = "samsung,s3c2440-pinctrl";
        reg = <0x56000000 0x1000>;

        wakeup-interrupt-controller {
                compatible = "samsung,s3c2410-wakeup-eint";
                interrupts = <0 0 0 3>,
                             <0 0 1 3>,
                             <0 0 2 3>,
                             <0 0 3 3>,
                             <0 0 4 4>,
                             <0 0 5 4>;
        };
        
        /*
         * Pin banks
         */

        gpa: gpa {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpb: gpb {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpc: gpc {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpd: gpd {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpe: gpe {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpf: gpf {
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
        };

        gpg: gpg {
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
        };

        gph: gph {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpj: gpj {
                gpio-controller;
                #gpio-cells = <2>;
        };


        /*
         * Pin groups
         */

        uart0_data: uart0-data {
                samsung,pins = "gph-2", "gph-3";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        uart1_data: uart1-data {
                samsung,pins = "gph-4", "gph-5";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };


        uart2_data: uart2-data {
                samsung,pins = "gph-6", "gph-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        uart2_fctl: uart2-fctl {
                samsung,pins = "gph-6", "gph-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        extuart_clk: extuart-clk {
                samsung,pins = "gph-8";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        i2c0_bus: i2c0-bus {
                samsung,pins = "gpe-14", "gpe-15";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        spi0_bus: spi0-bus {
                samsung,pins = "gpe-11", "gpe-12", "gpe-13";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_clk: sd0-clk {
                samsung,pins = "gpe-5";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_cmd: sd0-cmd {
                samsung,pins = "gpe-6";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_bus1: sd0-bus1 {
                samsung,pins = "gpe-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_bus4: sd0-bus4 {
                samsung,pins = "gpe-8", "gpe-9", "gpe-10";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        /*添加Nand Flash所用的管脚*/
        nand_pinctrl: nand_pinctrl {
            samsung,pins = "gpa-17", "gpa-18", "gpa-19",
                         "gpa-20", "gpa-22";
            samsung,pin-function = <1>;
        };

};
View Code

gpf、gpg本身也充当一个中断控制器,它的interrupt parent也是interrupt-controller@4a000000,gpf的interrupt cell是2, 表示引用gpf的一个中断需要2个参数来描述。

2.2 设备节点

以串口0设备节点为例,其使用了串口收发中断,设备节点定义如下:

uart0: serial@50000000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50000000 0x4000>;
        interrupts = <1 28 0 4>, <1 28 1 4>;
        status = "disabled";
};            

以I2C设备节点为例,其使用了I2C中断,设备节点定义如下:

i2c@54000000 {
        compatible = "samsung,s3c2440-i2c";
        reg = <0x54000000 0x100>;
        interrupts = <0 0 27 3>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
};

以网卡节点为例,其使用了外部中断7,设备节点定义为如下:

/*添加DM9000 网卡,以便于后面的驱动开发调试*/
srom-cs4@20000000 {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0x20000000 0x8000000>;
    ranges;
    ethernet@20000000 {
        compatible = "davicom,dm9000";
        reg = <0x20000000 0x2 0x20000004 0x2>;
        interrupt-parent = <&gpf>;
        interrupts = <7 IRQ_TYPE_EDGE_RISING>;
        local-mac-address = [00 00 de ad be ef];
        davicom,no-eeprom;
    };
};

interrupts属性中的四个参数中的含义:

针对于I2C,中断控制器节点为intc, 中断描述信息为:<0 0 27 3>:

  • 第1个0表示的是主中断控制器;
  • 第2个数字0无意义;
  • 第3个数字27表示硬件中断号;从S3C2440数据手册我们可以知道,I2C的硬件中断号是27;
  • 第4个数字3表示双边沿触发;

针对于串口0,中断控制器节点为intc, 第一个中断描述信息为:<1 28 0 4>:

  • 第1个数字1表示的是子中断控制器;
  • 第2个数字28表示的是串口0的主中断硬件中断号,从S3C2440数据手册我们可以知道,串口0主中断硬件中断号为28;
  • 第3个数字0表示的是子中断控制器的硬件中断号,也就是INT_RXD0的位号0;
  • 第4个数字4表示的是高电平触发;

针对于串口0,中断控制器节点为intc, 第二个中断描述信息为:<1 28 1 4>:

  • 第1个数字1表示的是子中断控制器;
  • 第2个数字28表示的是串口0的主中断硬件中断号,从S3C2440数据手册我们可以知道,串口0主中断硬件中断号为28;
  • 第3个数字0表示的是子中断控制器的硬件中断号,也就是INT_TXD0的位号1;
  • 第4个数字4表示的是高电平触发;

针对网卡,中断控制器节点为gpf, 中断描述信息为<7 IRQ_TYPE_EDGE_RISING>:

  • 第1个参数7表示外部中断7;
  • 第2个参数IRQ_TYPE_EDGE_RISING表示上升沿触发;

三、根中断控制器的注册

根中断控制器节点interrupt-controller@4a000000的初始化和注册是在函数s3c2410_init_intc_of中做的,这个函数是怎么被执行到的呢?我们将会逐一分析。

3.1 machine desc

内核启动的时候会执行start_kernel函数,start_kernel发出init_IRQ调用,init_IRQ在文件arch/arm/kernel/irq.c定义:

void __init init_IRQ(void)
{
        int ret;

        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
                irqchip_init();
        else
                machine_desc->init_irq();   // 执行

        if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
            (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
                if (!outer_cache.write_sec)
                        outer_cache.write_sec = machine_desc->l2c_write_sec;
                ret = l2x0_of_init(machine_desc->l2c_aux_val,
                                   machine_desc->l2c_aux_mask);
                if (ret && ret != -ENODEV)
                        pr_err("L2C: failed to init: %d\n", ret);
        }

        uniphier_cache_init();
}

它会直接调用所属板子machine_desc结构体中的init_irq回调。machine_desc在arch/arm/mach-s3c24xx/mach-smdk2440-dt.c中定义:

static const char *const s3c2440_dt_compat[] __initconst = {
        "samsung,s3c2440",
        "samsung,mini2440",
        NULL
};

DT_MACHINE_START(S3C2440_DT, "Samsung S3C2440 (Flattened Device Tree)")
        /* Maintainer: Heiko Stuebner <heiko@sntech.de> */
        .dt_compat      = s3c2440_dt_compat,
        .map_io         = s3c2440_dt_map_io,
        .init_irq       = irqchip_init,
        .init_machine   = s3c2440_dt_machine_init,
MACHINE_END

3.2 irqchip_init

由于machine_desc->init_irq初始化为irqchip_init,定位到文件drivers/irqchip/irqchip.c。

irqchip_init函数如下:

void __init irqchip_init(void)
{
        of_irq_init(__irqchip_of_table);
        acpi_probe_device_table(irqchip);
}

of_irq_init函数用于初始化中断控制器,并注册irq_domain。

在drivers/irqchip/irqchip.c文件中引用了__irqchip_of_table全局变量:

static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_table_end);   extern struct of_device_id __irqchip_of_table[];

__irqchip_of_table定义在arch/arm/kernel/vmlinux.lds链接脚本里,是一个内存地址,由于段__irqchip_of_table中存放的都是of_device_id结构体类型,所以在C文件中中引用了该地址,定义为of_device_id结构体数组。

.init.data : AT(ADDR(.init.data) - 0) { 
    KEEP(*(SORT(___kentry+*))) 
    *(.init.data init.data.*) 
    *(.meminit.data*) 
    *(.init.rodata .init.rodata.*) 
    *(.meminit.rodata) 
    . = ALIGN(8); 
    __clk_of_table = .; 
    KEEP(*(__clk_of_table)) 
    KEEP(*(__clk_of_table_end)) 
    . = ALIGN(8); 
    __reservedmem_of_table = .; 
    KEEP(*(__reservedmem_of_table)) 
    KEEP(*(__reservedmem_of_table_end)) 
    . = ALIGN(8); 
    __timer_of_table = .; 
    KEEP(*(__timer_of_table)) 
    KEEP(*(__timer_of_table_end)) 
    . = ALIGN(32); 
    __dtb_start = .; 
    KEEP(*(.dtb.init.rodata)) 
    __dtb_end = .; 
    . = ALIGN(8); 
    __irqchip_of_table = .; 
    KEEP(*(__irqchip_of_table)) 
    KEEP(*(__irqchip_of_table_end)) 
    . = ALIGN(8); 
    __earlycon_table = .; 
    KEEP(*(__earlycon_table)) 
    __earlycon_table_end = .; 
    . = ALIGN(16); 
    __setup_start = .; 
    KEEP(*(.init.setup)) 
    __setup_end = .; 
    __initcall_start = .; 
    KEEP(*(.initcallearly.init)) 
    __initcall0_start = .; 
    KEEP(*(.initcall0.init)) 
    KEEP(*(.initcall0s.init)) 
    __initcall1_start = .; 
    KEEP(*(.initcall1.init)) 
    KEEP(*(.initcall1s.init)) 
    __initcall2_start = .; 
    KEEP(*(.initcall2.init)) 
    KEEP(*(.initcall2s.init)) 
    __initcall3_start = .; 
    KEEP(*(.initcall3.init)) 
    KEEP(*(.initcall3s.init)) 
    __initcall4_start = .; 
    KEEP(*(.initcall4.init)) 
    KEEP(*(.initcall4s.init)) 
    __initcall5_start = .; 
    KEEP(*(.initcall5.init)) 
    KEEP(*(.initcall5s.init)) 
    __initcallrootfs_start = .; 
    KEEP(*(.initcallrootfs.init)) 
    KEEP(*(.initcallrootfss.init)) 
    __initcall6_start = .; 
    KEEP(*(.initcall6.init)) 
    KEEP(*(.initcall6s.init)) 
    __initcall7_start = .; 
    KEEP(*(.initcall7.init)) 
    KEEP(*(.initcall7s.init))
    __initcall_end = .; 
    __con_initcall_start = .; 
    KEEP(*(.con_initcall.init))
    __con_initcall_end = .; 
    . = ALIGN(4); 
    __initramfs_start = .; 
    KEEP(*(.init.ramfs)) 
    . = ALIGN(8); 
    KEEP(*(.init.ramfs.info)) 
}
View Code

此外这里还定义一个名为irqchip_of_match_end的 of_device_id 结构体,并将其放置在内核的 .irqchip_of_table_end 节(section)内。

3.2.1 __irqchip_of_table

在linux内核根路径下搜索samsung,s3c2410-irq:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# grep "samsung,s3c2410-irq" * -nR
drivers/irqchip/irq-s3c24xx.c:1307:IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

定位到drivers/irqchip/irq-s3c24xx.c文件:

IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

IRQCHIP_DECLARE的作用如下所述:用IRQCHIP_DECLARE声明中断控制器驱动,并将其与初始化函数关联。

其中IRQCHIP_DECLARE宏定义在include/linux/irqchip.h中:

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
 
#define OF_DECLARE_2(table, name, compat, fn) \ 
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
 
#define _OF_DECLARE(table, name, compat, fn, fn_type)            \ 
    static const struct of_device_id __of_table_##name        \ 
        __used __section(__##table##_of_table)            \ 
         = { .compatible = compat,                \ 
             .data = (fn == (fn_type)NULL) ? fn : fn  }

展开后,其实就是定义了:

static const struct of_device_id __clk_of_table_s3c2410_irq \
__used__section(__irqchip_of_table) \
= {
  .compatible = "samsung,s3c2410-irq",
  .data = s3c2410_init_intc_of,
};

定义一个名为__clk_of_table_s3c2410_irq的of_device_id结构体,并将其放置在内核的 .__irqchip_of_table节(section)内。 

不同的中断控制器都将有对应的IRQCHIP_DECLARE声明,它们都位于__irqchip_of_table段。

因此我们可以了解到__irqchip_of_table数组存储的是中断控制器的信息,用来与设备树节点进行匹配,匹配成功将会执行中断控制器的初始化函数。

3.2.2 of_irq_init

of_irq_init函数定义在drivers/of/irq.c文件,扫描__irqchip_of_table,匹配设备树中定义的中断控制器,匹配成功则调用中断控制器设置的初始化函数。

/**
 * of_irq_init - Scan and init matching interrupt controllers in DT
 * @matches: 0 terminated array of nodes to match and init function to call
 *
 * This function scans the device tree for matching interrupt controller nodes,
 * and calls their initialization functions in order with parents first.
 */
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);    // 初始化链表头 存储设备树中interrupt-controller属性的节点
        INIT_LIST_HEAD(&intc_parent_list);  // 初始化链表头 

        for_each_matching_node_and_match(np, matches, &match) {  // 遍历matchs每个成员,查找设备树中与之匹配的节点,并返回到匹配的结果到match
                if (!of_property_read_bool(np, "interrupt-controller") ||   // 非中断控制器节点
                                !of_device_is_available(np))
                        continue;

                if (WARN(!match->data, "of_irq_init: no init function for %s\n",  // 未指定中断控制器初始化函数
                         match->compatible))
                        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);   // 为中断控制器分配一个of_init_desc结构体
                if (!desc) {
                        of_node_put(np);
                        goto err;
                }

                desc->irq_init_cb = match->data;
                desc->dev = of_node_get(np);
                desc->interrupt_parent = of_irq_find_parent(np);  // 获取父级中断控制器,即解析当前节点的interrupt-parent属性
                if (desc->interrupt_parent == np)             // 父级中断控制器为自身
                        desc->interrupt_parent = NULL;
                list_add_tail(&desc->list, &intc_desc_list);  // 条件到双向链表intc_desc_list
        }
        /*
         * 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) {  // 遍历双向链表intc_desc_list,找到父节点为parent的节点,并将其从intc_desc_list链表移除
// 执行该节点的初始化函数 同时追加到intc_parent_list链表
int ret; // 找到具有父级的中断控制器,第一次parent为NULL,因此找到的是root中断控制器,如intc:interrupt-controller@4a000000 if (desc->interrupt_parent != parent) continue; list_del(&desc->list); // 删除节点 of_node_set_flag(desc->dev, OF_POPULATED); pr_debug("of_irq_init: init %pOF (%p), parent %p\n", desc->dev, desc->dev, desc->interrupt_parent); ret = desc->irq_init_cb(desc->dev, // 执行中断控制器的初始化函数,对root中断控制器,如intc的s3c2410_init_intc_of desc->interrupt_parent); if (ret) { of_node_clear_flag(desc->dev, OF_POPULATED); kfree(desc); continue; } /* * This one is now set up; add it to the parent list so * its children can get processed in a subsequent pass. */ list_add_tail(&desc->list, &intc_parent_list); // 将当前节点追加到链表 } /* Get the next pending parent that might have children */ desc = list_first_entry_or_null(&intc_parent_list, // 获取第一个节点 typeof(*desc), list); if (!desc) { pr_err("of_irq_init: children remain, but no parents\n"); break; } list_del(&desc->list); // 删除节点 parent = desc->dev; // 更新parent为下一级中断控制器 kfree(desc); } list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); kfree(desc); } err: list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { list_del(&desc->list); of_node_put(desc->dev); kfree(desc); } }

其主要流程如下:

(1) 调用for_each_matching_node_and_match,遍历matches数组中每一个of_device_id结构体对象,查找设备树中与其匹配的节点;一旦匹配成功,就会调用设备树框架提供的 of_node_put 函数释放已匹配的节点,然后返回当前匹配结果。

for_each_matching_node_and_match是一个宏定义,它接收三个参数:np、matches和match,其中:

  • np:是对应驱动程序的设备树节点指针;
  • matches:是一个of_device_id结构体数组,用于存储设备树节点与驱动程序匹配的规则;
  • match: 则是 of_device_id 结构体对象,用于存储当前匹配到的结果。

这里matches传入的就是__irqchip_of_table数组,假设数组只有一个元素__clk_of_table_s3c2410_irq,其compatible为samsung,s3c2410-irq,那么会和设备树中如下节点匹配:

intc:interrupt-controller@4a000000 {
     compatible = "samsung,s3c2410-irq";
     reg = <0x4a000000 0x100>;
     interrupt-controller;
     #interrupt-cells = <4>;
};

匹配成功后,np则为该设备节点,match为__clk_of_table_s3c2410_irq。

(2) 匹配成功后,剔除没有interrupt-controller属性描述的节点,剩余的都是中断控制器节点;为每个中断控制器构造一个of_init_desc结构体,并进行初始化:

  •  desc->irq_init_cb = match->data,即s3c2410_init_intc_of;
  • desc->dev = of_node_get(np);
  • desc->interrupt_parent = of_irq_find_parent(np),并调用of_irq_find_parent查找当前中断控制器的父级中断控制器;

of_intc_desc 是一个结构体类型,用于表示一个中断控制器的设备树节点、中断号等属性,定义在drivers/of/irq.c:

struct of_intc_desc {
        struct list_head        list;
        of_irq_init_cb_t        irq_init_cb;
        struct device_node      *dev;         // 中断控制器设备节点
        struct device_node      *interrupt_parent;  // 父级中断控制器设备节点
};

(3) 将当前中断控制器desc添加到全局双向链表intc_desc_list;

(4) 调用list_for_each_entry_safe遍历双向链表intc_desc_list,循环执行如下操作直至intc_desc_list链表尾空:找到父节点为parent的所有子节点,并将其从intc_desc_list链表移除,同时执行执行所有子节点的初始化函数,并将所有子节点同时追加到intc_parent_list链表;

  • 第一次查找时parent为NULL,所有第一次找到的是root中断控制器,然后执行根中断控制器的初始化函数;如intc的__of_table_s3c2410_irq,第一个参数为设备节点intc,第二个参数为intc设备节点的父节中断控制器节点,即NULL;然后获取intc_parent_list中第一个节点赋值给parent,同时从intc_parent_list中移除该节点;
  • 第二次找到的是子中断控制器,然后执行子中断控制器的初始化函数;依次类推.....

假设我们的设备树文件中描述了10个中断控制器,并且被添加到intc_desc_list链表中,如下图:

那么list_for_each_entry_safe这段代码按照interrupt controller的连接关系从root开始,依次初始化每一个interrupt controller。图中每一个圆圈都代表一个interrupt-controller,以此都成了系统的中断树,其中的数字表示的是of_irq_init函数初始化中断控制器的顺序。

实际上由于我们定义的设备树文件中只有主中断控制器设备节点intc:interrupt-controller@4a000000与__irqchip_of_table匹配,因此这段代码会执行s3c2410_init_intc_of函数进行中断控制器的初始化。

3.3 s3c2410_init_intc_of

s3c2410_init_intc_of函数定义在drivers/irqchip/irq-s3c24xx.c文件:

static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = {
        {                       // 主中断控制器
                .name = "intc",
                .offset = 0,
        }, {                    // 子中断控制器   
                .name = "subintc",
                .offset = 0x18,
                .parent = &s3c_intc[0],
        }
};

int __init s3c2410_init_intc_of(struct device_node *np,   // 第一个参数为intc设备节点,第二个参数为NULL
                        struct device_node *interrupt_parent)
{
        return s3c_init_intc_of(np, interrupt_parent,
                                s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
}

由于主中断控制器设备节点intc:interrupt-controller@4a000000只定义了中断控制器的基地址,因此在irq-s3c24xx.c文件s3c2410_ctrl中写死了主、子控制器的偏移位置;

3.3.1 s3c_init_intc_of

s3c_init_intc_of函数用于中断控制器的初始化工作;

static int __init s3c_init_intc_of(struct device_node *np,
                        struct device_node *interrupt_parent,  // NULL
                        struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl)  // num_ctrl=2
{
        struct s3c_irq_intc *intc;
        struct s3c24xx_irq_of_ctrl *ctrl;
        struct irq_domain *domain;
        void __iomem *reg_base;
        int i;

        reg_base = of_iomap(np, 0);  // 获取中断控制器reg属性,并进行地址空间映射;返回0x4a000000的虚拟地址 
        if (!reg_base) {
                pr_err("irq-s3c24xx: could not map irq registers\n");
                return -EINVAL;
        }

// 为中断控制器创建中断域,主中断控制器和子中断控制器共用一个中断域,支持64个中断,对应的irq_domain_ops是s3c24xx_irq_ops_of domain
= irq_domain_add_linear(np, num_ctrl * 32, &s3c24xx_irq_ops_of, NULL); if (!domain) { pr_err("irq: could not create irq-domain\n"); return -EINVAL; } for (i = 0; i < num_ctrl; i++) { // 依次处理两个中断控制器 ctrl = &s3c_ctrl[i]; pr_debug("irq: found controller %s\n", ctrl->name); intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL); // 为主中断控制器和子中断控制器各分配一个s3c_irq_intc if (!intc) return -ENOMEM; intc->domain = domain; // 共用一个中断域 intc->irqs = kcalloc(32, sizeof(struct s3c_irq_data),  // 为主和子中断控制器下的每个中断各分配一个s3c_irq_data结构体 GFP_KERNEL); if (!intc->irqs) { kfree(intc); return -ENOMEM; } if (ctrl->parent) { // 如果是子中断控制器 intc->reg_pending = reg_base + ctrl->offset; //SUBSRCPND intc->reg_mask = reg_base + ctrl->offset + 0x4; // INTSUBMSK if (*(ctrl->parent)) { intc->parent = *(ctrl->parent); } else { pr_warn("irq: parent of %s missing\n", ctrl->name); kfree(intc->irqs); kfree(intc); continue; } } else { // 主中断控制器 intc->reg_pending = reg_base + ctrl->offset; //SECPND intc->reg_mask = reg_base + ctrl->offset + 0x08; // INTMSK intc->reg_intpnd = reg_base + ctrl->offset + 0x10; // INTPND } s3c24xx_clear_intc(intc); // 清除中断挂起 s3c_intc[i] = intc; //  //解析完设备树后,将解析得到中断控制器intc写入s3c_intc } set_handle_irq(s3c24xx_handle_irq); // 设置中断统一入口函数为s3c24xx_handle_irq return 0; }

其主要流程如下:

(1) 映射出中断控制器的基地址;

(2) 调用irq_domain_add_linear为中断控制器创建中断域,并进行部分成员初始化工作,主中断控制器和子中断控制器共用这一个中断域;最终将中断域追加到全局链表irq_domain_list;

(3) 为主中断控制器和子中断控制器各分配一个s3c_irq_intc;

  • 初始化s3c_irq_intc中断相关寄存器:源挂起寄存器、中断屏蔽寄存器、中断挂起寄存器;
  • 清除中断挂起状态;

(4) 设置中断统一入口程序为s3c24xx_handle_irq;在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,即s3c24xx_handle_irq,进行后续的中断处理;

3.3.2 s3c24xx_irq_ops_of

中断域操作集s3c24xx_irq_ops_of函数定义在drivers/irqchip/irq-s3c24xx.c文件:

static const struct irq_domain_ops s3c24xx_irq_ops_of = {
    .map = s3c24xx_irq_map_of,      //建立硬件中断号到IRQ编号时回调该函数,irq_create_mapping函数会调用它
    .xlate = s3c24xx_irq_xlate_of,  //获取中断触发方式,设置具有子中断的主中断源流控处理函数为s3c_irq_demux
};
3.3.3 irq_domain_add_linear

irq_domain_add_linear用于创建线性中断域,函数在include/linux/irqdomain.h中定义:

/**
 * irq_domain_add_linear() - Allocate and register a linear revmap irq_domain.
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: Number of interrupts in the domain.
 * @ops: map/unmap domain callbacks
 * @host_data: Controller private data pointer
 */
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
                                         unsigned int size,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}

该函数接受 4 个参数:

  • of_node:指向设备树节点的指针,该节点表示要部署中断域的中断控制器设备节点;
  • size:新建中断域所需的中断号数量;
  • ops:一个指向 irq_domain_ops 结构体的指针,该结构体定义了中断域的行为;
  • host_data:一个指针,可以传递与中断域相关的任何主机数据,用于设置中断域的host_data私有字段;

调用该函数之后,如果成功,将返回一个指向新创建中断域的指针。而中断号将从全局IRQ编号空间中自动分配,这里的线性中断域是指采用线性表来存储硬件中断号到IRQ编号的映射关系。

__irq_domain_add用于创建一个通用中断域结构体并返回其指针。定义如下:

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

该函数接受 6 个参数:

  • fwnode:表示与中断域相关联的固件节点(Firmware Node);
  • size:新建中断域所需的中断号数量;
  • hwirq_max:硬件中断号的最大值,一般等于size-1;
  • direct_max:直接中断号的最大值,通常设置为0;
  • ops:一个指向 irq_domain_ops 结构体的指针,该结构体定义了中断域的行为;
  • host_data:一个指针,可以传递与中断域相关的任何主机数据,用于设置中断域的host_data私有字段;;

调用该函数之后,如果成功,将返回一个指向新创建中断域的指针。

该函数适用于需要更复杂的中断管理、或支持不同种类的中断控制器的场景。比如,一个 SoC 中可以有多个中断控制器,每个中断控制器都需要一个中断域管理其所管辖的硬件设备的中断号,这时就可以使用 __irq_domain_add创建一个中断域并将其与对应的中断控制器关联。

3.4 总结

我们分析到这里,内核已经完成了根中断控制器节点interrupt-controller@4a000000的注册工作:

  • 为主中断控制器和子中断控制器创建一个支持64个中断的线性中断域struct irq_domain;
  • 为主中断控制器和子中断控制器各分配一个struct s3c_irq_intc,并进行其成员的初始化工作;其中主中断控制器描述的是s3c2440的32个主中断,子中断控制器描述的是s3c2440的15个内部子中断;

注册完成之后,中断域、主中断控制器、子中断控制器数据结构关系如下:

但是我们有没有发现,好像缺了点什么,这里并没有进行:

  • 为中断域添加硬件中断号到IRQ编号的映射;
  • 为外部中断控制器分配s3c_irq_intc;
  • 设备树中串口0、I2C等设备节点均使用了中断,即如何通过interrupt属性获取设备节点的中断资源;

这部分工作会到后面有人引用interrupt-controller@4a000000上的某个中断时再分配和建立,我们下面一一介绍。

四、interrupts属性的解析

在内核启动期间解析设备树,调用of_platform_default_populate_init将device_node转换为platform_device的时候会处理节点中的interrupts属性,将其转换为中断resource,同时在所属的irq domain中建立起硬件中断号到IRQ编号的映射。

serial@50000000节点为例,暂时只关心interrupts属性的处理:

uart0: serial@50000000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50000000 0x4000>;
        interrupts = <1 28 0 4>, <1 28 1 4>;
        status = "disabled";
};      

下面列出主要的函数调用,期间就会调用上面中断控制器的irq_domain的s3c24xx_irq_ops_of中的xlate和map:

of_platform_default_populate_init
    ---> of_platform_default_populate
        ---> of_platform_populate
            ---> of_platform_bus_create
                ---> of_platform_device_create_pdata
                    ---> of_device_alloc  // 为设备节点(Device Node)动态分配一个platform_device结构体
                        ---> of_irq_to_resource_table
                            ---> of_irq_to_resource
                                ---> irq_of_get
                                    ---> of_irq_parse_one
                                    ---> irq_create_of_mapping   
---> irq_create_fwspec_mapping ---> irq_domain_translate ---> s3c24xx_irq_xlate_of ---> irq_create_mapping // 创建hwirq到virq的映射 ---> irq_domain_associate ---> s3c24xx_irq_map_of

4.1 of_device_alloc

of_device_alloc函数根据device_node分配platform_device,函数定义在drivers/of/platform.c:

/**
 * of_device_alloc - Allocate and initialize an of_device
 * @np: device node to assign to device
 * @bus_id: Name to assign to the device.  May be null to use default name.
 * @parent: Parent device.
 */
struct platform_device *of_device_alloc(struct device_node *np,
                                  const char *bus_id,
                                  struct device *parent)
{
        struct platform_device *dev;
        int rc, i, num_reg = 0, num_irq;
        struct resource *res, temp_res;

        dev = platform_device_alloc("", PLATFORM_DEVID_NONE);   // 动态分配平台设备
        if (!dev)
                return NULL;

        /* count the io and irq resources */
        while (of_address_to_resource(np, num_reg, &temp_res) == 0)  // 循环获取设备树节点的第num_req内存资源,即reg属性 统计内存资源的数量,这里为1
                num_reg++;
        num_irq = of_irq_count(np);  // 统计这个节点的interrupts属性中描述了几个中断  针对serial@50000000节点中断资源数量为2

        /* Populate the resource table */
        if (num_irq || num_reg) {
                res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);    // 动态申请资源数组,用于保存内存资源和中断资源
                if (!res) {
                        platform_device_put(dev);
                        return NULL;
                }

                dev->num_resources = num_reg + num_irq;   // 该设备节点描述的内存资源 + 中断资源数量
                dev->resource = res;                      // 设置资源
                for (i = 0; i < num_reg; i++, res++) {
                        rc = of_address_to_resource(np, i, res);  // 获取设备树节点的第num_req内存资源
                        WARN_ON(rc);
                }
                if (of_irq_to_resource_table(np, res, num_irq) != num_irq)  // 解析interrupts属性,将每一个中断转化为resource结构体
                        pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
                                 np);
        }

        dev->dev.of_node = of_node_get(np);  // 成员初始化
        dev->dev.fwnode = &np->fwnode;
        dev->dev.parent = parent ? : &platform_bus;  // 设置父设备

        if (bus_id)
                dev_set_name(&dev->dev, "%s", bus_id);  // 设置设备名称
        else
                of_device_make_bus_id(&dev->dev);

        return dev;
}

这段代码其实很好理解,为设备节点np动态分配平台设备dev,并进行成员初始化;

  • 初始化成员num_resources,resource,通过解析设备节点:将reg属性转为内存资源、将interrupts属性转为中断资源;
  • 初始化成员dev.of_node、dev.parent等;

这里主要涉及到两个函数of_irq_count和of_irq_to_resource_table。

4.1.1 of_irq_count

of_irq_count:这个函数会解析interrupts属性,并统计其中描述了几个中断;

找到serial@50000000节点的所隶属的interrupt-controller,即interrupt-controller@4a000000节点,然后获得其#interrupt-cells属性的值,因为只要知道了这个值,也就知道了在interrupts属性中描述一个中断需要几个参数,也就很容易知道interrupts所描述的中断个数。

of_irq_count函数定义在drivers/of/irq.c,这里关键的函数是of_irq_parse_one:

/**
 * of_irq_count - Count the number of IRQs a node uses
 * @dev: pointer to device tree node
 */
int of_irq_count(struct device_node *dev)
{
        struct of_phandle_args irq;
        int nr = 0;

        while (of_irq_parse_one(dev, nr, &irq) == 0)
                nr++;

        return nr;
}

nr表示的是index,of_irq_parse_one每次成功返回,都表示成功从interrupts属性中解析到了第nr个中断,同时将关于这个中断的信息存放到irq中,struct of_phandle_args的含义如下:

#define MAX_PHANDLE_ARGS 16
struct of_phandle_args {
    struct device_node *np;       // 用于存放赋值处理这个中断的中断控制器的节点 以serial@50000000节点为例,这里就是interrupt-controller@4a000000对应的设备节点
    int args_count;               // 就是interrupt-controller的#interrupt-cells的值  这里就是4
    uint32_t args[MAX_PHANDLE_ARGS];  // 用于存放具体描述某一个中断的参数的值   以serial@50000000节点为例,存放的是[1,28,0 4]或[1,28,1,4]
};

of_irq_parse_one函数定义在drivers/of/irq.c:

/**
 * of_irq_parse_one - Resolve an interrupt for a device
 * @device: the device whose interrupt is to be resolved
 * @index: index of the interrupt to resolve
 * @out_irq: structure of_irq filled by this function
 *
 * This function resolves an interrupt for a node by walking the interrupt tree,
 * finding which interrupt controller node it is attached to, and returning the
 * interrupt specifier that can be used to retrieve a Linux IRQ number.
 */
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
        struct device_node *p;
        const __be32 *addr;
        u32 intsize;
        int i, res;

        pr_debug("of_irq_parse_one: dev=%pOF, index=%d\n", device, index);

        /* OldWorld mac stuff is "special", handle out of line */
        if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
                return of_irq_parse_oldworld(device, index, out_irq);

        /* Get the reg property (if any) */
        addr = of_get_property(device, "reg", NULL);

        /* Try the new-style interrupts-extended first */
        res = of_parse_phandle_with_args(device, "interrupts-extended",
                                        "#interrupt-cells", index, out_irq);
        if (!res)
                return of_irq_parse_raw(addr, out_irq);

        /* Look for the interrupt parent. */
        p = of_irq_find_parent(device);  // 查找"interrupt-parent"属性指定的设备节点(通常是一个中断控制器),从当前节点查找,如果没有,查找父结点,...  
// 以serial@50000000节点为例,这里就是interrupt-controller@4a000000对应的设备节点
if (p == NULL) return -EINVAL; /* Get size of interrupt specifier */ if (of_property_read_u32(p, "#interrupt-cells", &intsize)) { // 获取中断控制器的interrupt-cells属性 res = -EINVAL; goto out; } pr_debug(" parent=%pOF, intsize=%d\n", p, intsize); /* Copy intspec into irq structure */ out_irq->np = p; // 设定为中断所属的中断控制器对应的设备节点 out_irq->args_count = intsize; // 设定描述一个中断需要的参数个数 for (i = 0; i < intsize; i++) { // 读取中断描述参数 res = of_property_read_u32_index(device, "interrupts", (index * intsize) + i, out_irq->args + i); if (res) goto out; } pr_debug(" intspec=%d\n", *out_irq->args); /* Check if there are any interrupt-map translations to process */ res = of_irq_parse_raw(addr, out_irq); out: of_node_put(p); return res; }

该函数接收3个参数:

  • device:需要解析的设备节点;
  • index:因为一个设备可能有多个中断,所以index参数用于确定要解析第几个中断信息;
  • out_irq:存储解析得到的中断信息;

如果解析成功,该函数将返回0。否则,将返回一个负数错误码。对于没有interrupts属性的设备节点,该函数返回一个负数错误码。

4.1.2 of_irq_to_resource_table

知道interrupts中描述了几个中断后,这个函数开始将这些中断转换为resource,这个是由of_irq_to_resource函数完成。函数定义在drivers/of/irq.c:

/**
 * of_irq_to_resource_table - Fill in resource table with node's IRQ info
 * @dev: pointer to device tree node
 * @res: array of resources to fill in
 * @nr_irqs: the number of IRQs (and upper bound for num of @res elements)
 *
 * Returns the size of the filled in table (up to @nr_irqs).
 */
int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
                int nr_irqs)
{
        int i;

        for (i = 0; i < nr_irqs; i++, res++)
                if (of_irq_to_resource(dev, i, res) <= 0)
                        break;

        return i;
}

of_irq_to_resource函数第二个参数i表示的是index,即interrupts属性中的第i个中断。

/**
 * of_irq_to_resource - Decode a node's IRQ and return it as a resource
 * @dev: pointer to device tree node
 * @index: zero-based index of the irq
 * @r: pointer to resource structure to return result into.
 */
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
        int irq = of_irq_get(dev, index);   // 返回interrupts中第index个hwirq中断映射到的virq

        if (irq < 0)
                return irq;

        /* Only dereference the resource if both the
         * resource and the irq are valid. */
        if (r && irq) {                    // 将这个irq封装成resource
                const char *name = NULL;

                memset(r, 0, sizeof(*r));
                /*
                 * Get optional "interrupt-names" property to add a name
                 * to the resource.
                 */
                of_property_read_string_index(dev, "interrupt-names", index,   // 读取""interrupt-names"",这个"interrupt-names"属性是可选的
                                              &name);

                r->start = r->end = irq;
                r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));  // 中断的属性,如上升沿还是下降沿触发
                r->name = name ? name : of_node_full_name(dev);  // 中断名称
        }

        return irq;
}

所以,分析重点是of_irq_get,这个函数我们重点关照一下。

4.2 of_irq_get

of_irq_get函数定义在drivers/of/irq.c,调用该函数会解析 dev 所指向的设备节点中第 index 个中断绑定信息,并将其映射到一个对应的IRQ 编号。如果解析和映射成功,该函数将返回对应的IRQ编号。如果解析或映射失败,则返回 0 或者错误码(如 -EPROBE_DEFER)。

/**
 * of_irq_get - Decode a node's IRQ and return it as a Linux IRQ number
 * @dev: pointer to device tree node
 * @index: zero-based index of the IRQ
 *
 * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
 * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
 * of any other failure.
 */
int of_irq_get(struct device_node *dev, int index)
{
        int rc;
        struct of_phandle_args oirq;
        struct irq_domain *domain;

        rc = of_irq_parse_one(dev, index, &oirq);  //获取dev节点的interrupts属性的第index个中断的参数,保存到oirq中
        if (rc)
                return rc;

        domain = irq_find_host(oirq.np);         // 根据设备节点获取中断域
        if (!domain)
                return -EPROBE_DEFER;

        return irq_create_of_mapping(&oirq);   // 
}

这个函数主要流程如下:

  • 获得serial@50000000节点的interrupts属性的第index个中断的参数,这是通过of_irq_parse_one完成的;
  • 然后获得该中断所隶属的interrupt-controller的irq domain,也就是前面interrupt-controller@4a000000注册的那个irq domain,利用该domain的of_xlate函数从前面表示第index个中断的参数中解析出hwirq和中断类型;
  • 最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中;
4.2.1 irq_create_of_mapping

irq_create_of_mapping用于创建与设备树节点相关联的中断描述符(IRQ Descriptor),该函数接受一个指向设备树节点的指针,返回关联的IRQ编号。函数定义在kernel/irq/irqdomain.c:

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
        struct irq_fwspec fwspec;

        of_phandle_args_to_fwspec(irq_data->np, irq_data->args,   // 将irq_data中的数据转存到fwspec
                                  irq_data->args_count, &fwspec);

        return irq_create_fwspec_mapping(&fwspec);
}
4.2.2 irq_create_fwspec_mapping

irq_create_fwspec_mapping用于创建与irq_fwspec结构体相关联的中断描述符(IRQ Descriptor),同时设置中断触发类型。irq_fwspec结构体包含了执行映射所必要的所有信息,其中 fwnode 字段指向所使用的中断控制器,函数返回这个中断描述符对应的IRQ编号。函数定义在kernel/irq/irqdomain.c:

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;
        
        // 根据中断控制器的device_node找到所对应的irq domain, 在前面调用__irq_domain_add函数为中断控制器注册irq domian的时候,会将irq_domain的fwnode设置为中断控制器的device_node的fwnode成员
        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;
        }

        if (!domain) {
                pr_warn("no irq domain found for %s !\n",
                        of_node_full_name(to_of_node(fwspec->fwnode)));
                return 0;
        }

        // 调用domain->ops->translate(domain, fwspec, hwirq, type), 对于没有定义translate的irq_domain,会调用domain->ops->xlate
        if (irq_domain_translate(domain, fwspec, &hwirq, &type))
                return 0;

        /*
         * WARN if the irqchip returns a type with bits
         * outside the sense mask set and clear these bits.
         */
        if (WARN_ON(type & ~IRQ_TYPE_SENSE_MASK))
                type &= IRQ_TYPE_SENSE_MASK;
        /*
         * If we've already configured this interrupt,
         * don't do it again, or hell will break loose.
         */
        virq = irq_find_mapping(domain, hwirq);  // 从这个irq domain查询看该hwirq之前是否已经映射过,一般情况下都没有
        if (virq) {    // 如果存在的话,判断中断类型是否一致
                /*
                 * If the trigger type is not specified or matches the
                 * current trigger type then we are done so return the
                 * interrupt number.
                 */
                if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
                        return virq;

                /*
                 * If the trigger type has not been set yet, then set
                 * it now and return the interrupt number.
                 */
                if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {    // 之前未设置中断触发类型,则现在中断触发类型
                        irq_data = irq_get_irq_data(virq);
                        if (!irq_data)
                                return 0;

                        irqd_set_trigger_type(irq_data, type);
                        return virq;
                }

                pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n",
                        hwirq, of_node_full_name(to_of_node(fwspec->fwnode)));
                return 0;
        }

        if (irq_domain_is_hierarchy(domain)) {   // 如果domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY,走这个分支   include/linux/irqdomain.h
                virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
                if (virq <= 0)
                        return 0;
        } else { //  走这里
                /* Create mapping */
                virq = irq_create_mapping(domain, hwirq);
                if (!virq)
                        return virq;
        }

        irq_data = irq_get_irq_data(virq);
        if (!irq_data) {
                if (irq_domain_is_hierarchy(domain))
                        irq_domain_free_irqs(virq, 1);
                else
                        irq_dispose_mapping(virq);
                return 0;
        }

        /* Store trigger type */
        irqd_set_trigger_type(irq_data, type);  // 设置中断触发类型

        return virq;  //返回映射到的virq
}

4.3 irq_domain_translate

irq_domain_translate用于将设备树中的中断信息转换为对应的硬件中断号和中断类型,并填充到hwirq和type指针所指向的位置上。函数定义在kernel/irq/irqdomain.c:

static int irq_domain_translate(struct irq_domain *d,
                                struct irq_fwspec *fwspec,
                                irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
        if (d->ops->translate)
                return d->ops->translate(d, fwspec, hwirq, type);
#endif
        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;
}

该函数接受4个参数:

  • d:表示要进行转换的中断域所对应的 struct irq_domain 结构体;
  • fwspec:指向struct irq_fwspec结构体的指针,表示需要进行转换的中断请求相关的数据;由设备节点interrupts属性中某个中断解析得到的;
  • hwirq:指向 irq_hw_number_t 类型变量的指针,用于返回转换得到的硬件中断号;
  • type:指向 unsigned int 类型变量的指针,用于返回转换得到的中断类型(如 IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW 等);

该函数会通过d所表示的中断域的相关信息,结合fwspec中的设备树节点信息,计算出实际的硬件中断号,并将其填充到*hwirq中。同时,根据fwspec中的中断类型信息,将对应的中断类型填充到*type中。

从设备节点interrupts属性中如何解析得到硬件中断号以及中断类型,具体是由中断控制器的irq_domain_ops中的xlate来解释,也就是就是drivers/irqchip/irq-s3c24xx.c中的s3c24xx_irq_xlate_of。

4.3.1 s3c24xx_irq_xlate_of

domain->ops->xlate被定义为s3c24xx_irq_xlate_of,该函数从设备节点interrupts属性中如何解析得到硬件中断号以及中断类型,定义在drivers/irqchip/irq-s3c24xx.c:

/* Translate our of irq notation
 * format: <ctrl_num ctrl_irq parent_irq type>
 */
static int s3c24xx_irq_xlate_of(struct irq_domain *d, struct device_node *n,
                        const u32 *intspec, unsigned int intsize,
                        irq_hw_number_t *out_hwirq, unsigned int *out_type)
{
        struct s3c_irq_intc *intc;
        struct s3c_irq_intc *parent_intc;
        struct s3c_irq_data *irq_data;
        struct s3c_irq_data *parent_irq_data;
        int irqno;

        if (WARN_ON(intsize < 4))   // 中断描述参数个数长度不足 4
                return -EINVAL;

        if (intspec[0] > 2 || !s3c_intc[intspec[0]]) {  // 第一个参数指定中断控制器  0:主中断控制器 1:子中断控制器 2:为NULL
                pr_err("controller number %d invalid\n", intspec[0]);
                return -EINVAL;
        }
        intc = s3c_intc[intspec[0]];    // 获取中断控制器

        *out_hwirq = intspec[0] * 32 + intspec[2];    // 计算硬件中断号 主中断控制器硬件中断号为0:31 子中断控制器硬件中断号为32:63
        *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;  // 中断类型

        parent_intc = intc->parent;
        if (parent_intc) {  // 如果是子中断控制器
                irq_data = &intc->irqs[intspec[2]];                           // 获取子中断源数据
                irq_data->parent_irq = intspec[1];                            // 设置主中断硬件编号 
                parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];   // 获取子中断对应的主中断源数据
                parent_irq_data->sub_intc = intc;                             // 设置子中断中断控制器
                parent_irq_data->sub_bits |= (1UL << intspec[2]);             // 设置子中断偏移位,该主中断可能对应多个子中断,所以使用|  

                /* parent_intc is always s3c_intc[0], so no offset */
                irqno = irq_create_mapping(parent_intc->domain, intspec[1]);  // 为主中断动态分配IRQ编号
                if (irqno < 0) {
                        pr_err("irq: could not map parent interrupt\n");
                        return irqno;
                }

                irq_set_chained_handler(irqno, s3c_irq_demux);  // 设置子中断的主中断源的中断流控处理函数
        }

        return 0;
}

函数包含6个参数:

  • d:表示要进行转换的中断域所对应的 struct irq_domain 结构体;
  • n:引用中断的设备节点,比如serial@50000000节点;
  • intspec:设备节点interrupts属性中某个中断的值,指向u32类型,比如<1 28 0 4>,指向一个数组内容为[1,28,0,4]; 
  • intsize:描述一个中断所需要的参数个数,比如:#interrupt-cells=4,该值就为4;
  • out_hwirq:保存从设备节点中断信息中解析到的硬件中断号;比如:<1 28 0 4>,得到的值为1*32 + 0 = 32;
  • out_type:保存从设备节中断信息解析到的中断类型;

如果是子中断,这里会调用irq_set_chained_handler设置主中断所对应的irq_desc->handle_irq回调函数;

  • 主要是因为子中断控制器在把多个irq汇集起来后,输出端连接到根中断控制器的其中一个irq中断线输入脚,这意味着,每个子中断控制器的中断发生时,CPU一开始只会得到根中断控制器的irq编号,然后进入该irq编号对应的irq_desc->handle_irq回调,该回调我们不能使用流控层定义好的几个流控函数,而是要自己实现一个函数,比如这里的s3c_irq_demux函数:
  • s3c_irq_demux函数负责从子中断控制器中获得irq的中断源,并计算出对应新的irq编号,然后调用新irq所对应的irq_desc->handle_irq回调,这个回调使用流控层的标准实现;

4.4 irq_create_mapping

irq_create_mapping函数为中断域某个硬件中断号动态分配IRQ编号的函数;函数定义在kernel/irq/irqdomain.c:

/**
 * irq_create_mapping() - Map a hardware interrupt into linux irq space
 * @domain: domain owning this hardware interrupt or NULL for default domain
 * @hwirq: hardware irq number in that domain space
 *
 * Only one mapping per hardware interrupt is permitted. Returns a linux
 * irq number.
 * If the sense/trigger is to be specified, set_irq_type() should be called
 * on the number returned from that call.
 */
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);

        /* Look for default domain if nececssary */
        if (domain == NULL)
                domain = irq_default_domain;
        if (domain == NULL) {
                WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
                return 0;
        }
        pr_debug("-> using domain @%p\n", domain);

        of_node = irq_domain_get_of_node(domain);

        /* Check if mapping already exists */
        virq = irq_find_mapping(domain, hwirq);   // 查找hwirq对应的virq
        if (virq) {                               // 映射已经存在  
                pr_debug("-> existing mapping on virq %d\n", virq);
                return virq;
        }
        /* Allocate a virtual interrupt number */
        virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);  // 为硬件中断动态申请中断描述符,将其与hwirq关联,同时为每个硬件中断号申请一个全局IRQ编号,并返回IRQ编号
        if (virq <= 0) {
                pr_debug("-> virq allocation failed\n");
                return 0;
        }

        if (irq_domain_associate(domain, virq, hwirq)) {   // 为中断域添加hwirq到virq的映射
                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;
}

irq_create_mapping函数输入参数是中断域和硬件中断号,返回IRQ编号。该函数:

  • 首先判断hwirq映射是否存在,如果存在,直接返回;
  • 调用irq_domain_alloc_descs为硬件中断动态申请中断描述符,将其与hwirq关联,同时为硬件中断号申请一个全局IRQ编号,并返回IRQ编号;
  • 然后调用irq_domain_associate把硬件中断号到IRQ编号的映射添加到中断域;同时调用domain->ops->map回调,以便驱动程序可以执行任何所需的硬件设置;

关于irq_domain_associate函数我们在linux驱动移植-中断子系统执行流程文章中已经介绍过,函数调用流程:

irq_create_mapping(domain, hwirq) 
     virq  = irq_find_mapping(domain, hwirq)    // 判断hwirq映射是否存在,如果存在,直接返回
     irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL) // 为硬件中断动态申请中断描述符
     irq_domain_associate(domain, virq, hwirq)   // 为中断域添加hwirq到virq的映射
         domain->ops->map(domain, virq, hwirq)   // 回调domain操作集map函数  
         irq_domain_set_mapping(domain, hwirq, irq_data); // 把硬件中断号到IRQ编号的映射添加到中断域的线性表linear_revmap  
4.4.1 s3c24xx_irq_map_of

domain->ops->map被定义为s3c24xx_irq_map_of,定义在drivers/irqchip/irq-s3c24xx.c:

static int s3c24xx_irq_map_of(struct irq_domain *h, unsigned int virq,
                            irq_hw_number_t hw)
{
    unsigned int ctrl_num = hw / 32;     // 判断该hwirq属于主还是子中断控制器 0主 1子
    unsigned int intc_hw = hw % 32;      // 得到中断寄存器对应的硬件中断号
    struct s3c_irq_intc *intc = s3c_intc[ctrl_num];         // 获得中断控制器对应的结构体
    struct s3c_irq_intc *parent_intc = intc->parent;
    struct s3c_irq_data *irq_data = &intc->irqs[intc_hw];   // 每个中断控制器都会有一个s3c_irq_data数组结构体

    /* attach controller pointer to irq_data */
    irq_data->intc = intc;
    irq_data->offset = intc_hw;         // 中断寄存器对应的硬件中断号,也就是对应的位号

    // 将virq对应的irq_desc的handle_irq初始化为handle_edge_irq,下面在s3c_irq_type中会根据中断触发类型再次修改
    if (!parent_intc)   // 主中断控制器,走这里
        irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq);  // 为IRQ编号为virq的中断设置中断流控处理函数、以及中断控制器(struct irq_chip)
    else       // 子中断控制器,走这里
        irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
                     handle_edge_irq);

    irq_set_chip_data(virq, irq_data);

    return 0;
}

第一个参数为中断域、第二个参数为IRQ编号,第三个为硬件中断号。这里做了以下事情s3c_irq_data:

(1) 根据硬件中断号获取主/子中断控制器对应的s3c_irq_intc;

(2) 根据硬件中断号intc_hw从中断控制器的irqs获取中断源信息,类型为s3c_irq_data;

(3) 然后调用irq_set_chip_and_handler,设置其成员handle_irq回调和irq_chip指针;

s3c24xx_irq_map函数根据是主中断控制器,还是子中断控制器,为中断描述符绑定不同的struct irq_chip;比如:

  • 对于主中断控制器下的中断,其对应的irq_desc->irq_data.chip设置为s3c_irqext_chip,同时 irq_desc->handle_irq设置为handle_edge_irq;
  • 对于子中断控制器下的中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_level_chip,同时 irq_desc->handle_irq设置为handle_edge_irq;
4.4.2 irq_chip

上面主和子中断控制虽然使用的时同一个irq domain但是对应的irq_chip却是各自的。

static struct irq_chip s3c_irq_chip = {   // 主
        .name           = "s3c",
        .irq_ack        = s3c_irq_ack,
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_set_type   = s3c_irq_type,
        .irq_set_wake   = s3c_irq_wake
};

static struct irq_chip s3c_irq_level_chip = {  // 子
        .name           = "s3c-level",
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_ack        = s3c_irq_ack,
        .irq_set_type   = s3c_irq_type,
};

在s3c_irq_mask中屏蔽中断的时候,如果屏蔽的是子中断,还需要判断目前该子中断所属的主中断是不是还有其它子中断在使用,如果没有的话,也会把该主中断也给屏蔽了。

static void s3c_irq_mask(struct irq_data *data)  // data为当前中断的数据
{
        struct s3c_irq_data *irq_data = irq_data_get_irq_chip_data(data);
        struct s3c_irq_intc *intc = irq_data->intc;
        struct s3c_irq_intc *parent_intc = intc->parent;  // 父中断控制器
        struct s3c_irq_data *parent_data;
        unsigned long mask;
        unsigned int irqno;

        mask = readl_relaxed(intc->reg_mask);
        mask |= (1UL << irq_data->offset);
        writel_relaxed(mask, intc->reg_mask);

        if (parent_intc) {   // 如果是子中断,判断该子中断所属的主中断是不是还有其它子中断在使用,如果没有的话,也会把该主中断也给屏蔽了
                parent_data = &parent_intc->irqs[irq_data->parent_irq];

                /* check to see if we need to mask the parent IRQ
                 * The parent_irq is always in main_intc, so the hwirq
                 * for find_mapping does not need an offset in any case.
                 */
                if ((mask & parent_data->sub_bits) == parent_data->sub_bits) {
                        irqno = irq_find_mapping(parent_intc->domain,
                                         irq_data->parent_irq);
                        s3c_irq_mask(irq_get_irq_data(irqno));
                }
        }
}

同样,s3c_irq_unmask在打开子中断时也会将其所属的主中断也打开。

4.5 总结

linux内核启动过程中在将device node转换为platform_device的过程中,会对interrupts属性进行处理:

  • 获取所指定的中断控制器s3c_irq_intc、以及中断域irq_domain;
  • 在中断域上为硬件中断动态申请中断描述符,将其与hwirq关联,同时为硬件中断号申请一个全局IRQ编号;
  • 将硬件中断号到IRQ编号的映射添加到中断域;

仍然以serial@50000000设备节点为例,处理完成之后,中断域、主中断控制器、子中断控制器、平台设备、资源、中断描述符数据结构关系如下:

需要注意的是:设备树中在描述中断信息时,interrupts第3个参数:

  • 对于主中断,第三个参数描述的是主中断的硬件中断号0~31,由于主/子中断控制器共用了一个线性中断域,对应到中断域中的硬件中断号为0~31;
  • 对于子中断,第三个参数描述的是子中断的硬件中断号0~31,由于主/子中断控制器共用了一个线性中断域,对应到中断域中的硬件中断号为32~63;

至此,device node在转化为platform_device过程中的interrupts属性的处理就暂时分析完毕,后面会注册该platform_device,然后匹配到的platform_driver的probe就会被调用。

五、设备驱动申请中断

5.1 串口中断

在drivers/tty/serial/samsung.c中可以搜索关键字 “samsung,s3c2440-uart”;当platform设备和驱动匹配后,s3c24xx_serial_probe函数被调用。

在s3c24xx_serial_probe函数会调用s3c24xx_serial_init_port函数,而该函数进行中断申请:

ret = platform_get_irq(platdev, 0); // UART0 receive interrupt
ret = platform_get_irq(platdev, 1); // UART0 transmit interrupt

5.2 I2C控制器

在drivers/i2c/busses/i2c-s3c2410.c中可以搜索关键字 “samsung,s3c2440-i2c”;当platform设备和驱动匹配后,s3c24xx_i2c_probe函数被调用。

在函数s3c24xx_i2c_probe中会申请中断:

i2c->irq = ret = platform_get_irq(pdev, 0); 

5.3 查看系统信息

开发板上电,内核启动后,可以从/proc/interrupts中看到当前的中断资源申请信息:

[root@zy:/]# cat /proc/interrupts
           CPU0
  7:       1320  s3c-eint   7 Edge      eth0
  8:          0       s3c   8 Edge      s3c2410-rtc tick
 13:        700       s3c  13 Edge      samsung_time_irq
 27:          4       s3c  27 Edge      54000000.i2c
 30:          0       s3c  30 Edge      s3c2410-rtc alarm
 32:          9  s3c-level  32 Level     50000000.serial
 33:         60  s3c-level  33 Level     50000000.serial
 59:          0  s3c-level  59 Edge      53000000.watchdog
Err:          0

下面解释一下上面这些参数的含义:

六、总结

到了这里,我们已经已经完成了根中断控制器节点interrupt-controller@4a000000的注册工作

  • 为主中断控制器和子中断控制器创建一个支持64个中断的线性中断域struct irq_domain;
  • 为主中断控制器和子中断控制器各分配一个struct s3c_irq_intc,并进行其成员的初始化工作;其中主中断控制器描述的是s3c2440的32个主中断,子中断控制器描述的是s3c2440的15个内部子中断;

同时也完成了对设备节点interrupts属性的解析,仅限于interrupt-parent = <&intc>的设备节点:

  • 获取所指定的中断控制器s3c_irq_intc、以及中断域irq_domain;
  • 在中断域上为硬件中断动态申请中断描述符,将其与hwirq关联,同时为硬件中断号申请一个全局IRQ编号;
  • 将硬件中断号到IRQ编号的映射添加到中断域;

s3c2440除了主中断、15个内部子中断,还有24个外部中断,也就是gpf、gpg中断控制器节点的注册,以及引用外部中断设备节点interrupts属性的解析在这篇博客并没有介绍。

pinctrl@56000000节点在转化成platform_device被注册的时候samsung_pinctrl_probe会被调用,外部中断中断控制器的注册实际是在samsung_pinctrl_probe函数中完成的,这一块内容我们在下一篇博客linux设备树-pin控制器驱动会说到。

参考文章

[1]基于设备树的TQ2440的中断(1)

[2]基于设备树的TQ2440的中断(2)

[3]基于设备树的中断实现 (24x0平台)

[4]基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)

[5]基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(2)

[6]第五课. 中断系统中的设备树

posted @ 2023-04-25 23:48  大奥特曼打小怪兽  阅读(546)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步