最近在研究ARM Linux中断子系统,发现需要补习的中断子系统各个模块知识还挺多的,本文基于此方向下较为详细的分析了中断控制器GIC的软硬件原理以及以IMX6UL为例追踪分析GIC相关代码。
注:本文所分析的Linux内核代码版本为 Linux 4.1.15
GIC硬件部分
GIC 中断类型:外设中断、软件中断、虚拟中断、Maintenance interrupt
我只关注外设中断和软件中断,外设中断有如下中断类型:
- PPI(Private Peripheral Interrupt):PPI只能分发给特定CPU,ID16 - ID31
用于PPI - SPI(Shared Peripheral Interrupt):Distributor可以路由给任意的CPU其中一个处理,中断号
ID32 - ID1019
外设类型的中断一般通过一个interrupt request line的硬件信号线连接到中断控制器,可能是电平触发的(Level-sensitive),也可能是边缘触发的(Edge-triggered)
软件中断即SGI(software-generated interrupts):软件产生的中断,主要用于核间交互,内核中的IPI:inter-processor interrupts就是基于SGI,中断号ID0 - ID15用于SGI。
GIC组成框图

从上面的GIC组成框图可以看出,GIC由两部分组成:Distributor和CPU interface
Distributor的作用就是就是集中处理所有的中断源,决定每个中断的优先级、对于每个CPU interface将优先级最高的中断转发到该interface上。Distributor提供了如下编程接口:
- 全局控制分发到CPU interface上的中断
- 对于每个中断都可以自由选择使能与否
- 对每一个中断都可以设置优先级
- 设置每个中断可以路由的CPU(CPU list)
- 对于外设类型中断,可以设置中断类型是边沿触发还是电平触发
- 设置每个中断的group,group0用于安全相关的中断,支持FIQ和IRQ。group1用于非安全中断,仅支持IRQ
- 对于SGI中断,可以把其分发到1个或多个CPU
- 每个中断的状态可见
- 提供了一种机制来设置或清
外设中断状态
CPU interface功能:CPU interface顾名思义就是提供一个interface连接着GIC和CPU,CPU interface提供如下编程接口:
使能与否interrupt request送达到CPU
响应中断
中断处理完了,通过CPU interface传达EOI信号给GIC
为CPU设置中断优先级掩码,即可以自由的设置想分发给CPU的中断,即使interrupt request line上来了一个中断信号,但CPU interface mask了该中断,该中断也是不能送达CPU的
对于多核处理器,定义了抢占策略
确定了处理器最高优先级pending中断
CPU interface取Distributor分发的最高优先级中断并且决定这个中断是否有足够的优先级从而分发给CPU进行处理。此时中断的状态还是pending,当CPU ACK了CPU interface分发的中断之后,Distributor把该中断的状态修改为active 或者active and pending(多核处理器,某一个CPU正在处理一个中断,但又来了一个相同的中断)。需要注意的是此时CPU interface也可以signal其它中断来抢占当前处理器上处于active状态的中断。当CPU上运行的中断处理函数完成了工作之后,CPU会往CPU interface的相关寄存器写值指示中断完成了,然后CPU interface会移除该中断的active状态。
中断状态机转换图

中断状态有四种:
- Inactive:没有中断
- Pending:硬件或者软件在interrupt request line上触发了中断,但还没有传到CPU上
- Active:中断产生了并且传递到了目标CPU,目标CPU也可以处理该中断
- Active and Pending:发生了中断并且将其传递到目标CPU,同时又发生了相同的中断并且该中断在等待处理
GIC中断检测与处理流程

首先,外设会通过interrupt request line连接到GIC控制器上,我们以边沿中断(active on low)为例:interrupt request line上空闲状态都为高电平,某一时刻产生了出现了低电平,然后GIC就检测到了下降沿,此时中断信号assert,标记为pending状态。然后Distributor确定好目标CPU后,将中断信号发送到目标CPU上(实际是否发送到了目标CPU,Distributor不用关注),同时对于每个CPU,Distributor会从pending信号中选择最高优先级的中断发送到CPU interface。CPU interface根据相应中断是否被mask,从而选择是否将该中断上传到目标CPU,如果成功上传至CPU,CPU首先会ACK该中断,然后Distributor把该中断的状态修改为active 或者active and pending。当该中断的中断handler完成了使命之后,CPU会告诉CPU interface它已经处理完了该中断(EOI)。至此GIC硬件部分对于中断的处理流程分析完毕,接下来从代码层面分析GIC驱动。
GIC驱动注册流程分析
首先看看GIC初始的入口init_IRQ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void __init init_IRQ(void)
{
int ret;
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)//machine_desc相关的初始化在setup_arch的setup_machine_fdt函数内完成
irqchip_init();
else
machine_desc->init_irq();//对于IMX6UL,实际调用的是这个,init_irq这个函数指针在mach-imx6ul.c里实现绑定
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)
pr_err("L2C: failed to init: %d\n", ret);
}
}在init_IRQ函数里,首先判断是否使能了of style及init_irq是否注册过了,这里的init_irq是machine specific,由于我使用的是IMX6UL,因此去mach-imx6ul.c里看看init_irq具体与哪个函数绑定了。
1
2
3
4
5
6
7DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END可以看到mach-imx6ul.c里init_irq被绑定到了imx6ul_init_irq上,我们看看其实现:
1
2
3
4
5static void __init imx6ul_init_irq(void)
{
......
irqchip_init();
}imx6ul_init_irq最后调用了irqchip_init来进行中断控制器GIC相关的初始化,继续走
1
2
3
4
5
6
7
8void __init irqchip_init(void)
{
//of_irq_init是在所有的device node中寻找中断控制器节点,形成树状结构(系统可以有多个interrupt controller,
//之所以形成中断控制器的树状结构,是为了让系统中所有的中断控制器驱动按照一定的顺序进行初始化)
of_irq_init(__irqchip_of_table);
acpi_irq_init();
}在irqchip_init里,会调用of_irq_init对中断控制器进行初始化,其中
__irqchip_of_table是编译器定义的一个特殊的段(section),里面存放着与gic初始化相关的函数指针。在irq-gic.c里可以看到使用了IRQCHIP_DECLARE定义了很多与GIC中断控制器相关的的函数。1
2
3
4
5
6
7
8
9
10//IRQ chip的初始化函数都是gic_of_init
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);追踪下IRQCHIP_DECLARE的实现:
1
2
3
4
5
6
7
8
9
10
可以看出IRQCHIP_DECLARE最终是调用了_OF_DECLARE来静态一个of_device_id类型变量,变量名为
__of_table_##name,这里的name就是我们调用IRQCHIP_DECLARE传入的GIC控制器名字,对于IMX6UL来说,name是cortex-a7-gic,然后链接器会把这个of_device_id结构放到__irqchip_of_tablesection里。好,现在回到of_irq_init函数,在of_irq_init函数里,我们主要做的事就是扫描和初始化设备树里那些interrupt-controller的节点,然后调用初始化函数irq_init_cb,这里的irq_init_cb就是个函数指针,指向irq-gic.c里的IRQCHIP_DECLARE声明的gic_of_init函数。1
2irq_init_cb = (of_irq_init_cb_t)match->data;//对于GIC,这里的data其实就是个函数指针,指向irq-gic.c里的IRQCHIP_DECLARE声明的gic_of_init函数
ret = irq_init_cb(desc->dev, desc->interrupt_parent);//对于IMX6UL,其imx6ul.dtsi,其root interrrupt controller是arm,cortex-a7-gic继续追踪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//从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,
//一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为
//参数传递给irq chip driver
static int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
void __iomem *dist_base;
u32 percpu_offset;
int irq;
if (WARN_ON(!node))
return -ENODEV;
dist_base = of_iomap(node, 0);//映射GIC Distributor的寄存器地址空间
WARN(!dist_base, "unable to map gic dist registers\n");
cpu_base = of_iomap(node, 1);//-映射GIC cpu interface的寄存器地址空间
WARN(!cpu_base, "unable to map gic cpu registers\n");
if (of_property_read_u32(node, "cpu-offset", &percpu_offset))//imx6ul没有cpu-offset属性
percpu_offset = 0;
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
if (!gic_cnt)
gic_init_physaddr(node);
if (parent) {//如果parent存在的说明中断控制器有级联,那么此处就处理级联。对于root gic,其parent为NULL
irq = irq_of_parse_and_map(node, 0);//解析级联节点的interrupts属性,并进行mapping,返回IRQ
gic_cascade_irq(gic_cnt, irq);
}
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_of_init(node, gic_data[gic_cnt].domain);
gic_cnt++;
return 0;
}GIC是由Distributor和CPU interface组成的,因此在gic_of_init里首先会调用of_iomap来mapping 这二者。接下来就是重头戏,调用
gic_init_bases进行gic的初始化。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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128struct gic_chip_data {
union gic_base dist_base;//GIC Distributor的基地址空间
union gic_base cpu_base;//GIAC CPU interface基址空间
u32 saved_spi_enable[DIV_ROUND_UP(1020, 32)];
u32 saved_spi_conf[DIV_ROUND_UP(1020, 16)];
u32 saved_spi_target[DIV_ROUND_UP(1020, 4)];
u32 __percpu *saved_ppi_enable;
u32 __percpu *saved_ppi_conf;
struct irq_domain *domain;
unsigned int gic_irqs;//gic所支持的irq数目
void __iomem *(*get_base)(union gic_base *);
};
//drivers/irqchip/irq-gic.c
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct device_node *node)
{
irq_hw_number_t hwirq_base;//GIC上的HW interrupt ID
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;
BUG_ON(gic_nr >= MAX_GIC_NR);
gic = &gic_data[gic_nr];//硬件GIC信息
if (percpu_offset) { /* Frankein-GIC without banked registers... */
unsigned int cpu;
gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
if (WARN_ON(!gic->dist_base.percpu_base ||
!gic->cpu_base.percpu_base)) {
free_percpu(gic->dist_base.percpu_base);
free_percpu(gic->cpu_base.percpu_base);
return;
}
for_each_possible_cpu(cpu) {
u32 mpidr = cpu_logical_map(cpu);
u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
unsigned long offset = percpu_offset * core_id;
*per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;
*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;
}
gic_set_base_accessor(gic, gic_get_percpu_base);
} else
{ /* Normal, sane GIC... */
WARN(percpu_offset,
"GIC_NON_BANKED not enabled, ignoring %08x offset!",
percpu_offset);
gic->dist_base.common_base = dist_base;
gic->cpu_base.common_base = cpu_base;
gic_set_base_accessor(gic, gic_get_common_base);
}
/*
* Initialize the CPU interface map to all CPUs.
* It will be refined as each CPU probes its ID.
*/
for (i = 0; i < NR_GIC_CPU_IF; i++)
gic_cpu_map[i] = 0xff;
/*
* Find out how many interrupts are supported.
* The GIC only supports up to 1020 interrupt sources.
*/
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;//gic_irqs代表了该GIC最多支持的中断数目
gic_irqs = (gic_irqs + 1) * 32;//参见gic-v2手册ITLinesNumber计算公式
if (gic_irqs > 1020)
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;
if (node) { /* DT case */
gic->domain = irq_domain_add_linear(node, gic_irqs,//创建irq domain,线性方式
&gic_irq_domain_hierarchy_ops,
gic);
} else { /* Non-DT case */
/*
* For primary GICs, skip over SGIs.
* For secondary GICs, skip over PPIs, too.
*/
if (gic_nr == 0 && (irq_start & 31) > 0) {//gic_nr标识GIC number,gic_nr为0就代表着root GIC
hwirq_base = 16;//忽略掉16个SGI
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;//interrupt ID 0-15:SGI, 16-31:PPI.忽略掉16个SGI + 16个PPI(对于系统中其他的GIC,其PPI也没有必要mapping)
}
//减去不需要分配IRQ的那些interrupt ID,此时gic_irqs就代表该GIC实际需要分配的IRQ数目
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
//由于我们传入的irq_start为-1,代表不指定IRQ number.那就需要搜索,第二个参数16就是起始搜索的IRQ number
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
numa_node_id());
if (IS_ERR_VALUE(irq_base)) {
WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
irq_start);
irq_base = irq_start;
}
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
}
if (WARN_ON(!gic->domain))
return;
if (gic_nr == 0) {
set_smp_cross_call(gic_raise_softirq);
register_cpu_notifier(&gic_cpu_notifier);
set_handle_irq(gic_handle_irq);
}
gic_dist_init(gic);
gic_cpu_init(gic);
gic_pm_init(gic);
}在gic_init_bases函数里,首先会获取硬件GIC信息,然后初始化CPU interface,接下来获取该GIC最大支持的中断数量gic_irqs,最大支持1020个中断。对于每一个含有interupt-controller的device node,会调用irq_domain_add_linear来分配一个irq_domain,分析irq_domain_add_linear之前首先看看irq_domain的原型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct irq_domain {//IRQ domain用来将HW中断号转换成Linux里的中断号virq
struct list_head link;//用于添加到全局链表irq_domain_list中
const char *name;//IRQ domain的名字
const struct irq_domain_ops *ops;//callback函数
void *host_data;//定义了底层interrupt controller使用的私有数据,和具体的interrupt controller相关(对于GIC,该指针指向一个struct gic_chip_data数据结构)
unsigned int flags;
/* Optional data */
struct device_node *of_node;
struct irq_domain_chip_generic *gc;
struct irq_domain *parent;//支持级联的话,指向父设备
/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;//domain中最大的那个HW interrupt ID
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;//线性映射的size
struct radix_tree_root revmap_tree;
unsigned int linear_revmap[];//线性映射使用的lookup table
};从linux kernel角度看,任何外部设备的中断都是一个异步事件,kernel需要识别这个事件。在内核里,用IRQ number来标识一个设备的interrupt request。有了IRQ number之后就可以在irq_desc里定位到具体使用的irq descriptor。但对于中断控制器来说,它不知道IRQ number,它只知道HW interrupt number(中断控制器会为其所支持的中断源进行编码,这个编码就称之为HW interrupt number)不同的软件模块用不同的ID来识别中断源,这样就需要映射了。所以出现了irq_domain这样一个数据结构,专门用来把HW interrupt number翻译成IRQ number。每个interrupt controller都会形成一个irq_domain,负责解析其下游的interrupt source。对于含有interrupt-controller属性的节点,会调用irq_domain_add_linear创建线性映射的irq_domain。irq_domain_add_linear实际上会调用__irq_domain_add来完成irq_domain的分配和添加到全局irq_domain_list的任务。
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//kernel/irq/irqdomain.c
struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
if (WARN_ON(!domain))
return NULL;
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->of_node = of_node_get(of_node);
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
irq_domain_check_hierarchy(domain);
mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list);
mutex_unlock(&irq_domain_mutex);
pr_debug("Added domain %s\n", domain->name);
return domain;
}在__irq_domain_add函数里,首先会调用kzalloc_node为irq_domain分配内存,然后初始化domain的一些成员,最后调用list_add把刚分配并且初始化好的irq_domain添加到全局的irq_domain_list,通过irq_domain_list这个指针,可以获取整个系统中HW interrupt ID和IRQ number的mapping DB。回到gic_init_bases函数,分配irq_domain之后,会判断gic_nr是否为0,如果为0,就代表该node是root gic,我们现在只考虑单核情况(IMX6UL是单核),然后会调用set_handle_irq设置通用的irq handler:
gic_handle_irq1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;//得到HW IRQ number
if (likely(irqnr > 15 && irqnr < 1021)) {//外设触发的中断
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {//软件触发的中断,SGI
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
handle_IPI(irqnr, regs);
continue;
}
break;
} while (1);
}一进gic_handle_irq,首先会调用readl_relaxed读取GIC_CPU_INTACK,我们在GIC-V2手册可知,这个读实际上是读取GICC_IAR寄存器,读一次该寄存器表示响应了中断。如图,GICC_IAR的bit[9:0]保存了Interrupt ID

读取GICC_IAR就会返回CPU interface上最高优先级并且处于pending状态的Interrupt ID。gic_handle_irq里的irq_nr就代表了HW Interrupt ID,接下来会判断HW Interrupt ID是否是外设触发的中断(Interrupt ID:16-1020),如果是,就调用
handle_domain_irq。handle_domain_irq主要工作在__handle_domain_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
29int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter();//进入中断上下文
if (lookup)
irq = irq_find_mapping(domain, hwirq);//找到HW interrupt number映射的virq
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);
}
irq_exit();
set_irq_regs(old_regs);
return ret;
}在__handle_domain_irq函数内,首先调用irq_enter通过显式增加hardirq域计数,通知Linux进入中断上下文。然后调用irq_find_mapping根据irq_domain和之前解析的hwirq翻译出对应的virq。然后调用
generic_handle_irq进行通用的中断处理流程。1
2
3
4
5
6
7
8
9
10//kernel/irq/irqdesc.c
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);//根据virq找到对应的中断描述符
if (!desc)
return -EINVAL;
generic_handle_irq_desc(irq, desc);
return 0;
}generic_handle_irq里做的事很简单,首先根据软件irq号找到对应的irq_desc。我们来看看这个irq_desc以及通过irq如何找到对应的irq_desc。
1
2
3
4
5
6
7
8
9
10struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
irq_preflow_handler_t preflow_handler;
struct irqaction *action; /* IRQ action list */
......
}1
2
3
4struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}1
2
3
4
5
6
7struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};irq_desc是一个全局的中断描述符数组,由于我们采用的是线性映射,因此由irq找到对应的struct irq_desc可以使用irq_desc的基地址加上偏移量,偏移量就是virq,就可以得到对应的struct irq_desc的指针。好啦,我们继续往下走,看
generic_handle_irq_desc1
2
3
4static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}generic_handle_irq_desc调用irq对应的irq_desc绑定好的handle_irq,默认是handle_bad_irq,但用户可以修改。那问题来了,desc->handle_irq是在什么时候绑定的呢?跟谁绑定?handle_irq属于highlevel irq-events handler,我们后面会单开一文进行讲解,现在我们先留着这个问题。generic_handle_irq_desc执行完了,接下来就回到了前面讲的gic_init_bases函数里继续执行,先执行gic_dist_init:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22static void __init gic_dist_init(struct gic_chip_data *gic)
{
unsigned int i;
u32 cpumask;
unsigned int gic_irqs = gic->gic_irqs;//获取该GIC支持的IRQ的数目
void __iomem *base = gic_data_dist_base(gic);//获取该GIC对应的Distributor基地址
writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);//写入0表示Distributor不向CPU interface发送中断请求信号,也就disable了全部的中断请求
/*
* Set all global interrupts to this CPU only.
*/
cpumask = gic_get_cpumask(gic);//此处的cpumask是8bit,之后又需要扩充到32bit
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);//设定每个SPI类型的中断都是只送达该CPU
gic_dist_config(base, gic_irqs, NULL);//配置GIC distributor的其他寄存器,设定初始值
writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}gic_dist_init主要是对GIC 的Distuibutor进行初始化。由GIC-V2手册可以看到,Distributor的控制寄存器是GICD_CTRL,其有效位仅仅是bit0,
bit 0控制着是否把pending状态的中断forward给CPU interface。在初始化Distributor的时候,先往GICD_CTRL写0,禁止给CPU interface分发中断。然后调用gic_get_cpumask来对CPU mask来进行修正。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20static u8 gic_get_cpumask(struct gic_chip_data *gic)
{
void __iomem *base = gic_data_dist_base(gic);
u32 mask, i;
//GICD_ITARGETSRn一共有32bit,但GIC-400只支持8个CPU,因此CPU mask只需要8bit
//所以需要cpu mask右移两次
for (i = mask = 0; i < 32; i += 4) {
mask = readl_relaxed(base + GIC_DIST_TARGET + i);
mask |= mask >> 16;
mask |= mask >> 8;
if (mask)
break;
}
if (!mask && num_possible_cpus() > 1)
pr_crit("GIC CPU mask not found - kernel will fail to boot.\n");
return mask;
}GIC_DIST_TARGET对应着GIC-V2里的GICD_ITARGETSRn(Interrupt Processor Targets Registers),其位于Distributor HW block中,能控制forward的CPU interface。GICD_ITARGETSRn是一个32bit的寄存器,但由于我们使用的中断控制器是GIC-400,它只支持8个CPU,因此CPU mask只需要8bit,所以在代码里会先右移16位,然后再右移8位。假设CPU 0接在了CPU interface 1上,那么运行在CPU 0上的程序读GICD_ITARGETSRn的时候返回的就是0b00000010。回到gic_dist_init,接下来又把gic_get_cpumask返回的mask左移8位之后又左移了16位,相当于把8bit的值又扩充到了32bit,每8个bit是CPU mask值,然后调用writel_relaxed把cpu_mask写到GICD_ITARGETSRn里。为什么从32开始操作呢?因为Interrupt ID 0-15用于SGI,16-31用于PPI,所以从32开始设定所有的SPI类型中断的CPU mask。接下来再调用gic_dist_config配置GIC Distributor其他寄存器:
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//drivers/irqchip/irq-gic.c
//都是默认值,各个driver初始化过程中可能根据实际情况进行修改
void __init gic_dist_config(void __iomem *base, int gic_irqs,
void (*sync_access)(void))
{
unsigned int i;
/*
* Set all global interrupts to be level triggered, active low.
*/
for (i = 32; i < gic_irqs; i += 16)
writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
base + GIC_DIST_CONFIG + i / 4);
/*
* Set priority on all global interrupts.
*/
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i);
/*
* Disable all interrupts. Leave the PPI and SGIs alone
* as they are enabled by redistributor registers.
*/
for (i = 32; i < gic_irqs; i += 32)
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ENABLE_CLEAR + i / 8);
if (sync_access)
sync_access();
}在gic_dist_config里,主要做的事就是设置默认的触发方式、优先级设置等,实际driver初始化可能根据需求再修改。完成了gic_dist_config之后,就使能GIC Distributor。再次回到gic_init_bases函数,gic_dist_init初始化完成之后,开始初始化GIC CPU interface。主要工作在gic_cpu_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
32static void gic_cpu_init(struct gic_chip_data *gic)
{
void __iomem *dist_base = gic_data_dist_base(gic);
void __iomem *base = gic_data_cpu_base(gic);
unsigned int cpu_mask, cpu = smp_processor_id();//得到CPU 逻辑ID
int i;
/*
* Get what the GIC says our CPU mask is.
*/
BUG_ON(cpu >= NR_GIC_CPU_IF);
cpu_mask = gic_get_cpumask(gic);
//在gic_init_bases函数中,我们将该lookup table中的值都初始化为0xff,也就是说不进行mask,送达所有的CPU。这里,我们会进行重新修正
gic_cpu_map[cpu] = cpu_mask;//以CPU 逻辑ID为索引找到对应的cpu mask,后续通过cpu mask值来控制中断是否送达该CPU
/*
* Clear our mask from the other map entries in case they're
* still undefined.
*/
for (i = 0; i < NR_GIC_CPU_IF; i++)
if (i != cpu)
gic_cpu_map[i] &= ~cpu_mask;
gic_cpu_config(dist_base, NULL);
//通过Distributor中的寄存器可以控制送达CPU interface。当中断到了GIC CPU interface也不一定直接到达CPU,因此此时还需要受
//CPU interface里的Interrupt Priority mask register控制。这个寄存器设置了一个缺省值,只有中断优先级高于该值的中断请求才会被
//送到CPU上.之前初始化的时候把interrupt ID设置的缺省优先级是0xa0,而此处设置的是0xf0,数值越小,优先级越大,因此这样的设定标识
//所有的interrupt source都有送达CPU的机会
writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
gic_cpu_if_up();
}gic_init_bases开始处使用了一个for循环把gic_cpu_map设置成了0xff,也就是送达CPU interface的所有中断都不会被mask,都有机会送达CPU,但在gic_cpu_init里,我们会根据实际的cpu_mask来进行修正,修正后的逻辑是:以CPU 逻辑ID为索引找到对应的CPU mask,后面再通过设置CPU mask来控制中断是否能送达该CPU。接下来调用gic_cpu_config配置GIC CPU interface的其他寄存器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
{
int i;
/*
* Deal with the banked PPI and SGI interrupts - disable all
* PPI interrupts, ensure all SGI interrupts are enabled.
*/
writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR);
writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
/*
* Set priority on PPI and SGI interrupts
*/
for (i = 0; i < 32; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4,
base + GIC_DIST_PRI + i * 4 / 4);
if (sync_access)
sync_access();
}gic_cpu_config主要完成的工作设置与PPI和SGI相关的寄存器。
我们在gic_dist_init里可以控制中断是否到达CPU interface,但即使到了CPU interface也不一定有机会forward到CPU上运行,还需要受CPU interface里的GICC_PMR(Interrupt Priority Mask Register)控制,GICC_PMR设定了一个默认值,只有中断优先级高于GICC_PMR寄存器值的中断请求才会被送到CPU上处理。因此
writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK)的作用就是设定GICC_PMR的默认优先级值为0xf0,只有当中断优先级的值大于0xf0时才会被送到CPU。gic_init_bases的最后一步就是初始化与电源相关的东西,这不是我们研究的重点,以后再分析。ok,我们已经分析完了gic_init_bases,回到gic_of_init继续走,接下来判断含有interrupt-controller的节点是否parent,如果有,就说明中断控制器存在级联的情况,对于root GIC,parent为NULL,由于我们是以IMX6UL分析的,我们从imx6ul.dtsi可知,其gpiox充当gpio-controller的同时也扮演者interrupt-controller的角色。1
2
3
4
5
6
7
8
9
10gpio1: gpio@0209c000 {//既是gpio-controller也是interrupt-controller
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
interrupt-controller;
};gpio1的interrupt parent就是root GIC,也即intc,intc在DTS定义如下:
1
2
3
4
5
6
7intc: interrupt-controller@00a01000 {//root interrupt controller
compatible = "arm,cortex-a7-gic";
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};gpio1我们也可称之为second GIC,由于其parent存在,则首先调用irq_of_parse_and_map解析其interrupts属性,并进行mapping,返回IRQ number。
1
2
3
4
5
6
7
8
9
10//drivers/of/irq.c
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
if (of_irq_parse_one(dev, index, &oirq))//分析device node中的interrupt相关属性
return 0;
return irq_create_of_mapping(&oirq);//创建映射,并返回对应的IRQ number
}irq_of_parse_and_map首先调用of_irq_parse_one来解析device node里的interrupt相关属性,比如interrupts-extended、interrupts等。
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//drivers/of/irq.c
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
struct device_node *p;
const __be32 *intspec, *tmp, *addr;
u32 intsize, intlen;
int i, res;
pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(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);
/* Get the interrupts property */
intspec = of_get_property(device, "interrupts", &intlen);
if (intspec == NULL)
return -EINVAL;
intlen /= sizeof(*intspec);
pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen);
/* Look for the interrupt parent. */
p = of_irq_find_parent(device);
if (p == NULL)
return -EINVAL;
/* Get size of interrupt specifier */
tmp = of_get_property(p, "#interrupt-cells", NULL);
if (tmp == NULL) {
res = -EINVAL;
goto out;
}
intsize = be32_to_cpu(*tmp);
pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);
/* Check index */
if ((index + 1) * intsize > intlen) {
res = -EINVAL;
goto out;
}
/* Copy intspec into irq structure */
//这里根据我们传递的数组下标作位移,index是具体下标(我们传的是0),intsize为每个interrupt cell数目。
//指针按这个offset进行位移后,就会指向我们需要的中断数据
intspec += index * intsize;
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++)
out_irq->args[i] = be32_to_cpup(intspec++);
/* 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;
}在of_irq_parse_one函数中,首先调用of_get_property获取属性为interrupt-controller的节点的reg属性,得到该节点的interrupt resource地址空间。由于imx6ul.dtsi带有interrupt-controller属性的节点没有
interrupts-extended属性,因此略过of_parse_phandle_with_args解析过程。然后调用of_get_property获取interrupts属性值,并把属性值的地址保存到intspec,用intlen来保存描述中断所需要的个数,以gpio1为例,intlen就为2。继续往下走,接着调用of_get_property获取带有interrupt-controller属性的节点的parent,找到其parent之后,再调用of_get_property找parent(对于gpio1,parent就是intc节点)的interrupt-cells属性,由imx6ul.dtsi可以知道intc的interrupt-cells为3,也就是需要用3个cell来描述中断,分别是中断类型(SPI,PPI…)、中断号(HW interrupt)、触发方式(电平触发、边沿触发)。最后把解析后的数据放入out_irq,就这样of_irq_parse_one完成了它的使命,紧接着开始调用irq_create_of_mapping创建映射,并返回对应的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//kernel/irq/irqdomain.c
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
struct irq_domain *domain;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
int virq;
domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
if (!domain) {
pr_warn("no irq domain found for %s !\n",
of_node_full_name(irq_data->np));
return 0;
}
/* If domain has no translation, then we assume interrupt line */
if (domain->ops->xlate == NULL)
hwirq = irq_data->args[0];
else {
if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
irq_data->args_count, &hwirq, &type))
return 0;
}
if (irq_domain_is_hierarchy(domain)) {
/*
* If we've already configured this interrupt,
* don't do it again, or hell will break loose.
*/
virq = irq_find_mapping(domain, hwirq);
if (virq)
return virq;
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
if (virq <= 0)
return 0;
} else {
/* Create mapping */
virq = irq_create_mapping(domain, hwirq);
if (!virq)
return virq;
}
/* Set type if specified and different than the current one */
if (type != IRQ_TYPE_NONE &&
type != irq_get_trigger_type(virq))
irq_set_irq_type(virq, type);
return virq;
}在irq_create_of_mapping函数里,首先会根据中断控制器的device node找到对应的irq domain,找irq domain的过程非常简单,其实就是调用irq_find_host在全局的irq_domain_list上,调用irq_domain_ops的match函数遍历irq_domain_list上每个entry的irq_domain是否与中断控制器节点是否匹配,如果匹配则返回1。当找到匹配的irq_domain之后,调用irq_domain_ops的xlate函数。给定某个外设的device node,xlate函数可以解析出在irq_domain上该设备使用的HW interrupt ID和irq type。对于GIC,我们之前已经把xlate函数绑定到了
gic_irq_domain_xlate:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23static int gic_irq_domain_xlate(struct irq_domain *d,
struct device_node *controller,
const u32 *intspec, unsigned int intsize,//s输入参数
unsigned long *out_hwirq, unsigned int *out_type)//输出参数
{
unsigned long ret = 0;
if (d->of_node != controller)
return -EINVAL;
if (intsize < 3)
return -EINVAL;
/* Get the interrupt number and add 16 to skip over SGIs */
*out_hwirq = intspec[1] + 16;
/* For SPIs, we need to add 16 more to get the GIC irq ID number */
if (!intspec[0])
*out_hwirq += 16;
*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;//取出bit[3:0]信息,其保存了触发方式
return ret;
}当调用domain->ops->xlate找到了硬件中断号以及中断触发类型后,再调用irq_domain_is_hierarchy判断irq_domain的flags是否级联,在IMX6UL平台上没有设置该标志位,因此走else分支调用
irq_create_mapping根据domain和hwirq创建与virq的映射关系。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
41unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
int virq;
pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);
/* Look for default domain if nececssary */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL) {
WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
return 0;
}
pr_debug("-> using domain @%p\n", domain);
/* Check if mapping already exists */
virq = irq_find_mapping(domain, hwirq);
if (virq) {
pr_debug("-> existing mapping on virq %d\n", virq);
return virq;
}
/* Allocate a virtual interrupt number */
virq = irq_domain_alloc_descs(-1, 1, hwirq,
of_node_to_nid(domain->of_node));
if (virq <= 0) {
pr_debug("-> virq allocation failed\n");
return 0;
}
if (irq_domain_associate(domain, virq, hwirq)) {
irq_free_desc(virq);
return 0;
}
pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
hwirq, of_node_full_name(domain->of_node), virq);
return virq;
}在irq_create_mapping函数里,先检查是否已经映射过了,如果是就直接返回。否则调用
irq_domain_alloc_descs来分配一个虚拟的中断号virq。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//kernel/irq/irqdomain.c
static int irq_domain_alloc_descs(int virq, unsigned int cnt,
irq_hw_number_t hwirq, int node)
{
unsigned int hint;
if (virq >= 0) {
virq = irq_alloc_descs(virq, virq, cnt, node);
} else {
hint = hwirq % nr_irqs;
if (hint == 0)
hint++;
virq = irq_alloc_descs_from(hint, cnt, node);
if (virq <= 0 && hint > 1)
virq = irq_alloc_descs_from(1, cnt, node);
}
return virq;
}irq_domain_alloc_descs实参virq是-1小于0,因此走else分支。首先得到一个hint(0-nr_irqs),然后根据这个hint调用irq_alloc_descs_from,最终调用到__irq_alloc_descs从全局的静态数组allocated_irqs里找一个没有使用的下标作为映射的中断号返回给驱动使用。那我们现在也知道了其实中断映射就是将硬件中断号与alloctated_irqss数组下标建立对应关系。完成映射之后返回gic_of_init函数里的gic_cascade_irq继续处理级联的情况(gpiox级联到了intc上,gpiox也充当中断控制器)。
1
2
3
4
5
6
7
8void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
if (gic_nr >= MAX_GIC_NR)
BUG();
if (irq_set_handler_data(irq, &gic_data[gic_nr]) != 0)
BUG();
irq_set_chained_handler(irq, gic_handle_cascade_irq);
}gic_cascade_irq首先调用了irq_set_handler_data设置了IRQ的私有硬件中断控制器数据hanler_data,然后调用irq_set_chained_handler,最终调到__irq_set_handler进行high level的IRQ handle设置。
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__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
if (!desc)
return;
if (!handle) {
handle = handle_bad_irq;
} else {
struct irq_data *irq_data = &desc->irq_data;
/*
* With hierarchical domains we might run into a
* situation where the outermost chip is not yet set
* up, but the inner chips are there. Instead of
* bailing we install the handler, but obviously we
* cannot enable/startup the interrupt at this point.
*/
while (irq_data) {
if (irq_data->chip != &no_irq_chip)
break;
/*
* Bail out if the outer chip is not set up
* and the interrrupt supposed to be started
* right away.
*/
if (WARN_ON(is_chained))
goto out;
/* Try the parent */
irq_data = irq_data->parent_data;
}
if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
goto out;
}
/* Uninstall? */
if (handle == handle_bad_irq) {
if (desc->irq_data.chip != &no_irq_chip)
mask_ack_irq(desc);
irq_state_set_disabled(desc);
desc->depth = 1;
}
desc->handle_irq = handle;//对于IMX6UL,此处的handle是mx3_gpio_irq_handler
desc->name = name;
if (handle != handle_bad_irq && is_chained) {
irq_settings_set_noprobe(desc);
irq_settings_set_norequest(desc);
irq_settings_set_nothread(desc);
irq_startup(desc, true);
}
out:
irq_put_desc_busunlock(desc, flags);
}在__irq_set_handler函数里,一般不使能CONFIG_IRQ_DOMAIN_HIERARCHY。核心代码是
desc->handle_irq = handle
结合上图分析,外设device1通过gpio1连接到IMX6UL的GPIO IP上,当产生中断时,中断请求会通过interrupt request line传到GPIO模块进行处理,因为GPIO模块同时也是interrupt-controller,然后GPIO模块上报中断请求给GIC,GIC再forward给CPU interface,CPU interface再上报给CPU进行处理。handle_irq是high level irq events,对于我们的IMX6UL,由于imx6ul.dtsi里gpio1也充当了interrupt controller,因此这里的desc->handle_irq被绑定到了IMX6UL的SOC GPIO IP core上,在IMX6UL的GPIO probe时,即mxc_gpio_probe函数内,调用了
irq_set_chained_handler(port->irq, mx3_gpio_irq_handler)。接下来由于handle不是handle_bad_irq同时is_chained为true,因此调用irq_setings_set_*函数设置了中段描述符irq_desc一些状态标志,然后调用irq_startup回调irq_chip底层接口来启动中断。OK,现在gic_of_init任务终于圆满完成。总结
本文较为详细的介绍了GIC对中断处理的软硬件原理,分析了中断从在GIC这一层的处理流程,为以后深入理解中断的整个生成、处理、执行流程打好基础。