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

发布时间 2023-04-25 23:52:45作者: 大奥特曼打小怪兽

我们在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 Iint 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 Iint 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 设备树

2.1.1 中断控制器节点

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

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

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

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

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

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

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

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

针对于I2C,引用的是主中断:<0 0 27 3>:

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

针对于串口0,引用的是子中断:<1 28 0 4>:

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

针对于串口0,引用的是子中断:<1 28 1 4>:

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

2.2 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

2.3 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函数对设备树文件中每一个中断控制器节点,调用对应的处理函数;为每一个符合的"interrupt-controller"节点调用处理函数。

先调用根中断控制器对应的函数, 再调用子控制器的函数。

2.3.1 of_irq_init

of_irq_init函数定义在drivers/of/irq.c文件:

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

                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);
                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);
                if (desc->interrupt_parent == np)
                        desc->interrupt_parent = NULL;
                list_add_tail(&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) {
                        int ret;

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

其主要流程如下::

//建立中断控制器desc数据结构体,调用中断控制器注册的初始化程序,
//来建立父子中断控制器之间的联系
of_irq_init    
{
    //遍历符合compatible的设备节点
    //例子:遍历所有符合compatible = "samsung,s3c2410-irq"节点
    for循环(匹配设备树的输入节点)
    {
        1.符合compatible的节点中,找"interrupt-controller"属性的节点
        找中断控制器:匹配设备树输入节点的"interrupt-controller"属性
        
        //match =__of_table_s3c2410_irq, 即match->data 调用__of_table_s3c2410_irq
        2.分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // 驱动中IRQCHIP_DECLARE宏:match->data=init*irq函数
        
        3.创建of_intc_desc的中断控制器desc结构体,并持续添加进循环双链表
    }
    
    //遍历刚刚的循环双链表
    //1.先找出主中断控制器,调用驱动init*irq
    //2.再找出子中断控制器,调用驱动init*irq,建立和中断控制器的联系
    while (!list_empty(&intc_desc_list))//
}

其中:

#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 __of_table_s3c2410_irq     \
        __used __section("__irqchip_of_table")          \
         = { .compatible = "samsung,s3c2410-irq",               \
             .data = s3c2410_init_intc_of  }
2.3.2 中断域的创建

为每一个中断控制器分配irq_domain,并进行初始化,最后追加到全局链表irq_domain_list;中断域存储了硬件中断号到IRQ编号的映射,对于设备树的场景,硬件中断号和IRQ编号是随机的;

通过查询irq_domain,可以获取硬件中断号和IRQ编号之间的联系:

irq_domain是核心:
a. 每一个中断控制器都有一个irq_domain
b. 对设备中断信息的解析, 
b.1 需要调用 irq_domain->ops->xlate (即从设备树中获得hwirq, type)
b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq;
b.3 在hwirq和virq之间建立联系:
   要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一个分发函数还是可以直接处理中断)
        irq_desc[virq].handle_irq = 常规函数;
   如果这个hwirq有上一级中断, 假设它的中断号为virq', 还要设置: 
        irq_desc[virq'].handle_irq = 中断分发函数;

三、interrupts属性的解析

参考文章

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

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

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