Linux级联中断控制器注册与中断处理

https://hexoimg.oss-cn-shanghai.aliyuncs.com/blog/24/11/image_fab373f25dba54cbc43f27389179066b.png

特别提醒:本文章所有涉及的代码均可在linux-5.15处查看源码。

一、内核中关于中断控制器的几个概念

在介绍中断控制器的注册前先介绍内核中关于中断控制器几个知识点:

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上的。
1731640793807.png
而级联中断控制器的中断触发流程如下图
1731641815037.png
下面分这两种情况介绍中断控制器的注册,分为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);

// 从系统devicetree的根节点开始,遍历系统所有的device node,查找与matches数组相匹配device node
for_each_matching_node_and_match(np, matches, &match) {
// 查找compatible="interrupt-controller"的节点
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np)) //判断节点是否有效,节点是否被标记为可用(status = "okay";)
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;
}

//这个match->data就是指的IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);的第三个参数初始化函数
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);

/* 获取该中断控制器的父中断控制器node,由该中断控制器device node中的
interrupt-parent property指定 */
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.
*/

/* GIC中断控制器的node中的interrupt-parent property指向其自身,所以GIC中断控制器的描
述符号中的interrupt-parent变量为NULL */
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);
}
}

这里贴一下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
这部分的代码不再本文进行分析,详细可看

特别提醒:本文章所有涉及的代码均可在__irq_domain_add处查看源码。

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
这部分不详细介绍了,每一个驱动的走向都是不一样的,但是整体的设计思路是一致的。

  1. irq_domain的创建
  2. irq number与硬件中断号的map
  3. 填充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处理连接到级联该中断的处理函数。