一、内核中关于中断控制器的几个概念 在介绍中断控制器的注册前先介绍内核中关于中断控制器几个知识点:
1.1 IRQCHIP_DECLARE 用于实现中断控制器的of_device_id结构,该数据结构会在__irqchip_of_table内核代码段。 这段代码的作用就是创建一个结构体,名为 __#table_of_table,table是传入的参数,从OF_DECLARE_2定义看,也就是irqchip,所以最后的结构体名为__irqchip_of_table
1 2 3 4 5 6 7 8 9 10 11 12 #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" ) \ __aligned(__alignof__(struct of_device_id)) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn }
1.2 IRQCHIP_OF_MATCH_TABLE CONFIG_IRQCHIP被设置为y,所以 OF_TABLE(CONFIG_IRQCHIP, irqchip) -> __OF_TABLE(1, irqchip) -> ___OF_TABLE(1, irqchip) -> _OF_TABLE_1(irqchip) 所以宏的意义为定义了变量__irqchip_of_table、并指定了该变量在内核代码段的空间,其起始地址__irqchip_of_table段的开始、结束地址为__irqchip_of_table_end段起始地址
1 2 3 4 5 6 7 8 9 10 11 12 #define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip) #define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name) #define __OF_TABLE(cfg, name) ___OF_TABLE(cfg, name) #define OF_TABLE(cfg, name) __OF_TABLE(IS_ENABLED(cfg), name) #define _OF_TABLE_0(name) #define _OF_TABLE_1(name) \ . = ALIGN(8); \ __##name##_of_table = .; \ KEEP(*(__##name##_of_table)) \ KEEP(*(__##name##_of_table_end))
1.3 __irqchip_of_table __irqchip_of_table 数组中存放了通过IRQCHIP_DECLARE定义的个中断控制器的of_device_id结构数据。通过__irqchip_of_table和irqchip_of_match_end变量可以知道系统中的所有的控制器的of_device_id结构数据。 因为__irqchip_of_table section在init data段,所以该段的数据内核完成init后其也会被释放,故__irqchip_of_table、irqchip_of_match_end变量也在只在系统初始化完成前有意义。
1 2 3 4 5 6 7 extern struct of_device_id __irqchip_of_table [];void __init irqchip_init (void ) { of_irq_init(__irqchip_of_table); acpi_probe_device_table(irqchip); }
二、中断控制器的注册 首先我们要确认的一点,中断控制器不是一定只有一个的,中断控制器支持级联。但是root中断控制器只有一个,那就是GIC中断控制器,其余的中断控制器都是挂载在GIC上的。 而级联中断控制器的中断触发流程如下图 下面分这两种情况介绍中断控制器的注册,分为root中断控制器(也就是GIC)和级联中断控制(会找一个作为示范)
2.1 GIC中断控制器注册 目前我们公司所开发的项目的代码,该系统的根中断控制器为gicv3,gicv3通过IRQCHIP_DECLARE(gic_v3, “arm,gic-v3”, gic_of_init)宏实现了of_device_id结构__of_table_gic_v3变量的定义,该变量链接到__irqchip_of_table 数据段、系统通过__irqchip_of_table 数组来访问__irqchip_of_table 数据段、进而间接的实现了__of_table_gic_v3->gic_of_init的访问
2.1.1 系统启动到GICv3初始化开始的过程 start_kernel -> init_IRQ -> irqchip_init -> of_irq_init(__irqchip_of_table) -> gic_of_init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 ; 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); } while (!list_empty(&intc_desc_list)) { 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 ; } list_add_tail(&desc->list , &intc_parent_list); } 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); } }
这里贴一下GIC的devicetree的节点,取自我司warm代码blair.dtsi
1 2 3 4 5 6 7 8 9 10 intc: interrupt-controller@f200000 { compatible = "arm,gic-v3"; #interrupt-cells = <3>; interrupt-controller; #redistributor-regions = <1>; redistributor-stride = <0x0 0x20000>; reg = <0xf200000 0x10000>, /* GICD */ <0xf240000 0x100000>; /* GICR * 8 */ interrupts = <GIC_PPI 8 IRQ_TYPE_LEVEL_HIGH>; };
2.1.2 GICv3中断控制器的初始化 gic_of_init -> gic_init_bases -> irq_domain_create_tree -> __irq_domain_add
gic_of_init函数是GICv3中断控制器的初始化过程的开始,该函数除了通过解析设备树种的节点,完成struct gic_chip_data gic_data数据结构的初始化及distributor、CPU interface、PM等初始化外 另一个重要的是将GICv3中断控制器以irq 。 这部分的代码不再本文进行分析,详细可看
2.2 级联的中断控制器的注册 这里以高通平台的tlmm pinctrl控制器来作为演示
2.2.1 设备树节点 1 2 3 4 5 6 7 8 9 10 11 tlmm: pinctrl@400000 { compatible = "qcom,pitti-pinctrl"; // 与驱动匹配 reg = <0x400000 0x1000000>; interrupts = <GIC_SPI 227 IRQ_TYPE_LEVEL_HIGH>; // 表示其连接在GIC中断控制器的227号共享类型中断上 gpio-controller; // 标明是一个gpio控制器 #gpio-cells = <2>; interrupt-controller; // 标明是一个中断控制器 #interrupt-cells = <2>; wakeup-parent = <&mpm>; qcom,gpios-reserved = <36 37 38 39>; };
2.2.2 tlmm pinctrl中断控制器驱动 code: lc-u-warm-vendor/kernel_platform/msm-kernel/drivers/pinctrl/qcom/pinctrl-pitti.c 这部分不详细介绍了,每一个驱动的走向都是不一样的,但是整体的设计思路是一致的。
irq_domain的创建
irq number与硬件中断号的map
填充irq_desc结构体
2.3 级联情况的中断处理流程 中断处理过程会先经过parent interrupt-controller的处理然后到 child interrupt controller,与级联中断的触发过程相反,触发时中断信号会由child interrupt-controller 到 parent interrupt-controller。 中断处理时target processor接收到的来自GIC的中断,通过调用GIC注册到系统的中断处理函数gic_handle_irq获取具体的中GIC硬件中断号,再通过GIC的硬件中断号获取其对应的虚拟中断gic_virq,进而获取到该virq对应的handle_irq。 通过中断的级联关系(级联中断控制器与GIC的该硬件中断相连接)可知此处的gic_virq->handle_irq对应child interrupt-contoller的对应的中断处理函数,获取对应的中断号和其虚拟中断号,进而执行handle_irq处理连接到级联该中断的处理函数。