Linux4.x之Gpio分析(二)中断控制器初始化

内核启动流程

开始讲gpiolib中的irq相关的内容的内容首先要把启动的流程之前,我们知道存在一共叫做中断控制器的模块。
那么我们先看看中断控制器是如何工作的在来看GPIO中的中断与之的联系。
首先。我们把启动的调用脉络给列一下,方便后后面分析。

简化的内核启动流程

我们把启动的流程总结一下:

  1. 通过设备树找到板配置 machine_desc
  2. 解析设备树获得struct device_node of_root
  3. 初始化中断(中断控制器)
  4. 初始化各种板级设备
  5. 启动init程序

源码跟踪

获取板配置的过程不再分析了,这里我们只关注我们板子的配置文件。

1
2
3
4
5
6
7
8
9
10
//arch/arm/mach-imx/mach-imx6q.c
DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)")
.smp = smp_ops(imx_smp_ops),
.map_io = imx6q_map_io,
.init_irq = imx6q_init_irq,
.init_machine = imx6q_init_machine,
.init_late = imx6q_init_late,
.dt_compat = imx6q_dt_compat,
.reserve = imx6q_reserve,
MACHINE_END

这里我们去看看imx6q_init_irq具体做了什么事:

1
2
3
4
5
6
7
8
9
//arch/arm/mach-imx/mach-imx6q.c
static void __init imx6q_init_irq(void)
{
imx_gpc_check_dt();
imx_init_revision_from_anatop();
imx_init_l2cache();
imx_src_init();
irqchip_init();
}

这里我们关注第4和8行和中断控制器的初始化有关。
imx_gpc_check_dt
imx_src_init
其余的都是芯片本身的初始化,并不需要关心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//arch/arm/mach-imx/gpc.c
static void __iomem *gpc_base;
OF_DECLARE_2(irqchip, imx_gpc, "fsl,imx6q-gpc", imx_gpc_init);

void __init imx_gpc_check_dt(void)
{
struct device_node *np;

np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc");
if (WARN_ON(!np))
return;

if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) {
pr_warn("Outdated DT detected, suspend/resume will NOT work\n");

/* map GPC, so that at least CPUidle and WARs keep working */
gpc_base = of_iomap(np, 0);
}
}

imx_gpc_check_dt做的事比较简单,初始化中断控制器的地址。
我们再关注一下宏:OF_DECLARE_2

1
2
3
4
5
6
7
8
9
10
#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 }

typedef int (*of_init_fn_2)(struct device_node *, struct device_node *);

展开宏可得:

1
2
3
4
static const struct of_device_id __of_table_imx_gpc      __used __section(__irqchip_of_table) = 
{ .compatible = "fsl,imx6q-gpc",
.data = imx_gpc_init
}

这里我相信这个结构体__of_table_imx_gpc肯定是会用到了,先放一下。
我们看看另外的一个我们关注的函数irqchip_init()函数,具体做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13

//drivers/irqchip/irqchip.c
static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_table_end);

extern struct of_device_id __irqchip_of_table[];

void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table);

acpi_irq_init();
}

果然就用到了,__irqchip_of_table 的section是放到.init.data中,具体可以看一下: arch/arm/kernel/vmlinux.lds
这里我们发现了一个问题:
在driver/irqchip大量使用IRQCHIP_DECLARE来,为什么imx为什么自己又定义出了一个宏呢?
注释中解释了原因,我们不应该使用驱动目录下头文件,所以不得已为之,也说明了这样做不是很好!
既然这样我们也顺道看看IRQCHIP_DECLARE,结果发现根本就是一回事!
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

1
2
3
4
/*
* We cannot use the IRQCHIP_DECLARE macro that lives in
* drivers/irqchip, so we're forced to roll our own. Not very nice.
*/

我们回到函数irqchip_init中调用了of_irq_init
根据这个名字of是对设备树的操作,然后参数是irqchip_of_table,这里我们不难猜这个函数的作用。
大胆的猜测 函数将加入到
irqchip_of_table中的IRQ控制器和设备树进行匹配后初始化对应的控制器。

在调用之前我们先来看看设备树中我们定义了那些IRQ控制?
好!这个检索的过程我就卖个苦力来对比一下。

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
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a00100 0x100>;
interrupt-parent = <&intc>;
};


gpio1: gpio@0209c000 {
compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
<0 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

gpc: gpc@020dc000 {
compatible = "fsl,imx6q-gpc";
reg = <0x020dc000 0x4000>;
interrupt-controller;
#interrupt-cells = <3>;
interrupts = <0 89 IRQ_TYPE_LEVEL_HIGH>,
<0 90 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&intc>;
pu-supply = <&reg_pu>;
clocks = <&clks IMX6QDL_CLK_GPU3D_CORE>,
<&clks IMX6QDL_CLK_GPU3D_SHADER>,
<&clks IMX6QDL_CLK_GPU2D_CORE>,
<&clks IMX6QDL_CLK_GPU2D_AXI>,
<&clks IMX6QDL_CLK_OPENVG_AXI>,
<&clks IMX6QDL_CLK_VPU_AXI>;
#power-domain-cells = <1>;
};
fec: ethernet@02188000 {
compatible = "fsl,imx6q-fec";
reg = <0x02188000 0x4000>;
interrupts-extended =
<&gpc 0 118 IRQ_TYPE_LEVEL_HIGH>,
<&gpc 0 119 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_ENET>,
<&clks IMX6QDL_CLK_ENET>,
<&clks IMX6QDL_CLK_ENET_REF>;
clock-names = "ipg", "ahb", "ptp";
stop-mode = <&gpr 0x34 27>;
fsl,wakeup_irq = <0>;
status = "disabled";
};

这里统计了一下设备树的interrupt控制器三类:

  • gic “arm,cortex-a9-gic”
  • gpio”fsl,imx6q-gpio”, “fsl,imx35-gpio”
  • gpc”fsl,imx6q-gpc”;

代码中:
irq-gic.c:1037:IRQCHIP_DECLARE(cortex_a9_gic, “arm,cortex-a9-gic”, gic_of_init);
gpc.c:571:OF_DECLARE_2(irqchip, imx_gpc, “fsl,imx6q-gpc”, imx_gpc_init);

这里 gpc接在gic上,从设备树中的interrupt-parent可以看出
这里我们就记住了需要初始化的只有GIC和GPC,这里我们去看看of_irq_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
//drivers/of/irq.c
void __init of_irq_init(const struct of_device_id *matches)
{
struct device_node *np, *parent = NULL;
struct 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(np, matches) {
if (!of_find_property(np, "interrupt-controller", NULL) ||
!of_device_is_available(np))
continue;
/*
* Here, we allocate and populate an intc_desc with the node
* pointer, interrupt-parent device_node etc.
*/
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (WARN_ON(!desc))
goto err;

desc->dev = 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) {
const struct of_device_id *match;
int ret;
of_irq_init_cb_t irq_init_cb;

if (desc->interrupt_parent != parent)
continue;

list_del(&desc->list);
match = of_match_node(matches, desc->dev);
if (WARN(!match->data,
"of_irq_init: no init function for %s\n",
match->compatible)) {
kfree(desc);
continue;
}

pr_debug("of_irq_init: init %s @ %p, parent %p\n",
match->compatible,
desc->dev, desc->interrupt_parent);
irq_init_cb = (of_irq_init_cb_t)match->data;
ret = irq_init_cb(desc->dev, desc->interrupt_parent);
if (ret) {
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);
kfree(desc);
}
}

这个代码比较长,逻辑还是比较简单的。

  1. 从设备树中找到所用的中断控制器
  2. 匹配__irqchip_of_table中的设备(也就是中断控制器),依次从父设备-子设备的顺序进行初始化。

中断控制器介绍

于是下面我们就看看GIC和GPC是做什么用的?

首先需要介绍一下GIC和GPC

GIC : General Interrupt Control
这个玩意儿是ARM做的通用的中断控制器,就不详细描述它了,可以上网搜搜GIC V2的资料。
简单的说:
ARM CPU 对外的连接只有 2 个中断,一个是 IRQ ,一个是 FIQ ,相对应的处理模式分别是一般中断( IRQ )处理模式和快速中断( FIQ )处理模式。所以 GIC 最后要把中断汇集成 2 条线,与 CPU 对接。

GPC: General Power Control
这里我们很难想象通用电源控制器和中断有什么关系?
enter description here
enter description here

这里GPC中包含了一个中断控制器的子模块,从结构图中我们看出主要是用来做 通知CCM(时钟控制模块)/CPU用的。

客官,小的这记口碎大石值几个钱?