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>; }; };
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)) }
此外这里还定义一个名为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
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2018-04-25 第九节,线性逻辑回归