我们在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 设备树
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属性的解析
参考文章
标签:中断,irq,list,INT,init,linux,控制器驱动,desc From: https://www.cnblogs.com/zyly/p/17353184.html