本文的目的在在于介绍中断控制器的注册过程及中断的处理,从软件系统的角度理解级联中断控制器。
背景:Arm64处理器、Linux-4.14内核
目录
1.内核中关于中断控制器的宏定义
在介绍中断控制器的注册前先介绍内核中关于中断控制器几个宏定义:
1.1 IRQCHIP_DECLARE 宏
用于实现中断控制器的of_device_id结构,该数据结构会在__irqchip_of_table内核代码段。
#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)
#if defined(CONFIG_OF) && !defined(MODULE)
#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 }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__attribute__((unused)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#endif
1.2 IRQCHIP_OF_MATCH_TABLE宏
因为CONFIG_IRQCHIP有定义,所以宏的意义为定义了变量__irqchip_of_table、并指定了该变量在内核代码段的空间,其起始地址__irqchip_of_table段的开始、结束地址为__irqchip_of_table_end段起始地址。
#define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip)
#define OF_TABLE(cfg, name) __OF_TABLE(IS_ENABLED(cfg), name)
#define __OF_TABLE(cfg, name) ___OF_TABLE(cfg, name)
#define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name)
#define _OF_TABLE_1(name) \
. = ALIGN(8); \
__##name##_of_table = .; \
KEEP(*(__##name##_of_table)) \
KEEP(*(__##name##_of_table_end))
1.3 irqchip相关变量
/* 路径 Drivers/irqchip/irqchip.c */
/* irqchip_of_match_end 为__irqchip_of_table_end段地址,
irqchip_of_match_end 表示 __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 数组中存放了通过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 变量也在只在系统初始化完成前有意义。
2.中断控制器设备注册
2.1 root interrupt controller
通过上文《处理器中的级联中断控制器-CSDN博客》可知该系统的根中断控制器为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中断控制器的流程
如图内核在启动过程中会通过of_irq_init函数来调用gicv3的初始化函数,of_irq_init函数是在内核初始化irqchip时被调用。
of_irq_init函数解析如下,该函数会调用通过IRQCHIP_DECLEAR宏或类似宏定义的与系统device node相匹配的中断控制器的初始化函数,完成中断控制器的初始化动作。
/**
* of_irq_init 函数用来在设备树中查找与matches数组匹配的中断控制器,并对该中断控制器进行初始化
* @matches:是指向内核__irqchip_of_table数据段的数组,该数组中包含代码中通过
* IRQCHIP_DECLEARE宏定义的所有中断控制器的of_device_id结构数据(不是所有的中断控制
* 都通过IRQCHIP_DECLEARE宏来进行数 据定义)。
**/
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) {
/*检查该device node是否存在 interrupt-controller property,若存在表明device为中断
控制器*/
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);
if (WARN_ON(!desc)) {
of_node_put(np);
goto err;
}
/* match->data :为中断控制器的初始化函数 */
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);
/* 当中断控制器的父中断控制器node执行其自身时,将该中断控制器的interrupt_parent
变量设置为NULL */
if (desc->interrupt_parent == np)
desc->interrupt_parent = NULL;
/* 将该中断控制器的描述符添加到中断控制器描符链表 */
list_add_tail(&desc->list, &intc_desc_list);
}
/* GIC中断控制器的node中的interrupt-parent property指向其自身,所以GIC中断控制器的描
述符号中的interrupt-parent变量为NULL */
/* 如下代码逻辑:以interrupt-parent组的形式进行intc_desc_list中断控制器进行初始化 */
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);
}
}
2.1.2 GICV3中断控制器的初始化
gic_of_init函数开启了GICv3中断控制器的初始化过程,该函数除了通过解析GICv3 的device node完成struct gic_chip_data gic_data数据结构的初始化及distributor、CPU interface、PM等初始化外,另一个重要的重要是将GICv3中断控制器以irq domain数据结构形式嵌入到系统irq_domain_list中断域链表,使系统中连接在此中断控制器的上中断能够进行注册。
GICv3 中断控制器irq_domain结构数据主要通过irq_domain_create_tree函数实现。irq_domain_create_tree函数创建GICv3 irq_domain需要三个GICv3相关参数:
a. gic_data.fwnode
b.gicv3的irq_domain_ops函数指针,提供了该
c.gic_data ,是gicv3的私有数据。
irq_domain_create_tree调用__irq_domain_add最终实现gicv3的irq_domain 并将其添加到irq_domain_list链表。__irq_domain_add函数具体实现如下:
/* 分配&初始化新的irq_domain 并将其添加到系统*irq_domain_list链表 */
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)
{
struct device_node *of_node = to_of_node(fwnode);
struct irqchip_fwid *fwid;
struct irq_domain *domain;
static atomic_t unknown_domains;
/* 分配irq_domain数据结构空间,sizeof(unsigned int) * size :代表了结构中
linear_revmap[]数组元素的内存大小 */
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
if (WARN_ON(!domain))
return NULL;
if (fwnode && is_fwnode_irqchip(fwnode)) {
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
switch (fwid->type) {
case IRQCHIP_FWNODE_NAMED:
case IRQCHIP_FWNODE_NAMED_ID:
domain->fwnode = fwnode;
domain->name = kstrdup(fwid->name, GFP_KERNEL);
if (!domain->name) {
kfree(domain);
return NULL;
}
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
break;
default:
domain->fwnode = fwnode;
domain->name = fwid->name;
break;
}
#ifdef CONFIG_ACPI
} else if (is_acpi_device_node(fwnode)) {
struct acpi_buffer buf = {
.length = ACPI_ALLOCATE_BUFFER,
};
acpi_handle handle;
handle = acpi_device_handle(to_acpi_device_node(fwnode));
if (acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf) == AE_OK) {
domain->name = buf.pointer;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
domain->fwnode = fwnode;
#endif
} else if (of_node) {
char *name;
/*
* DT paths contain '/', which debugfs is legitimately
* unhappy about. Replace them with ':', which does
* the trick and is not as offensive as '\'...
*/
name = kasprintf(GFP_KERNEL, "%pOF", of_node);
if (!name) {
kfree(domain);
return NULL;
}
strreplace(name, '/', ':');
/* 设置domain->name为device node指针 */
domain->name = name;
domain->fwnode = fwnode;
/*IRQ_DOMAIN_NAME_ALLOCATED 表示irq_domian name 空间是__irq_domain_add函数中分
配的*/
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
if (!domain->name) {
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
atomic_inc_return(&unknown_domains));
if (!domain->name) {
kfree(domain);
return NULL;
}
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
of_node_get(of_node);
/* 填充irq_domain 结构数据 */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
mutex_init(&domain->revmap_tree_mutex);
domain->ops = ops; /* 该中断控制器对应的irq_domain_ops函数 */
domain->host_data = host_data; /* 该中断控制器的私有数据 */
domain->hwirq_max = hwirq_max; /* 该中断控制器支持的最大硬件中断数 */
domain->revmap_size = size; /* 线性映射的硬中断的size */
domain->revmap_direct_max_irq = direct_max; /* 直接映射的最大数 */
irq_domain_check_hierarchy(domain);/* 检查是否为分级的irq_domain,如果是该中断控制器
支持irq_domian_ops->alloc函数则该中断控制的irq_domain支持分级 */
mutex_lock(&irq_domain_mutex);
debugfs_add_domain_dir(domain);
list_add(&domain->link, &irq_domain_list);/* 添加该irq_domain到系统irq_domain_list
链表*/
mutex_unlock(&irq_domain_mutex);
pr_debug("Added domain %s\n", domain->name);
return domain; /* 返回该中断控制器对应的irq_domain结构指针 */
}
2.2 child中断控制器的注册过程
本节以pio中断控制器的初始化过程为列,介绍子中断控制器的注册过程。在MTK平台PIO既是pinctrl控制器又是中断控制器,所以PIO中断控制器初始化过程和PIO pinctrl的初始化流程有耦合、具体如下图。
PIO中断控制器注册主要由mtk_build_do_eint函数实现,该函数分为三个部分:
a.PIO irq_domain数据创建
通过调用irq_domain_add_linear函数来实现PIO中断控制的irq_domain数据结
果的创建,具体的创建过程同GICv3 irq_domain 相同 都会调用到
__irq_domain_add 函数,但是在入参上两者有些差异:
a1. PIO的irq_domain->ravamp_size会被赋值且该值为PIO中断控制器支持的
硬件中断数,这是因为PIO控制器的中断采用linear映射方式 GICv3的
irq_domain->ravamp_size、revmap_direct_max_irq都为零,代表示采用
radix映射方式。
a2. PIO 和 GICv3 的irq_domain->irq_domain_ops 函数有差异,因为两个中
断控制器支持的功能不同。
b.将PIO 支持的硬件中断映射到系统虚拟中断
首先根据硬件中断号通过irq_create_mapping函数创建对应的系统虚拟中断号。
然后通过irq_set_chip_and_handler & irq_set_chip_data函数填充该virq对应的
irq_desc结构。
c.填充PIO连接到GIC的中断对应的中断描述符结构
该硬件中断到系统虚拟中断的映射在此之前已通过irq_of_parse_and_map函数完成。
不同于PIO之身所支持的中断,PIO连接到GIC的中断会通过irq_set_chained_handler_and_data函数标记为is_chained,被标记为is_chained的中断具有_IRQ_NOPROBE、_IRQ_NOREQUEST、_IRQ_NOTHREAD属性并且action指向chained_action,表示该中断不能被其他设备申请。
3.中断控制器级联关系的代码体现
同样以PIO与GIC的中断控制器的级联结构为例,该级联关系代码体现在PIO连接GIC的硬件的中断到系统虚拟中断的映射过程,具体实现为函数irq_of_parse_and_map。
如图irq_of_parse_and_map函数主要由两部分实现:of_irq_parse_one & irq_create_of_mapping。
a.of_irq_parse_one解析指定的的device node中的特定中断的of_phandle_args结构数据。
b.irq_create_of_mapping将解析出的硬件中断映射到系统虚拟中断。 因为该PIO连接到
GIC的SPI类型的中断pin上,所以of_phandle_args->np 指向 gic的device node、
of_phandle_args->np->fwnode指向gic_data.domain->fwnode,该中断对应domain为
gic_data.domain。在映射过程中需要使用gic_data.domain.ops函数,通过
gic_data.domain.ops->translate将of_phandle_args的中断解析为GIC对用的硬件中断
号,并通过gic_data.domain.ops->alloc函数将GIC对应的硬件中断号与virq进行关联。
4.连接到PIO设备中断函数的软件执行过程
如上图级联形式的中断处理过程会先经过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。通过中断的级联关系(PIO与GIC的该硬件中断相连接)可知此处的gic_virq->handle_irq对应pio(child interrupt-contoller)的中断处理函数mtk_eint_irq_handler,获取pio对应的中断号和其虚拟中断号,进而执行pio_virq->handle_irq处理连接到PIO该中断的处理函数。
级联中断的处理就是从parent到child中断控制器逐级的获取硬件中断号、根据硬件中断号找到到对应的系统虚拟中断号,通过系统虚拟中断号转向child中断控制器,child中断控制器再获取硬件中断号、转系统虚拟中断、进而确认最终的中断处理函数。