1. 综述
linux kernel版本4.19
1.1 中断系统相关硬件描述
中断硬件系统主要有三种器件参与,各个外设、中断控制器和CPU。各个外设提供irq request line,在发生中断事件的时候,通过irq request line上的电气信号向CPU系统请求处理。外设的irq request line太多,CPU需要一个小伙伴帮他,这就是Interrupt controller。Interrupt Controller是连接外设中断系统和CPU系统的桥梁。**CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interrupt controller的事情。**对于CPU而言,一般有两种中断请求,例如:对于ARM,是IRQ和FIQ信号线,分别让ARM进入IRQ mode和FIQ mode。对于X86,有可屏蔽中断和不可屏蔽中断。
1、HW中断系统的逻辑block图
中断接口:ARM以及GIC组成的中断系统中,具体中断接口的形态就是一个硬件的信号线,通过电平信号传递中断事件
控制接口:CPU和Interrupt Controller之间还需要有控制信息的交流。Interrupt Controller会开放一些寄存器让CPU访问、控制
2、Interrupt controller把中断事件送给哪个CPU?
一般而言,中断控制器可以把中断事件上报给一个CPU或者一组CPU(包括广播到所有的CPU上去)。对于外设类型的中断,当然是送到一个cpu上就OK了,我看不出来要把这样的中断送给多个CPU进行处理的必要性。如果送达了多个cpu,实际上,也应该只有一个handler实际和外设进行交互,另外一个cpu上的handler的动作应该是这样的:发现该irq number对应的中断已经被另外一个cpu处理了,直接退出handler,返回中断现场。IPI的中断不存在这个限制,IPI更像一个CPU之间通信的机制,对这种中断广播应该是毫无压力。
1.2 中断子系统相关的软件框架
linux kernel的中断子系统相关的软件框架图如下所示:
由上面的block图,我们可知linux kernel的中断子系统分成4个部分:
(1)硬件无关的代码,我们称之Linux kernel通用中断处理模块
(2)CPU architecture相关的中断处理。 和系统使用的具体的CPU architecture相关。
(3)Interrupt controller驱动代码 。和系统使用的Interrupt controller相关。
(4)普通外设的驱动。这些驱动将使用Linux kernel通用中断处理模块的API来实现自己的驱动逻辑。
2. IRQ Domain介绍
2.1 概述
在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:
1、IRQ number:CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
2、HW interrupt ID:Interrupt controller用HW interrupt ID来标识外设的中断。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller
系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(我们称之interrupt source),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。
2.2 接口
1、向系统注册irq domain
通用中断处理模块中有一个irq domain的子模块,该模块将这种映射关系分成了三类:
(1)线性映射:其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要满足一定的条件:hw ID不能过大,而且ID排列最好是紧密的
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, -------回调函数
void *host_data) -------私有数据
{
return __irq_domain_add(of_node_to_fwnode(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
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_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}
3)no map。有些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。在这种情况下,不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了。
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);
}
2、为irq domain创建映射
上节的内容主要是向系统注册一个irq domain,具体HW interrupt ID和IRQ number的映射关系都是空的,因此,具体各个irq domain如何管理映射所需要的database还是需要建立的。
创建映射有四个接口函数:
(1)调用irq_create_mapping函数建立HW interrupt ID和IRQ number的映射关系。该接口函数以irq domain和HW interrupt ID为参数,返回IRQ number(这个IRQ number是动态分配的)
static inline unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq)
{
return irq_create_mapping_affinity(host, hwirq, NULL);
}
(2)irq_create_strict_mappings。这个接口函数用来为一组HW interrupt ID建立映射。具体函数的原型定义如下
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。这个接口是利用device tree进行映射关系的建立。具体函数的原型定义如下:
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))
return 0;
return irq_create_of_mapping(&oirq);
}
(4)irq_create_direct_mapping。这是给no map那种类型的interrupt controller使用的
unsigned int irq_create_direct_mapping(struct irq_domain *domain)
2.3 数据结构描述
1、irq domain的callback接口
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token);
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函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)
match是判断一个指定的interrupt controller(node参数)是否和一个irq domain匹配(d参数),如果匹配的话,返回1
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函数中设定。
2、irq domain
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops; -------------callback函数
void *host_data;
unsigned int flags;
unsigned int mapcount;
/* Optional data */
struct fwnode_handle *fwnode; ---------------该interrupt domain对应的interrupt controller的fwnode
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
#endif
/* 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,对于Radix Tree map和no map,该值等于0
struct radix_tree_root revmap_tree; ------------------------Radix Tree map使用到的radix tree root node
struct mutex revmap_tree_mutex;
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。
2.4 中断相关的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的属性。
2.5 Mapping DB的建立
1、概述
系统中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初始化的过程中,创建映射
2、 interrupt controller初始化的过程中,注册irq domain(以GIC为例)
static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
struct fwnode_handle *handle)
{
irq_hw_number_t hwirq_base;
int gic_irqs, irq_base, ret;
...
/*
* For primary GICs, skip over SGIs.
* For secondary GICs, skip over PPIs, too.
*/
if (gic == &gic_data[0] && (irq_start & 31) > 0) {
hwirq_base = 16;
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;
}
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ -------------------------------------------(1)
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
numa_node_id()); --------------------------------------------(2)
gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic); -------------------------------------------(3)
...
}
(1)gic_irqs = 系统支持的所有的中断数目-16。之所以减去16主要是因为root GIC的0~15号HW interrupt 是for SGI的,因此要去掉。也正因为如此hwirq_base从16开始
(2)申请gic_irqs个IRQ资源,从16号开始搜索IRQ number。由于是root GIC,申请的IRQ基本上会从16号开始
(3)向系统注册irq domain并创建映射
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_to_fwnode(of_node), first_hwirq + size,
first_hwirq + size, 0, ops, host_data);
if (domain)
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
3、在各个硬件外设的驱动初始化过程中,创建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))
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_fwspec fwspec;
of_phandle_args_to_fwspec(irq_data, &fwspec);
return irq_create_fwspec_mapping(&fwspec);
}
irq_create_fwspec_mapping:
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
struct irq_domain *domain;
struct irq_data *irq_data;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
int virq;
if (fwspec->fwnode) {
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
if (!domain)
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
} else {
domain = irq_default_domain;
} ---------------------------------------------(1)
if (!domain) {
pr_warn("no irq domain found for %s !\n",
of_node_full_name(to_of_node(fwspec->fwnode)));
return 0;
}
if (irq_domain_translate(domain, fwspec, &hwirq, &type))
return 0; ------------------------------------------------(2)
/*
* WARN if the irqchip returns a type with bits
* outside the sense mask set and clear these bits.
*/
if (WARN_ON(type & ~IRQ_TYPE_SENSE_MASK))
type &= IRQ_TYPE_SENSE_MASK;
/*
* 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) {
/*
* If the trigger type is not specified or matches the
* current trigger type then we are done so return the
* interrupt number.
*/
if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
return virq;
/*
* If the trigger type has not been set yet, then set
* it now and return the interrupt number.
*/
if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {
irq_data = irq_get_irq_data(virq);
if (!irq_data)
return 0;
irqd_set_trigger_type(irq_data, type);
return virq;
}
pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n",
hwirq, of_node_full_name(to_of_node(fwspec->fwnode)));
return 0;
}
if (irq_domain_is_hierarchy(domain)) {
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
if (virq <= 0)
return 0;
} else {
/* Create mapping */
virq = irq_create_mapping(domain, hwirq); ----------------------------------(3)
if (!virq)
return virq;
}
irq_data = irq_get_irq_data(virq);
if (!irq_data) {
if (irq_domain_is_hierarchy(domain))
irq_domain_free_irqs(virq, 1);
else
irq_dispose_mapping(virq);
return 0;
}
/* Store trigger type */
irqd_set_trigger_type(irq_data, type);
return virq;
}
(1)这里的代码主要是找到irq domain
(2)如果没有定义xlate函数,那么取interrupts属性的第一个cell作为HW interrupt ID。
static int irq_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
if (d->ops->translate)
return d->ops->translate(d, fwspec, hwirq, type);
#endif
if (d->ops->xlate)
return d->ops->xlate(d, to_of_node(fwspec->fwnode),
fwspec->param, fwspec->param_count,
hwirq, type);
/* If domain has no translation, then we assume interrupt line */
*hwirq = fwspec->param[0];
return 0;
}
(3) 调用irq_create_mapping函数来创建HW interrupt ID和IRQ number的映射关系。
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
struct device_node *of_node;
int virq;
/* 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);
of_node = irq_domain_get_of_node(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(of_node), NULL); --------分配一个IRQ 描述符以及对应的irq number
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(of_node), virq);
return virq;
}
具体建立映射的函数是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;
if (WARN(hwirq >= domain->hwirq_max,
"error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
return -EINVAL;
if (WARN(!irq_data, "error: virq%i is not allocated", virq))
return -EINVAL;
if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
return -EINVAL;
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) { -------调用irq domain的map callback函数
ret = domain->ops->map(domain, virq, hwirq);
if (ret != 0) {
/*
* If map() returns -EPERM, this interrupt is protected
* by the firmware or some other service and shall not
* be mapped. Don't bother telling the user about it.
*/
if (ret != -EPERM) {
pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
domain->name, hwirq, virq, ret);
}
irq_data->domain = NULL;
irq_data->hwirq = 0;
mutex_unlock(&irq_domain_mutex);
return ret;
}
/* If not already assigned, give the domain the chip's name */
if (!domain->name && irq_data->chip)
domain->name = irq_data->chip->name;
}
domain->mapcount++;
irq_domain_set_mapping(domain, hwirq, irq_data); -----填写线性映射lookup table的数据
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST); ----该IRQ已经可以申请了,因此clear相关flag
return 0;
}
2.6 将HW interrupt ID转成IRQ number
创建了庞大的HW interrupt ID到IRQ number的mapping DB,最终还是要使用。具体的使用场景是在CPU相关的处理函数中,程序会读取硬件interrupt ID,并转成IRQ number,调用对应的irq event handler。在本章中,我们以一个级联的GIC系统为例,描述转换过程。
1、GIC driver初始化(second GIC)
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)
{
/*
* For primary GICs, skip over SGIs.
* For secondary GICs, skip over PPIs, too.
* /
if (gic == &gic_data[0] && (irq_start & 31) > 0) {
hwirq_base = 16;
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32; ---------------(1)
}
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
numa_node_id()); ------------------(2)
if (irq_base < 0) {
WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
irq_start);
irq_base = irq_start;
}
gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic); ---------(3)
}
(1)系统支持的所有的中断数目-32。之所以减去32主要是因为对于second GIC,其0~15号HW interrupt 是for IPI的,因此要去掉。而16~31号HW interrupt 是for PPI的,也要去掉。也正因为如此hwirq_base从32开始
(2)申请gic_irqs个IRQ资源,从16号开始搜索IRQ number。由于是second GIC,申请的IRQ基本上会从root GIC申请的最后一个IRQ号+1开始
(3)向系统注册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
对于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
}
……
}
对于second GIC,它是作为其parent(root GIC)的一个普通的irq source,因此,也需要注册该IRQ的handler。由此可见,非root的GIC的初始化分成了两个部分:一部分是作为一个interrupt controller,执行和root GIC一样的初始化代码。另外一方面,GIC又作为一个普通的interrupt generating device,需要象一个普通的设备驱动一样,注册其中断handler。
void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
irq_set_chained_handler_and_data(irq, gic_handle_cascade_irq,
&gic_data[gic_nr]); --------设置handler data与handler
}
2、具体如何在中断处理过程中,将HW interrupt ID转成IRQ number
在系统的启动过程中,经过了各个interrupt controller以及各个外设驱动的努力,整个interrupt系统的database(将HW interrupt ID转成IRQ number的数据库)已经建立。一旦发生硬件中断,经过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)。
参考:蜗窝科技