返回目录:《ARM-Linux中断系统》。html
总结:1、二概述了软硬件不一样角度的IRQ Number和HW Interrupt ID,这就须要他们之间架个桥梁。node
三介绍了架设这种桥梁的几种方式:Linear、Radix Tree和no map。linux
四介绍了两种基础数据结构描述中断域的irq_domain及针对中断域的操做函数。程序员
五针对中断DeviceTree的个属性进行了解释。数据库
六介绍了从DT到中断映射数据库的过程,也即HW interrupt ID到IRQ number之间的映射关系。数据结构
七介绍了实际使用中从中断触发时的HW interrupt ID如何映射到IRQ number,进而调用中断例程的步骤。架构
原文地址:《Linux kernel的中断子系统之(二):IRQ Domain介绍》oracle
1、概述app
在linux kernel中,咱们使用下面两个ID来标识一个来自外设的中断:dom
一、IRQ number。CPU须要为每个外设中断编号,咱们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
二、HW interrupt ID。对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,所以,interrupt controller须要对外设中断进行编码。Interrupt controller用HW interrupt ID来标识外设的中断。在interrupt controller级联的状况下,仅仅用HW interrupt ID已经不能惟一标识一个外设中断,还须要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不一样的Interrupt controller上是会重复编码的)。
这样,CPU和interrupt controller在标识中断上就有了一些不一样的概念,可是,对于驱动工程师而言,咱们和CPU视角是同样的,咱们只但愿获得一个IRQ number,而不关系具体是那个interrupt controller上的那个HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不须要修改。所以,linux kernel中的中断子系统须要提供一个将HW interrupt ID映射到IRQ number上来的机制,这就是本文主要的内容。
Notes:两种视角的中断号:IRQ Number:从驱动软件来看,CPU对每一个中断进行编号。HW interrupt ID:从中断控制起来看,每一个中断控制器上的中断都有一个编号。
这两种不一样视角就致使了,从硬件到软件的一个转换。
2、历史
关于HW interrupt ID映射到IRQ number上 这事,在过去系统只有一个interrupt controller的时候仍是很简单的,中断控制器上实际的HW interrupt line的编号能够直接变成IRQ number。例如咱们你们都熟悉的SOC内嵌的interrupt controller,这种controller多半有中断状态寄存器,这个寄存器可能有64个bit(也可能更多),每一个bit就是一个IRQ number,能够直接进行映射。这时候,GPIO的中断在中断控制器的状态寄存器中只有一个bit,所以全部的GPIO中断只有一个IRQ number,在该通用GPIO中断的irq handler中进行deduplex,将各个具体的GPIO中断映射到其相应的IRQ number上。若是你是一个足够老的工程师,应该是经历过这个阶段的。
随着linux kernel的发展,将interrupt controller抽象成irqchip这个概念愈来愈流行,甚至GPIO controller也能够被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了,一个传统意义的中断控制器,一个是GPIO controller type的中断控制器。随着系统复杂度加大,外设中断数据增长,实际上系统能够须要多个中断控制器进行级联,面对这样的趋势,linux kernel工程师如何应对?答案就是irq domain这个概念。
咱们据说过不少的domain,power domain,clock domain等等,所谓domain,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。系统中全部的interrupt controller会造成树状结构,对于每一个interrupt controller均可以链接若干个外设的中断请求(咱们称之interrupt source),interrupt controller会对链接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。
Notes:中断控制器级联,致使多irq domain。HW interrupt ID就只能在本irq domain有效,在驱动全局范围内就须要进行不一样映射。
3、接口
一、向系统注册irq domain
具体如何进行映射是interrupt controller本身的事情,不过,有软件架构思想的工程师更愿意对形形色色的interrupt controller进行抽象,对如何进行HW interrupt ID到IRQ number映射关系上进行进一步的抽象。所以,通用中断处理模块中有一个irq domain的子模块,该模块将这种映射关系分红了三类:
(1)线性映射。其实就是一个lookup table,HW interrupt ID做为index,经过查表能够获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要知足必定的条件:hw ID不能过大,并且ID排列最好是紧密的。对于线性映射,其接口API以下:
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size,---------该interrupt domain支持多少IRQ
const struct irq_domain_ops *ops,---callback函数
void *host_data)-----driver私有数据
{
return __irq_domain_add(of_node, size, size, 0, ops, host_data);
}
(2)Radix Tree map。创建一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID做为lookup key,在Radix Tree检索到IRQ number。若是的确不能知足线性映射的条件,能够考虑Radix Tree map。实际上,内核中使用Radix Tree map的只有powerPC和MIPS的硬件平台。对于Radix Tree map,其接口API以下:
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node, 0, ~0, 0, ops, host_data);
}
(3)no map。有些中断控制器很强,能够经过寄存器配置HW interrupt ID而不是由物理链接决定的。例如PowerPC 系统使用的MPIC (Multi-Processor Interrupt Controller)。在这种状况下,不须要进行映射,咱们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不须要进行mapping了。对于这种类型的映射,其接口API以下:
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
unsigned int max_irq,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node, 0, max_irq, max_irq, ops, host_data);
}
这类接口的逻辑很简单,根据本身的映射类型,初始化struct irq_domain中的各个成员,调用__irq_domain_add将该irq domain挂入irq_domain_list的全局列表。
二、为irq domain建立映射
上节的内容主要是向系统注册一个irq domain,具体HW interrupt ID和IRQ number的映射关系都是空的,所以,具体各个irq domain如何管理映射所须要的database仍是须要创建的。例如:对于线性映射的irq domain,咱们须要创建线性映射的lookup table,对于Radix Tree map,咱们要把那个反应IRQ number和HW interrupt ID的Radix tree创建起来。建立映射有四个接口函数:
(1)调用irq_create_mapping函数创建HW interrupt ID和IRQ number的映射关系。该接口函数以irq domain和HW interrupt ID为参数,返回IRQ number(这个IRQ number是动态分配的)。该函数的原型定义以下:
extern unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq);
驱动调用该函数的时候必须提供HW interrupt ID,也就是意味着driver知道本身使用的HW interrupt ID,而通常状况下,HW interrupt ID其实对具体的driver应该是不可见的,不过有些场景比较特殊,例如GPIO类型的中断,它的HW interrupt ID和GPIO有着特定的关系,driver知道本身使用那个GPIO,也就是知道使用哪个HW interrupt ID了。
(2)irq_create_strict_mappings。这个接口函数用来为一组HW interrupt ID创建映射。具体函数的原型定义以下:
extern int irq_create_strict_mappings(struct irq_domain *domain,
unsigned int irq_base,
irq_hw_number_t hwirq_base, int count);
(3)irq_create_of_mapping。看到函数名字中的of(open firmware),我想你也能够猜到了几分,这个接口固然是利用device tree进行映射关系的创建。具体函数的原型定义以下:
extern unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data);
一般,一个普通设备的device tree node已经描述了足够的中断信息,在这种状况下,该设备的驱动在初始化的时候能够调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并创建映射关系,具体代码以下:
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
}
对于一个使用Device tree的普通驱动程序(咱们推荐这样作),基本上初始化须要调用irq_of_parse_and_map获取IRQ number,而后调用request_threaded_irq申请中断handler。
(4)irq_create_direct_mapping。这是给no map那种类型的interrupt controller使用的,这里再也不赘述。
4、数据结构描述
一、irq domain的callback接口
struct irq_domain_ops抽象了一个irq domain的callback函数,定义以下:
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
void (*unmap)(struct irq_domain *d, unsigned int virq);
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
};
咱们先看xlate函数,语义是翻译(translate)的意思,那么到底翻译什么呢?在DTS文件中,各个使用中断的device node会经过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给kernel以便kernel能够正确的进行driver的初始化动做。这里,interrupts属性所表示的interrupt specifier只能由具体的interrupt controller(也就是irq domain)来解析。而xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。
match是判断一个指定的interrupt controller(node参数)是否和一个irq domain匹配(d参数),若是匹配的话,返回1。实际上,内核中不多定义这个callback函数,实际上struct irq_domain中有一个of_node指向了对应的interrupt controller的device node,所以,若是不提供该函数,那么default的匹配函数其实就是判断irq domain的of_node成员是否等于传入的node参数。
map和unmap是操做相反的函数,咱们描述其中之一就OK了。调用map函数的时机是在建立(或者更新)HW interrupt ID(hw参数)和IRQ number(virq参数)关系的时候。其实,从发生一个中断到调用该中断的handler仅仅调用一个request_threaded_irq是不够的,还须要针对该irq number设定:
(1)设定该IRQ number对应的中断描述符(struct irq_desc)的irq chip
(2)设定该IRQ number对应的中断描述符的highlevel irq-events handler
(3)设定该IRQ number对应的中断描述符的 irq chip data
这些设定不适合由具体的硬件驱动来设定,所以在Interrupt controller,也就是irq domain的callback函数中设定。
二、irq domain
在内核中,irq domain的概念由struct irq_domain表示:
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops; ----callback函数
void *host_data;/* Optional data */
struct device_node *of_node; ----该interrupt domain对应的interrupt controller的device node
struct irq_domain_chip_generic *gc; ---generic irq chip的概念,本文暂不描述/* 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,for Radix Tree map和no map,该值等于0
struct radix_tree_root revmap_tree; ----Radix Tree map使用到的radix tree root node
unsigned int linear_revmap[]; -----线性映射使用的lookup table
};
linux内核中,全部的irq domain被挂入一个全局链表,链表头定义以下:
static LIST_HEAD(irq_domain_list);
struct irq_domain中的link成员就是挂入这个队列的节点。经过irq_domain_list这个指针,能够获取整个系统中HW interrupt ID和IRQ number的mapping DB。host_data定义了底层interrupt controller使用的私有数据,和具体的interrupt controller相关(对于GIC,该指针指向一个struct gic_chip_data数据结构)。
对于线性映射:
(1)linear_revmap保存了一个线性的lookup table,index是HW interrupt ID,table中保存了IRQ number值
(2)revmap_size等于线性的lookup table的size。
(3)hwirq_max保存了最大的HW interrupt ID
(4)revmap_direct_max_irq没有用,设定为0。revmap_tree没有用。
对于Radix Tree map:
(1)linear_revmap没有用,revmap_size等于0。
(2)hwirq_max没有用,设定为一个最大值。
(3)revmap_direct_max_irq没有用,设定为0。
(4)revmap_tree指向Radix tree的root node。
5、中断相关的Device Tree知识回顾
想要进行映射,首先要了解interrupt controller的拓扑结构。系统中的interrupt controller的拓扑结构以及其interrupt request line的分配状况(分配给哪个具体的外设)都在Device Tree Source文件中经过下面的属性给出了描述。这些内容在Device Tree的三份文档中给出了一些描述,这里简单总结一下:
对于那些产生中断的外设,咱们须要定义interrupt-parent和interrupts属性:
(1)interrupt-parent。代表该外设的interrupt request line物理的链接到了哪个中断控制器上
(2)interrupts。这个属性描述了具体该外设产生的interrupt的细节信息(也就是传说中的interrupt specifier)。例如:HW interrupt ID(由该外设的device node中的interrupt-parent指向的interrupt controller解析)、interrupt触发类型等。
对于Interrupt controller,咱们须要定义interrupt-controller和#interrupt-cells的属性:
(1)interrupt-controller。代表该device node就是一个中断控制器
(2)#interrupt-cells。该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interrupt request line。?具体每一个cell表示什么样的含义由interrupt controller本身定义。
(3)interrupts和interrupt-parent。对于那些不是root 的interrupt controller,其自己也是做为一个产生中断的外设链接到其余的interrupt controller上,所以也须要定义interrupts和interrupt-parent的属性。
6、Mapping DB的创建
一、概述
系统中HW interrupt ID和IRQ number的mapping DB是在整个系统初始化的过程当中创建起来的,过程以下:
(1)DTS文件描述了系统中的interrupt controller以及外设IRQ的拓扑结构,在linux kernel启动的时候,由bootloader传递给kernel(实际传递的是DTB)。
(2)在Device Tree初始化的时候,造成了系统内全部的device node的树状结构,固然其中包括全部和中断拓扑相关的数据结构(全部的interrupt controller的node和使用中断的外设node)
(3)在machine driver初始化的时候会调用of_irq_init函数,在该函数中会扫描全部interrupt controller的节点,并调用适合的interrupt controller driver进行初始化。毫无疑问,初始化须要注意顺序,首先初始化root,而后first level,second level,最好是leaf node。在初始化的过程当中,通常会调用上节中的接口函数向系统增长irq domain。有些interrupt controller会在其driver初始化的过程当中建立映射
(4)在各个driver初始化的过程当中,建立映射
Notes:从最开始的DTB文件,到初始化DeviceTree的时候关于中断拓扑数据结构,而后在of_irq_init中调用合适的驱动进行初始化,最后在驱动初始化中建立映射关系。
二、 interrupt controller初始化的过程当中,注册irq domain
咱们以GIC的代码为例。具体代码在gic_of_init->gic_init_bases中,以下:
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;
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;……
对于root GIC
hwirq_base = 16;
gic_irqs = 系统支持的全部的中断数目-16。之因此减去16主要是由于root GIC的0~15号HW interrupt 是for IPI的,所以要去掉。也正由于如此hwirq_base从16开始irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());申请gic_irqs个IRQ资源,从16号开始搜索IRQ number。因为是root GIC,申请的IRQ基本上会从16号开始
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);---向系统注册irq domain并建立映射……
}
很遗憾,在GIC的代码中没有调用标准的注册irq domain的接口函数。要了解其背后的缘由,咱们须要回到过去。在旧的linux kernel中,ARM体系结构的代码不甚理想。在arch/arm目录充斥了不少board specific的代码,其中定义了不少具体设备相关的静态表格,这些表格规定了各个device使用的资源,固然,其中包括IRQ资源。在这种状况下,各个外设的IRQ是固定的(若是做为驱动程序员的你足够老的话,应该记得很长篇幅的针对IRQ number的宏定义),也就是说,HW interrupt ID和IRQ number的关系是固定的。一旦关系固定,咱们就能够在interupt controller的代码中建立这些映射关系。具体代码以下:
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
unsigned int size,
unsigned int first_irq,
irq_hw_number_t first_hwirq,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;domain = __irq_domain_add(of_node, first_hwirq + size,----注册irq domain
first_hwirq + size, 0, ops, host_data);
if (!domain)
return NULL;irq_domain_associate_many(domain, first_irq, first_hwirq, size); ---建立映射
return domain;
}
这时候,对于这个版本的GIC driver而言,初始化以后,HW interrupt ID和IRQ number的映射关系已经创建,保存在线性lookup table中,size等于GIC支持的中断数目,具体以下:
index 0~15对应的IRQ无效
16号IRQ <------------------>16号HW interrupt ID
17号IRQ <------------------>17号HW interrupt ID
……
若是想充分发挥Device Tree的威力,3.14版本中的GIC 代码须要修改。
三、在各个硬件外设的驱动初始化过程当中,建立HW interrupt ID和IRQ number的映射关系
咱们上面的描述过程当中,已经说起:设备的驱动在初始化的时候能够调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并创建映射关系,具体代码以下:
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_create_of_mapping函数如何建立映射:
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;
unsigned int virq;domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;--A
if (!domain) {
return 0;
}if (domain->ops->xlate == NULL)--------------B
hwirq = irq_data->args[0];
else {
if (domain->ops->xlate(domain, irq_data->np, irq_data->args,----C
irq_data->args_count, &hwirq, &type))
return 0;
}/* Create mapping */
virq = irq_create_mapping(domain, hwirq);--------D
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);---------E
return virq;
}
A:这里的代码主要是找到irq domain。这是根据传递进来的参数irq_data的np成员来寻找的,具体定义以下:
struct of_phandle_args {
struct device_node *np;---指向了外设对应的interrupt controller的device node
int args_count;-------该外设定义的interrupt相关属性的个数
uint32_t args[MAX_PHANDLE_ARGS];----具体的interrupt至关属性的定义
};
B:若是没有定义xlate函数,那么取interrupts属性的第一个cell做为HW interrupt ID。
C:解铃还需系铃人,interrupts属性最好由interrupt controller(也就是irq domain)解释。若是xlate函数可以完成属性解析,那么将输出参数hwirq和type,分别表示HW interrupt ID和interupt type(触发方式等)。
D:解析完了,最终仍是要调用irq_create_mapping函数来建立HW interrupt ID和IRQ number的映射关系。
E:若是有须要,调用irq_set_irq_type函数设定trigger type
irq_create_mapping函数创建HW interrupt ID和IRQ number的映射关系。该接口函数以irq domain和HW interrupt ID为参数,返回IRQ number。具体的代码以下:
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
unsigned int hint;
int virq;若是映射已经存在,那么不须要映射,直接返回
virq = irq_find_mapping(domain, hwirq);
if (virq) {
return virq;
}hint = hwirq % nr_irqs;-------分配一个IRQ 描述符以及对应的irq number
if (hint == 0)
hint++;
virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node));
if (virq <= 0)
virq = irq_alloc_desc_from(1, 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)) {---创建mapping
irq_free_desc(virq);
return 0;
}return virq;
}
对于分配中断描述符这段代码,后续的文章会详细描述。这里简单略过,反正,指向完这段代码,咱们就能够或者一个IRQ number以及其对应的中断描述符了。程序注释中没有使用IRQ number而是使用了virtual interrupt number这个术语。virtual interrupt number仍是重点理解“virtual”这个词,所谓virtual,其实就是说和具体的硬件链接没有关系了,仅仅是一个number而已。具体创建映射的函数是irq_domain_associate函数,代码以下:
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq)
{
struct irq_data *irq_data = irq_get_irq_data(virq);
int ret;mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq);---调用irq domain的map callback函数
}if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;----填写线性映射lookup table的数据
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);--向radix tree插入一个node
mutex_unlock(&revmap_trees_mutex);
}
mutex_unlock(&irq_domain_mutex);irq_clear_status_flags(virq, IRQ_NOREQUEST); ---该IRQ已经能够申请了,所以clear相关flag
return 0;
}
7、将HW interrupt ID转成IRQ number
建立了庞大的HW interrupt ID到IRQ number的mapping DB,最终仍是要使用。具体的使用场景是在CPU相关的处理函数中,程序会读取硬件interrupt ID,并转成IRQ number,调用对应的irq event handler。在本章中,咱们以一个级联的GIC系统为例,描述转换过程
一、GIC driver初始化
上面已经描述了root GIC的的初始化,咱们再来看看second GIC的初始化。具体代码在gic_of_init->gic_init_bases中,以下:
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;
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;……
对于second GIC
hwirq_base = 32;
gic_irqs = 系统支持的全部的中断数目-32。之因此减去32主要是由于对于second GIC,其0~15号HW interrupt 是for IPI的,所以要去掉。而16~31号HW interrupt 是for PPI的,也要去掉。也正由于如此hwirq_base从32开始irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());申请gic_irqs个IRQ资源,从16号开始搜索IRQ number。因为是second GIC,申请的IRQ基本上会从root GIC申请的最后一个IRQ号+1开始
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);---向系统注册irq domain并建立映射……
}
second GIC初始化以后,该irq domain的HW interrupt ID和IRQ number的映射关系已经创建,保存在线性lookup table中,size等于GIC支持的中断数目,具体以下:
index 0~32对应的IRQ无效
root GIC申请的最后一个IRQ号+1 <------------------>32号HW interrupt ID
root GIC申请的最后一个IRQ号+2 <------------------>33号HW interrupt ID
……
OK,咱们回到gic的初始化函数,对于second GIC,还有其余部分的初始化内容:
int __init gic_of_init(struct device_node *node, struct device_node *parent)
{……
if (parent) {
irq = irq_of_parse_and_map(node, 0);--解析second GIC的interrupts属性,并进行mapping,返回IRQ number
gic_cascade_irq(gic_cnt, irq);---设置handler
}
……
}
上面的初始化函数去掉和级联无关的代码。对于root GIC,其传入的parent是NULL,所以不会执行级联部分的代码。对于second GIC,它是做为其parent(root GIC)的一个普通的irq source,所以,也须要注册该IRQ的handler。因而可知,非root的GIC的初始化分红了两个部分:一部分是做为一个interrupt controller,执行和root GIC同样的初始化代码。另一方面,GIC又做为一个普通的interrupt generating device,须要象一个普通的设备驱动同样,注册其中断handler。
irq_of_parse_and_map函数相信你们已经熟悉了,这里再也不描述。gic_cascade_irq函数以下:
void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
if (irq_set_handler_data(irq, &gic_data[gic_nr]) != 0)---设置handler data
BUG();
irq_set_chained_handler(irq, gic_handle_cascade_irq);---设置handler
}
二、具体如何在中断处理过程当中,将HW interrupt ID转成IRQ number
在系统的启动过程当中,通过了各个interrupt controller以及各个外设驱动的努力,整个interrupt系统的database(将HW interrupt ID转成IRQ number的数据库,这里的数据库不是指SQL lite或者oracle这样通用数据库软件)已经创建。一旦发生硬件中断,通过CPU architecture相关的中断代码以后,会调用irq handler,该函数的通常过程以下:
(1)首先找到root interrupt controller对应的irq domain。
(2)根据HW 寄存器信息和irq domain信息获取HW interrupt ID
(3)调用irq_find_mapping找到HW interrupt ID对应的irq number
(4)调用handle_IRQ(对于ARM平台)来处理该irq number
对于级联的状况,过程相似上面的描述,可是须要注意的是在步骤4中不是直接调用该IRQ的hander来处理该irq number由于,这个irq须要各个interrupt controller level上的解析。举一个简单的二阶级联状况:假设系统中有两个interrupt controller,A和B,A是root interrupt controller,B链接到A的13号HW interrupt ID上。在B interrupt controller初始化的时候,除了初始化它做为interrupt controller的那部份内容,还有初始化它做为root interrupt controller A上的一个普通外设这部分的内容。最重要的是调用irq_set_chained_handler设定handler。这样,在上面的步骤4的时候,就会调用13号HW interrupt ID对应的handler(也就是B的handler),在该handler中,会重复上面的(1)~(4)。
原创文章,转发请注明出处。蜗窝科技。http://www.wowotech.net/linux_kenrel/irq-domain.html