蜗窝科技

Linux kernel的中断子系统之(二):IRQ Domain介绍

linuxer · · 中断子系统 · 阅读 152412 · 2014年8月存档

一、概述

在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:

1、IRQ number。CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。

2、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上来的机制,这就是本文主要的内容。

二、历史

关于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范围内。

三、接口

1、向系统注册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的全局列表。

2、为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使用的,这里不再赘述。

四、数据结构描述

1、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函数中设定。

2、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。

五、中断相关的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的属性。

六、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的代码为例。具体代码在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 代码需要修改。

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))----分析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;
}

七、将HW interrupt ID转成IRQ number

创建了庞大的HW interrupt ID到IRQ number的mapping DB,最终还是要使用。具体的使用场景是在CPU相关的处理函数中,程序会读取硬件interrupt ID,并转成IRQ number,调用对应的irq event handler。在本章中,我们以一个级联的GIC系统为例,描述转换过程

1、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
}

2、具体如何在中断处理过程中,将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)。

原创文章,转发请注明出处。蜗窝科技。/irq_subsystem/irq-domain.html

标签:irq_domain

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(80 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • forion

    ========================= 每个interrupt controller都可以产生一个HW interrupt,我们可以在该interrupt controller范围内对该HW interrupt ID(根据其在Interrupt controller中物理特性)进行编号。但这个编号仅仅限制在本interrupt controller范围内 ========================= 这里没法体会到这几句话的寓意,一个HW interrupt,进行编号,不太理解是什么意思。 我的理解每个interrupt controller都可以产生一个HW interrupt,但是是这个interrupt controller的上一级的interrupt controller对这个HW interrupt进行编号啊。 最上面一级是GIC
    • forion

      @forion:GIC是唯一一个可以产生多个HW interrupt的 是这么理解吗?
      • linuxer

        @forion:是我写错了,我已经修改了相关的描述。 多谢你的review!文档的peer review绝对是必要的,多提comments,有些文字也许是我表述不清楚
  • forion

    因此所有的GPIO中断只有一个IRQ number,在该通用GPIO中断的irq handler中进行deduplex,将各个具体的GPIO中断映射到其相应的IRQ number上 ============================== 那gpio controller的IRQ number跟具体的GPIO中断的IRQ number有什么关系呢? ============================== 另外我一直有个疑问也在这里提出来一下吧,就是现在的gpio controller,也做成了一个irq的 controller,我一直的理解,gpio controller只会有一个IRQ number,也就是说有一根线连接到root的irq conroller上面。但是所有的有中断功能的gpio口一般系统都会分配一个IRQ number,这个IRQ number就是比如request_irq所使用的IRQ number。但是从CPU的角度,比如一个gpio中断触发了,我cpu检测到的是gpio contoller的中断号,然后再gpio contoller的handler里面再找出是那个具体的gpio口,然后可能有很多级,最后转换成最终的IRQ number,也就是request_irq()里面用的的那个中断号。我的理解有问题吗??
    • linuxer

      @forion:看来你也是一个老驱动工程师了,你描述的应该是过去内核采取的方法。我在2.6.23内核上工作过一段时间,用的是PXA270的平台。其IRQ number是这么分配的: 0~63,对应SOC上interrupt controller的64个中断源 64开始的128个IRQ分配给了GPIO对应的中断源 当然IRQ=10那个中断比较特殊,它表示是一个普通的GPIO中断。该中断不能被request,在系统初始化的时候只是设定了了highlevel irq event handler。代码如下: set_irq_chained_handler(IRQ_GPIO_2_x, pxa_gpio_demux_handler); 所有普通的GPIO对应的IRQ number也可以自由的request,但是,系统不能直接转换成对应的IRQ number,中断系统总是发现是IRQ=10的中断被触发了,因此调用 pxa_gpio_demux_handler,看看具体是哪一个发生了中断,转换成对应的IRQ number,然后直接调用desc_handle_irq来处理该irq -------------------------------------- -------------------------------------- 上面是过去式。对于新的内核,首先不会有静态定义的IRQ number(当然,有些SOC还保留了arch/arm/mach-xxx/include/mach/irqs.h文件,但不是内核推荐的方式),具体的驱动的IRQ number是动态分配的。在驱动程序初始化的时候,可以通过irq_of_parse_and_map函数分析该设备的device tree中和中断相关内容(获取该设备使用哪一个interrupt controller,哪一个HW interrupt ID,触发类型为何),并创建映射。在这种情况下,发生了一个GPIO中断,可以直接被翻译成对应的IRQ number(不需要一个通用GPIO中断,然后demux)
      • linuxer

        @linuxer:不对不对,我又思考了一下,还是需要demux的。我们假设下面的架构: soc interrupt controller <-------> GPIO interrupt controller 最终的翻译过程需要两步,首先是root interrupt controller对应的irq domain,将HW interrupt ID(GPIO interrupt controller对应的那个)翻译成gpio controller的IRQ number。之后,进入GPIO irq domain,继续执行第二阶的翻译,获取最终的IRQ number
        • forion

          @linuxer:作为一个工作4年的小菜鸟,不敢在您面前称作老工程师啊。:) 感觉您比那个伟东山,宋宝华之流都厉害啊。你要是开讲座,卖视频或者去培训学校,那不照样是大赚啊? anyway,还是感谢您的分享。 博主加油! 后续的文章我会继续默默的review,然后提问题的。就麻烦您回答啦。
          • linuxer

            @forion:不敢当,不敢当~~~我也只是一只比你工作经验多几年的普通工程师而已(我99年毕业,从2.4内核做到2.6内核,现在到3.14内核,不过,话说回来,像我这么老的工程师的确不多啊) 我特地百度了一下你提及的那两个人,原来还可以买视频啊,真是让我大跌眼镜。不过说起培训机构,我一直都是挺有兴趣的(本质上,我这人好为人师,嘿嘿),不过一般而言,在公司赚的会比当培训老师多一些,唉,目前还没有达到财务自由,还需要出卖劳动力啊。
            • forion

              @linuxer:卖视频是比培训机构更为自由的一种经营模式啊,像个体户。这么浮躁的社会环境 ,您能这么静心,不容易啊,让人佩服.
              • linuxer

                @forion:如果能卖视频赚钱养活自己当然好,可是卖出一份之后不就在网上传开了吗?不就没有办法继续卖了吗?我是有些置疑这种培训模式是否能够盈利。 视频培训当然好,方便,不过还是面对面的授课效果比较好,可以互动。 大家都是说整个社会环境比较浮躁,不过这是由这个时代决定的,当前的中国正处在高速发展的过程中,等到10年后,发展平稳的时候,注定是要从繁华归于平静。这个小站就是希望能汇聚一些愿意静下心来,研究内核和各种技术。我倒是愿意每天阅读内核,写写文章,不过那种理想的生活暂时还比较远
              • ikirin1979

                @forion:浮华过去,市场会认可您的价值的。
  • forion

    这个回复的次数是不是有上限?卖视频是卖的那种加密的,不过他最开始是免费开放一期,然后让大家感觉听一听感觉不错,然后就卖,剩下的视频是加密的,必须要买他的授权才能看.没有授权的,视频能下载,但是不能播放.而且那个加密的播放器,也不能翻录,如果翻录的话,会有你的id啊,他就知道是你翻录的,此类的.大体就是这个套路.哈哈~~~
    • linuxer

      @forion:恩,学了一招。
    • wowo

      @forion:同一个回复的层数确实有限制,不然回复多了,一直右缩进,就不好看了。
  • 海

    拜读!多谢分享!
  • linxi_hnh

    @linuxer,文章写的真好。 1 针对接口部分的第1篇章:是不是少了irq_domain_legacy_revmap()这个接口函数?? 2 针对hw irq number和 gpio之间的对应关系,我有一个疑问,若hw irq number和gpio之间是一一对应的。例如hw irq number为34,对应的gpio号为5; hwirq number为35是对应的gpio号为6.这样的对应关系,可以采用irq domain的架构吗?调用哪一个接口函数比较好?? 3 针对hwirq number和irq number的对应,我现在晕晕糊糊的。
    • linuxer

      @linxi_hnh:多谢! 1、irq_domain_legacy_revmap()函数的legacy已经说明这是一个要被废弃的接口。在最新的3.14内核中,我没有找到它 2、irq domain是应对HW interrupt ID和IRQ number之间的关系,实际上,irq domain并不关心GPIO的编码。你说的这个场景应该是GPIO type的interrupt controller的实现,一般而言,GPIO type的interrupt controller要关注HW interrupt到GPIO之间的映射,例如HW interrupt ID=0,对应GPA中的0号GPIO,HW interrupt ID=1,对应GPA中的1号GPIO,......,使用哪一种irq domain是和你如何规划IRQ number到HW interrupt ID之间的映射相关的,一般而言,我们会采用线性映射方式的 3、呵呵,其实很简单啊,你可以再具体说说那里比较迷糊,我可以进一步表述
      • passerby

        @linuxer:liuxer,有点疑问是关于gpio interrupt contronller和HW interrupt id的。 1、HW interrupt id是直接从寄存器里面读取到的中断线号吗? 2、现在很多都采用gpio type interrupt controller做的request_irq,我们一般是向gpio号申请中断。这个中断号和HW interrupt id怎么联系起来的。
        • linuxer

          @passerby:1、HW interrupt id是直接从寄存器里面读取到的中断线号吗? ------------------------------- 是的,中断控制器对自己管控的interrupt request line进行编码其实就是HW interrupt ID,一般而言都是读取中断状态寄存器可以转换成HW interrupt ID 2、现在很多都采用gpio type interrupt controller做的request_irq,我们一般是向gpio号申请中断。这个中断号和HW interrupt id怎么联系起来的。 -------------------------------- 驱动调用的request_irq使用的irq我们称之IRQ number或者virtual interrupt ID,这个ID和硬件没有任何的关系,可以任意分配。 当然,对于老的系统,实际上还是固定分配的,即在初始化的时候就建立的了GPIO number、HW interrupt ID和IRQ Number之间的关系,因此,驱动才可以直接使用GIO 号来申请中断。
  • schedule

    楼主能否分析一下irq_gic_v3.c的代码?有许多疑惑想和大神交流一下。
    • linuxer

      @schedule:我正在写一个编译、链接和加载的文章,既然你有兴趣,那我先完成手头的这篇,然后再写如irq_gic_v3.c的代码分析文章好了
  • 强

    @linuxer 文章中有两个地方不是很清楚,能解答下吗? 第一个: 对于root GIC hwirq_base = 16; gic_irqs = 系统支持的所有的中断数目-16。之所以减去16主要是因为root GIC的0~15号HW interrupt 是for IPI的,因此要去掉。也正因为如此hwirq_base从16开始 #这里说的IPI是什么意思?看gic的文档这里说的是SGI,IPI是什么,这两个是一个东西吗? 第二个问题: index 0~15对应的IRQ无效 16号IRQ <------------------>16号HW interrupt ID 17号IRQ <------------------>17号HW interrupt ID #这里是说HW interrupt 和 IRQ number 从16号中断开始,是一一对应的意思吗? 我在device tree 中申请了这样一个中断 interrupts = <0 4 0x4> 然后我用 interrupt = platform_get_resource(dev->pdev, IORESOURCE_IRQ, 0); 并将interrupt->start 发现其值是36, 这里就有了两个疑问, 一:申请的是4号中断,4号中断在0-15号中断区间,映射到linux IRQ number 变成了36呢?差值是32 二:类似的,我又申请了<0 31 0x4>中断,打印出来interrupt->start 是63,差值是32, 请linuxer有空解答下哈。
    • tigger

      @强:我先回答一下你吧 ------------------------------- #这里说的IPI是什么意思?看gic的文档这里说的是SGI,IPI是什么,这两个是一个东西吗? Inter-processor interrupts (IPI) can, for example, be used for interrupt-driven communication between CPUs in an SMP system. The GIC implements IPI using 16 software interrupts. To generate an IPI, an MPCore CPU must write to the primary GIC's Software Interrupt register specifying an interrupt ID in the range 0 to 15 and which CPU(s) will receive the interrupt. 从上面来看,SGI跟IPI是同一个东西 ----------------------------------------------------- #这里是说HW interrupt 和 IRQ number 从16号中断开始,是一一对应的意思吗? hw interrrupt指的是硬件的中断号我的理解这里指的是 实际接到gic上面的硬件中断线,IRQ number其实指的是软件中断号。这两个应该是一一对应的。当然如果接到gic的只是一个子的中断控制器,比如gpio中断控制器,那么任意一个gpio的IRQ number,跟这个hw interrupt并不是一一对应的。 --------------------------------------- 3:在dt里面写的是SPI,Shared Peripheral Interrupt ,你通过dt 解出来的中断号是要+32的,因为我的理解,物理上,他是接到SPI上,SPI就是从32开始
      • 强

        @tigger:@trigger 回复的好快啊,非常感谢,基本明白了,特别是第三问题,一直没想明白,今天算是解惑了。 支持你们。
        • tigger

          @强:my pleasure 可以等linuxer来了再给个更详细的解释。我的理解可能有偏差。
          • linuxer

            @tigger:顶,多谢tigger同学的回答,我就不补充了,呵呵
            • wang

              @linuxer:@linuxer: 刚开始看linux的中断,这个SPI是什么?
      • wang

        @tigger:@linuxer: 刚开始看linux的中断,这个SPI是什么东西?
  • tigger

    hi linuxer hint = hwirq % nr_irqs;-------分配一个IRQ 描述符以及对应的irq number 这里的nr_irqs需要设置吗?我看默认值是64,这个值是不是要设置成实际的硬件支持的所有的硬件中断的个数呢? 我还是有点没有搞明白这里hint的作用,
    • tigger

      @tigger:这个nr_irqs=NR_IRQS 那这个宏就是一个预定义的咯,根据不同的系统写死的一个中断个数咯
      • linuxer

        @tigger:中断描述符(interrupt descriptor)有两种组织形式: 1、组织成静态数组 2、组织成Radix tree 如果组织成静态数组,那么NR_IRQS就是系统中的total的IRQ的数目,最大的irq ID就是NR_IRQS-1了。当引入radix tree之后,事情变得复杂一些,NR_IRQS是预先分配的IRQ数目(估计是为了兼容旧的驱动)。 在中断子系统的代码中经常需要一个当前系统中的最大的IRQ number的值,在单纯而美好的静态数组时代,NR_IRQS就够用了,在引入Radix tree后以及CONFIG_SPARSE_IRQ这样的配置项后,系统中IRQ number的最大值是一个变化的值,随着IRQ number的分配而不断变化,因此内核引入了一个变量来跟踪当前系统中IRQ number的最大值,这个变量就是nr_irqs。在初始化的时候,由于固定分配了NR_IRQS个IRQ number,因此nr_irqs被初始化成NR_IRQS
    • linuxer

      @tigger:nr_irqs这个值随着中断描述符的分配(irq_alloc_descs)而扩展(当然,也有可能不增加,主要看分配的IRQ number是否大于nr_irqs),如何分配IRQ number是通过一个bitmap进行管理的,每次分配IRQ number从bitmap的的0bit开始搜索不是一个好主意,hint是一个可能是一个更合适的值。
      • tigger

        @linuxer:为什么在gic初始化的时候,已经给所有的irq都初始化了desc,我们在这里还是要再申请一次呢? irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); -------(e)
        • linuxer

          @tigger:我没有明白你的问题,在初始化的时候,gic driver会调用irq_alloc_descs一次性的分配了若干个中断描述符,因此nr_irqs也会一次性的进行的扩展。 irq_alloc_descs最终会调用alloc_descs函数分配若干desc,当然,对于静态数据类型的,不会真正分配,仅仅是站上那个位置而已,对于CONFIG_SPARSE_IRQ的那种,的确会分配desc的内存并插入到radix tree中。 对于gic driver,一直都是初始化分配desc啊
          • tigger

            @linuxer:我是说 为什么irq_create_mapping 里面又会再分配一次呢?
            • linuxer

              @tigger:看,沟通还是需要成本的,我的理解是这样的: 对于目前版本的GIC driver,其实它采用了一个比较丑陋的方法: 1、调用irq_alloc_descs一次性的分配了若干个中断描述符 2、调用irq_domain_add_legacy一次性的创建所有的irq number到HW interrupt ID的映射。 也就是说,gic driver初始化之后就一切搞定了,分配好了irq descriptor,创建了映射关系。 而在具体的驱动程序中,虽然有机会调用irq_create_mapping 来创建mapping,不过由于mapping已经存在,因此不会再次分配了
              • tigger

                @linuxer:晓得啦,原来上面有写,可能是当时忽略了。确实是会检查一次。 我之所以有这个疑问是因为我看完这一篇,又读了一下gic的代码,发现gic里面是已经初始化了一遍的,就突然想到这里为什么又分配一遍,忘记了这里其实肯定应该有检查的。kernel 这种历经多年的代码,是不会犯这样的错误的 ^_^ /* Check if mapping already exists */ virq = irq_find_mapping(domain, hwirq); if (virq) { pr_debug("-> existing mapping on virq %d\n", virq); return virq; }
          • gzz

            @linuxer:alloc_descs这个貌似非smp下,是不重新malloc的吗,直接用的那个irq_desc全局数组。那要是用了SMP,是费用在alloc_desc一次吗?
            • linuxer

              @gzz:alloc_descs这个函数和SMP没有关系,适合CONFIG_SPARSE_IRQ这个配置项有关,如果配置了CONFIG_SPARSE_IRQ,那么需要动态分配,否则使用irq_desc全局数组,静态分配
  • tastier

    您好,看了您的文章,对应到自己做的项目,有两个个小问题请教。 1. 对于两个级联的8259A芯片,HW interupt ID是如何编码的?手册对两个级联芯片的中断输入管脚标有IRQ0~IRQ15,这个是编码吗? 2. 内核里的request_irq()函数的第一个参数irq,这个是文中的IRQ number吗?
    • linuxer

      @tastier:为了回答你的问题,我特地下载了8259A的datasheet来看,实际上,8259A支持8个interrupt,当两个8259A进行级联的时候,就形成了两个IRQ Domain,这两个domain的HW interrupt是可以重复的。 回到你的具体问题:1. 对于两个级联的8259A芯片,HW interupt ID是如何编码的? --------------------------------- 我认为每个8259A都是根据自己硬件引脚IR0~IR7进行HW interrupt ID的编码,对应HW interrupt ID 0~HW interrupt ID 7 2. 内核里的request_irq()函数的第一个参数irq,这个是文中的IRQ number吗? ------------------------------- 是的 我是搞ARM的,对X86的东西不是那么熟悉,上面是我的观点,请参考
      • tastier

        @linuxer:非常感谢您的回答,在看了您的回答之后,我又经过几天的学习,产生了如下几个问题。 1. 对于内核中的request_irq()函数,最终调用了request_threaded_irq()函数,但内核源码对于request_threaded_irq函数第一个参数irq的描述是:@irq: Interrupt line to allocate,意思是分配一个中断信号线,不是IRQ number? 我理解Interrupt line应该是HW interrupt ID吧,不知对不对? 2. 我看其它很多关于中断的文章都有中断向量这个概念,不知中断向量和IRQ number的区别在哪里? 与此对应的是,中断描述符表(IDT)和中断描述符(struct irq_desc)。我看过这种描述:“当中断或异常发生时,CPU通过查中断向量号对应IDT的表项决定动作”。您在下一篇文章中这样描述:“通过IRQ number就可以获取对应的中断描述符。调用中断描述符中的highlevel irq-events handler来进行中断处理就OK了”。这里定义了一个struct irq_desc类型的数组,它和IDT的区别是什么? 3. 在您的文章中,引用的代码和定义的概念是否针对ARM,X86不是很适用(有的适用有的不适用,尤其是一些代码,我没有接触过ARM)? 期待您的回复。
        • linuxer

          @tastier:1、我觉得这里的内核注释有些模糊,本质上request_threaded_irq函数第一个参数irq就是就是IRQ number(virtual interrupt ID),这个ID其实是和实际的interrupt request line是没有关系的,只是在irq domain的范围内有映射关系而已。我同意你的说法,interrupt line、interrupt request line或者HW interrupt ID都是类似的概念,如果我是中断子系统的维护者,我会修改这里的注释 2、所谓向量,当然就不是标量,呵呵~~~有点像是废话。标量是一维的,或者说是一个数值量。向量是向量空间的一个元素,说起来有些抽象,我们可以以三维实数坐标空间R3(3应该是上标)为例,(x,y,z)就是R3中的一个元素,当然x,y,z都是实数。 我们再来看中断。对于计算机系统而言,中断就是硬件或者软件向CPU发出的一种信号,希望CPU可以暂停当前程序的执行,转而处理该事件。 根据上面对向量和中断的理解,我们可知中断向量一定就是一组数据了,这一组数据表示了在发生中断后,CPU的各个寄存器(包括PC、状态寄存器和通用寄存器)需要加载的内容。对于ARM,中断向量只有一个地址(interrupt handler的地址)需要加载给CPU的r15寄存器(对于X86,需要加载CS和IP这两个寄存器,我都有些忘记了,好像是这样吧) OK,应该可以进入中断向量表了,所谓中断向量表就是一个table,保存了若干的中断向量而已。中断向量表是一个通用的概念,在X86上,它具体就是体现为中断描述符表。对于ARM,中断向量表更经常的被称为异常向量表。对于ARM,代码如下: ---------------------------------------------- .section .vectors, "ax", %progbits __vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, __vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq ---------------------------IRQ Vector W(b) vector_fiq ---------------------------FRQ Vector ------------------------------------------------------------- 和传统意义上的中断相关的只有两项,毫无疑问,这是不够的,因此,在ARM上进行了两次的跳转,也就是说,一个外设中断发生了,首先跳转到IRQ vector,然后由它再根据当前中断控制器的状态,获取了IRQ number,最终完成了到具体interrupt handler的跳转。 有些处理器不是这样的,对于X86,其中断向量表很大,分的很细(例如除0产生的异常也占有一个vecotr),每一个外设中断都是占用一个中断向量表的entry。 -------------- 我看过这种描述:“当中断或异常发生时,CPU通过查中断向量号对应IDT的表项决定动作”。 --------------这段话应该适用于类似像X86这样的中断向量表设计的CPU,更具体的内容我明天查阅x86的中断处理再回复你 3、没有错,我的文档都是for ARM的,我对X86不是那么熟悉。如果你是搞X86的,那么大家可以多交流互通有无。
          • tastier

            @linuxer:谢谢您的耐心回复,虽然我的问题表述都不是很清楚。 看了您的回复,对几个概念的理解真是如梦初醒,在阅读内核源码就明白多了。 我目前做的小项目是基于AMD x86 cpu的,用到了中断,正在努力学习,还希望能和您这样经验丰富的大牛多多交流。
  • SleepDeXiang

    西安出差两周,终于能继续看这么美妙的文章了,真是写的好,万分膜拜,其实这些都不是我自己工作业务相关的,我就是喜欢内核,闲了就自己看源码,可惜汇编不咋地,一到硬件层面的驱动需要各种查汇编语法
    • linuxer

      @SleepDeXiang:欢迎,欢迎,不论什么工作性质,只要保持一颗对linux kernel的热爱之心就OK了,把这个网站办成一个喜欢钻研linux kernel代码的集中营就是我们最大的心愿
  • gzz

    额,终于明白DT下platform device建立时的resource提取的IRQ原来是irq_of_parse_and_map后的。还一直以为就是interrupt cells里面的hw irq num,原来做了map处理。
  • gzz

    请问,那如果还是使用老是的板级描述符 resource = { .start = IRQ_EINT(irq_num)。 } 这里的还是用实际的物理中断号码?还是?,总不可能自己去映射关联吧。这种模式是直接被淘汰了?
    • linuxer

      @gzz:这种老的模式当然也支持,不过在device tree出现后已经不推荐使用了,如果的确想使用,可以参考platform_get_resource的实现。在resource的定义中有关于irq resource的类型,其start成员和end成员都是定义的irq number(virtual interrupt number),而“IRQ_EINT(irq_num)”中的irq_num是HW interrupt ID,本身arch/arm/mach-xxx/include/mach/irqs.h中定义的一大堆IRQ_XXX就是将HW interrupt ID映射到irq number的宏定义,也就是建立了irq number和HW interrupt ID之间的映射关系。
  • gzz

    假设我有一个二级的中断是SPI32要注册,挂在一级的20上。那也就是我这个二级 32号中断注册的时候直接以irq=20 request就行吗?request_irq会去帮我找帮handle放到到对应的irq_desc吗。 如果有多级了,这个irq_desc内核又是如何维护的呢?
    • linuxer

      @gzz:假设我有一个二级的中断是SPI32要注册,挂在一级的20上。那也就是我这个二级 32号中断注册的时候直接以irq=20 request就行吗? --------------------------------------- 当然不行。 唉,看来我的文档还是写的不行啊,我通篇都是在将irq number和HW interrupt ID的东西,我觉得自己讲的很清楚了,不过读者似乎不是这样的,郁闷。 1、对于老的方式而言,IRQ都是固定的,在driver初始化的时候可以通过传统的方法xxx_get_resource的方法获得irq number,这种方式都已经固定了所有的irq desc,因此irq desc也是一个静态表格而已。一旦获取了irq number,就可以调用request_irq来申请中断了 2、对于使用device tree的情况,一般可以通过of_irq_get的方式(Note:一般不会直接调用该函数,你的driver模型会封装的,例如如果你的设备是挂接到platform bus上的时候,可以使用platform_get_irq来获取irq number),一旦获取了irq number,就可以调用request_irq来申请中断了 描述硬件中断编号需要两个因素:irq domain和HW interrupt ID。根据你描述的例子,实际上硬件中断编号有两个,分别是: 1、一级irq domain的HW interrupt ID 52,也就是SPI20 2、二级irq domain的HW interrupt ID 64,也就是SPI32 二级irq domain的SPI32在注册的时候当然不能直接注册,因为这里描述都是硬件中断编号,不是irq number,必须申请irq number的资源并建立映射(当然,也可以在中断控制器初始化的时候一次性创建所有的映射,不过那也不是推荐的方式)。 如果有多级了,这个irq_desc内核又是如何维护的呢? ------------------------------------------------ irq_desc或者说irq number是和复杂的硬件拓扑没有关系的,关于中断描述符有一份专门的文档描述,可以参考
      • gzz

        @linuxer:thanks。你可能没有理解我的意思。就是说像ARM,他一般gic_init_base的时候已经做好一个controller的domain的irq和hw的映射。就比如现在SPI32要注册了,arm简单点一般irq num就是硬件中断号64了。那我的疑问是一级假如也有一个SPI32号硬件中断(对应于irq 64)被注册了呢?那内核是怎么request的时候是如何去区分相同的一级和二级的irq.还是说这样就直接报错,不运行呢?还是说这种级联情况下,应该不会存在这个问题?所以我就想是不是irq_desc会给每个domain来分组啥的。 谢谢,菜鸟不是很懂。
        • aly

          @gzz:文章说的比较明白了,我理解你说的64号中断是硬件中断号。硬件中断号在级联情况下的确是会重复的,但驱动里用的是软件中断号,linux在启动时帮你映射好的。软件中断号不会重复,你可以在驱动代码里通过irq_of_parse_and_map或者platform_get_irq获取软件中断号
  • callme_friend

    关于本文章最后的“中断处理过程”: 我依照源码(linux-3.17),是如下的执行路径: vector_irq: __irq_svc: irq_handler ldr r1, =handle_arch_irq //handle_arch_irq已初始化为gic_handle_irq ldr pc, [r1] || || \/ gic_handle_irq() handle_IRQ(irqnr, regs) generic_handle_irq(irq); | struct irq_desc *desc = irq_to_desc(irq); | generic_handle_irq_desc(irq, desc); | desc->handle_irq(irq, desc); (1)咋一看,没有发现处理GIC级联的情况呀? (2)对于级联,我猜是这样的:先进行root gic上的处理;然后发现是个级联点,则将domain转到次级gic,然后调用函数处理。不知道是否正确? (3)如果(2)中的猜测正确,那次级gic初始化时必定要向root GIC的某line申请一个响应函数,根据代码看应该是gic_handle_cascade_irq()函数,在该函数里我没发现转换domaim的代码。请问是哪里的问题? 谢谢linuxer。
    • linuxer

      @callme_friend:linux-3.17中gic_handle_irq的代码如下: ------------------------------------ static 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; if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr); handle_IRQ(irqnr, regs); continue; } if (irqnr < 16) { writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); #ifdef CONFIG_SMP handle_IPI(irqnr, regs); #endif continue; } break; } while (1); } ------------------------------------- irq_find_mapping是将hw interrupt ID转变成IRQ Number(当然是在root gic对应的irq domain上了),之后调用handle_IRQ处理这个IRQ。如果是级联的情况,那么root gic解析的这个IRQ对应的handler就是gic_handle_cascade_irq(你的猜测是对的),代码如下: ------------------------------------ static void gic_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) { struct gic_chip_data *chip_data = irq_get_handler_data(irq); struct irq_chip *chip = irq_get_chip(irq); unsigned int cascade_irq, gic_irq; unsigned long status; chained_irq_enter(chip, desc); raw_spin_lock(&irq_controller_lock); status = readl_relaxed(gic_data_cpu_base(chip_data) + GIC_CPU_INTACK); raw_spin_unlock(&irq_controller_lock); gic_irq = (status & 0x3ff); if (gic_irq == 1023) goto out; cascade_irq = irq_find_mapping(chip_data->domain, gic_irq); if (unlikely(gic_irq < 32 || gic_irq > 1020)) handle_bad_irq(cascade_irq, desc); else generic_handle_irq(cascade_irq); out: chained_irq_exit(chip, desc); } --------------------------------------- 同样的,irq_find_mapping将hw interrupt ID转变成IRQ Number(当然是在second gic对应的irq domain上了,传递给irq_find_mapping的domain参数是和上次不一样的)
  • callme_friend

    关于级联的中断处理,我认为博文漏掉了一个domain如何衔接的片段。 void gic_handle_irq(struct pt_regs *regs) { struct gic_chip_data *gic = &gic_data[0]; void __iomem *cpu_base = gic_data_cpu_base(gic); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & GICC_IAR_INT_ID_MASK; irqnr = irq_find_mapping(gic->domain, irqnr); handle_IRQ(irqnr, regs); generic_handle_irq(irq) desc->handle_irq(irq, desc); //对于级联,应是gic_handle_cascade_irq() } void gic_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) { //gic_handle_cascade_irq()传递进来的参数是root GIC的级联点的virq号 struct gic_chip_data *chip_data = irq_get_handler_data(irq);-------------(1) struct irq_chip *chip = irq_get_chip(irq); unsigned int cascade_irq, gic_irq; unsigned long status; status = readl_relaxed(gic_data_cpu_base(chip_data) + GIC_CPU_INTACK); //此处居然是次级 GIC的读取操作了 gic_irq = (status & 0x3ff); cascade_irq = irq_find_mapping(chip_data->domain, gic_irq); generic_handle_irq(cascade_irq); desc->handle_irq(irq, desc); } 对于gic_handle_cascade_irq()中的处理,没显式看到domain的转换代码。之前一直卡在这里。 对于(1)代码,才是转换domain的关键;因为在次级GIC初始化的时候,有如下代码: if (parent) { gic_cascade_irq(gic_cnt, irq); if (irq_set_handler_data(irq, &gic_data[gic_nr]) != 0)--------(2) BUG(); irq_set_chained_handler(irq, gic_handle_cascade_irq); } 代码(2)中,是将次级GIC_DATA保存在上级GIC的级联点对应的irq_desc中。以此来保证代码(1)中domain转换。
    • linuxer

      @callme_friend:是的,多谢补充!有什么心得都可以在这里分享^_^
  • hb775

    楼主您好,非常高兴您能分享。我最近也在看Linux 中断子系统的一些基本实现原理(工作需要移植Linux系统到自己公司的SOC上),对整个系统有大概的了解,但是并未深入,看了您的文章很有收获,谢谢!
  • 菜鸟

    hi linuxer, 如果系统中有对个domain , 他们的关系是怎么的呢? 如gic 的一个domain, 和 gpio对应生成的中断 domain。 是彼此独立还是 gic 为root ? 其他为级联? 在中断发生时我只能追踪到gic domain handler,而无法看到调用其他domain的 handler 。
    • 郭健

      @菜鸟:irq domain的结构类似linux系统中文件系统的结构,是树状结构,其根节点连接到了CPU,而叶节点连接到了设备。当设备发生了中断事件后,会通过各级的中断控制器反馈给CPU,而kernel的中断子系统会处理这个中断事件,通过各级的irq domain将HW interrupt ID翻译成virtual interrupt ID,也就是IRQ number。 看你的场景,涉及两个domain,一个是GIC irq domain,应该是root,另外一个是GPIO controler的irq domain。对于GIC irq domain而言,他完成的任务就是根据当前的硬件情况将GPIO controller(也是一个interrupt controller)对应的HW interrupt ID翻译成virtual interrupt ID从而调用其handler。 在GPIO controller驱动初始化的时候,会建立其irq domain并且通过irq_set_chained_handler设定该设备的中断处理函数。而GPIO domain的处理都是在这个GPIO controller(或者说GPIO type的中断控制器)中的中断处理函数完成的。 换句话说:中间节点的中断控制器(IRQ DOMAIN)都是有两个角色,一个是做为连接到上一级的中断控制器的普通设备(因此有IRQ number,有其对应的handler),另外一个是interrupt controller,对应irq domain,负责翻译其负责的HW interrupt ID。
      • 菜鸟

        @郭健:我能这样理解吗, 暂且不知道gpio 的中断 domain 和 GIC 的domain在物理上是不是连级,但是在逻辑上可以看成连级? 对gpio domain中的handler处理也是从这里irq_domain_associate调用的吗?
        • 郭健

          @菜鸟:GPIO中断控制器物理上是连接到GIC中断控制器的某一个interrupt request line上,因此,物理上也是级联的。除非你的硬件拓扑结构是所有的GPIO中断都是分别连接到若干GIC中断控制器的若干的interrupt request line上,我估计一般的硬件设计不会这样的。 irq_domain_associate和handler处理无关,这个函数只是建立一个HW interrupt ID和virtual interrupt ID之间的映射,在一个中断触发之前,其mapping就建立了。
  • z

    @linuxer:你好, 有个问题没想明白,对于domain级联以及使用radix tree的情况,比如中断1,2,3连在root domain A,中断4,5,6连在domain B,那么domain A和B它所对应的revmap_tree是同一个还是各自有自己的revmap_tree呢? 因为我看gic_handle_irq中通过hwid去找irq的时候直接去root domain中找domain->revmap_tree; 如果是这样的话他们的domain->revmap_tree是在哪里赋值的呢? 谢谢
    • 郭健

      @z:那么domain A和B它所对应的revmap_tree是同一个还是各自有自己的revmap_tree呢? ----------------------------------- 各自有各自的 因为我看gic_handle_irq中通过hwid去找irq的时候直接去root domain中找domain->revmap_tree; ------------------------------------- gic_handle_irq函数是什么?是真个系统的中断处理函数,因此,当中断发生后调用gic_handle_irq函数的时候,当然要从root irq domain开始了,你说是吧。 secondary gic和root gic有级联关系,那么实际上secondary gic有双重身份,,一个是做为连接到root gic上的普通设备(因此有IRQ number,有其对应的handler),另外一个是interrupt controller,对应irq domain,负责翻译其负责的HW interrupt ID。 我们假设场景如下:设备D连接到secondary gic的125号硬件中断上,而secondary gic连接到root gic的75号硬件中断上。当设备D产生中断的时候,CPU首先看到的是root gic上的75号中断(在gic_handle_irq函数中处理),然后才进入secondary gic的irq doamin(在gic_handle_cascade_irq函数中处理)。
  • electrlife

    请教下啊,在我的arch/arm目录中,为什么找不到vector_irq的相关定义,这个函数定义在哪里?
    • electrlife

      @electrlife:哈哈, 找到了,原来是宏vector_stub定义的
  • cracker

    Hi,linuxer: 目前遇到如下WARNING和error,但不知道如何去分析根本,可以指点一下吗? log如下: [ 2.213782] Driver 'mmcblk' needs updating - please use bus_type methods [ 2.213879] sdhci: Secure Digital Host Controller Interface driver [ 2.213894] sdhci: Copyright(c) Pierre Ossman [ 2.213997] sdhci-pci 0000:00:01.0: SDHCI controller found [8086:1190] (rev 1) [ 2.214050] ------------[ cut here ]------------ [ 2.214085] WARNING: CPU: 1 PID: 1 at /home/andrei/work/resin/resin-edison/build/tmp/work-shared/edison/kernel-source/kernel/irq/irqdomain.c:284 irq_domain_associate+0x1a3/0x200() [ 2.214100] error: virq0 is already associated [ 2.214111] Modules linked in: [ 2.214142] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 3.19.5-yocto-standard #1 [ 2.214156] Hardware name: Intel Corporation Merrifield/BODEGA BAY, BIOS 466 2014.06.23:19.20.05 [ 2.214169] 00000000 00000000 f6e0fc00 c19f08d7 f6e0fc40 f6e0fc30 c1253157 c1c24014 [ 2.214219] f6e0fc5c 00000001 c1c23f40 0000011c c12a4563 c12a4563 f6c05e00 f675e480 [ 2.214267] 00000000 f6e0fc48 c12531c3 00000009 f6e0fc40 c1c24014 f6e0fc5c f6e0fc7c [ 2.214314] Call Trace: [ 2.214349] [<c19f08d7>] dump_stack+0x4b/0x75 [ 2.214375] [<c1253157>] warn_slowpath_common+0x87/0xc0 [ 2.214402] [<c12a4563>] ? irq_domain_associate+0x1a3/0x200 [ 2.214426] [<c12a4563>] ? irq_domain_associate+0x1a3/0x200 [ 2.214449] [<c12531c3>] warn_slowpath_fmt+0x33/0x40 [ 2.214475] [<c12a4563>] irq_domain_associate+0x1a3/0x200 [ 2.214502] [<c12a45ff>] irq_domain_associate_many+0x3f/0xa0 [ 2.214528] [<c19f559d>] ? mutex_unlock+0xd/0x10 [ 2.214551] [<c19ecb88>] ? __irq_alloc_descs+0xe8/0x1b0 [ 2.214578] [<c12a46a2>] irq_create_strict_mappings+0x42/0x50 [ 2.214605] [<c12338fa>] mp_map_pin_to_irq+0x1ca/0x200 [ 2.214631] [<c1233f40>] mp_map_gsi_to_irq+0x90/0xc0 [ 2.214658] [<c184fb59>] intel_mid_pci_irq_enable+0x59/0x80 [ 2.214683] [<c1850390>] pcibios_enable_device+0x30/0x40 [ 2.214708] [<c1618f6f>] do_pci_enable_device+0x4f/0xd0 [ 2.214732] [<c1619e4d>] pci_enable_device_flags+0x9d/0xe0 [ 2.214757] [<c1619ee2>] pci_enable_device+0x12/0x20 [ 2.214782] [<c17dc4b2>] sdhci_pci_probe+0xd2/0x950 [ 2.214810] [<c161bb5f>] pci_device_probe+0x6f/0xd0 [ 2.214834] [<c13ba2a5>] ? sysfs_create_link+0x25/0x50 [ 2.214860] [<c16ada0f>] driver_probe_device+0x7f/0x3c0 [ 2.214885] [<c161ba92>] ? pci_match_device+0xd2/0x100 [ 2.214910] [<c16ade09>] __driver_attach+0x79/0x80 [ 2.214933] [<c16add90>] ? __device_attach+0x40/0x40 [ 2.214955] [<c16abdaf>] bus_for_each_dev+0x4f/0x80 [ 2.214979] [<c16ad51e>] driver_attach+0x1e/0x20 [ 2.215001] [<c16add90>] ? __device_attach+0x40/0x40 [ 2.215024] [<c16ad157>] bus_add_driver+0x157/0x240 [ 2.215049] [<c1e01c8f>] ? sdhci_drv_init+0x1b/0x1b [ 2.215072] [<c1e01c8f>] ? sdhci_drv_init+0x1b/0x1b [ 2.215095] [<c16ae56d>] driver_register+0x5d/0xf0 [ 2.215119] [<c1351cce>] ? kfree+0x9e/0x1d0 [ 2.215144] [<c15eb7d0>] ? kvasprintf+0x40/0x50 [ 2.215168] [<c161ab9a>] __pci_register_driver+0x4a/0x50 [ 2.215192] [<c1e01ca3>] sdhci_driver_init+0x14/0x16 [ 2.215216] [<c120044a>] do_one_initcall+0xaa/0x1e0 [ 2.215239] [<c1e01c8f>] ? sdhci_drv_init+0x1b/0x1b [ 2.215263] [<c1dcc4e3>] ? repair_env_string+0x12/0x54 [ 2.215288] [<c126d0a9>] ? parse_args+0x299/0x510 [ 2.215317] [<c1dccbeb>] kernel_init_freeable+0x147/0x1ef [ 2.215345] [<c1273a63>] ? preempt_count_sub+0xb3/0x110 [ 2.215368] [<c127262d>] ? finish_task_switch+0x5d/0xd0 [ 2.215392] [<c19eb630>] kernel_init+0x10/0xe0 [ 2.215415] [<c19f7441>] ret_from_kernel_thread+0x21/0x30 [ 2.215436] [<c19eb620>] ? rest_init+0x80/0x80 [ 2.215476] ---[ end trace 96410e869c63de19 ]--- [ 2.215566] flis_addr mapped addr: f8498900 [ 2.215694] sdhci-pci 0000:00:01.0: rte_addr mapped addr: f849c000 [ 2.317076] mmc0: BKOPS_EN bit is not set [ 2.324368] mmc0: new HS200 MMC card at address 0001 [ 2.325192] mmcblk0: mmc0:0001 H4G1d� 3.64 GiB [ 2.332236] mmcblk0: p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 [ 2.337906] mmc0: SDHCI controller on PCI [0000:00:01.0] using ADMA
    • linuxer

      @cracker:错误信息已经给出来了,就是virq等于0的那个中断描述符已经和某个irq domain的hw interrupt id绑定了,不能再次绑定。
      • cracker

        @linuxer:Hi,linuxer, 你说的很对。但我还是有疑问。 virq = irq_find_mapping(domain, hwirq); if (virq) { return virq; } unsigned int irq_find_mapping(struct irq_domain *domain, 552 irq_hw_number_t hwirq) { 554 struct irq_data *data; 555 。。。。。。。。。。。 561 562 if (hwirq < domain->revmap_direct_max_irq) { 563 data = irq_domain_get_irq_data(domain, hwirq); 564 if (data && data->hwirq == hwirq) 565 return hwirq; 566 } 。。。。。。。。。。。。。。。 573 data = radix_tree_lookup(&domain->revmap_tree, hwirq); 574 rcu_read_unlock(); 575 return data ? data->irq : 0; 这里如果virq为0说明data为NULL还是data->irq = 0?如果data->irq = 0是不是配置的mapping映射是不对的?
        • linuxer

          @cracker:看你上面栈的回溯,错误应该是发生在驱动初始化的过程中(irq_domain_associate),和irq_find_mapping无关。
  • hxj

    您好,我想问一下,在ARM架构下,我已经知道了HW interrupt ID 为26,在驱动程序中通过哪个函数得到irq number呢? 还是说在3.14内核中不推荐用resource的方式来定义硬件中断号?
    • linuxer

      @hxj:1、在DTS中描述该设备的HW Interrupt相关信息 2、如果是platform device,那么直接调用platform_get_irq_byname或者platform_get_irq即可获得IRQ number 3、如果不是platform device,那么需要调用更底层一些的接口函数。
  • kernel_123

    假设系统中就只有一个gic-400 gic:interrupt-controller@1000000 { compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; #interrupt-cells = <3>; #address-cells = <0>; interrupt-controller; reg = <0x0 0x10201000 0x1000>, <0x0 0x10202000 0x1000>, <0x0 0x10204000 0x2000>, <0x0 0x10206000 0x2000>; interrupts = <1 9 0xf04>; }; 比如上述的gic是这样的配置的,然后在pin-ctrl中引用gic作为parent pinctrl@10000000 { gpe0: gpe0 { gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; interrupt-parent = <&gic>; interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>, <0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>; }; 那这样的话,系统中的中断树就是如下样子:           gic | 32 33 ... 67 68 69 ... | xxx_irq_chip |   连接设备 问题1: 上面描述的也是一种级联情况,gic初始化的时候只会调用一次gic_of_init函数,分配irq_domain, 建立映射。   那gic_of_init中的这种情况会不会调用到?    if (parent) {  irq = irq_of_parse_and_map(node, 0);  gic_cascade_irq(gic_cnt, irq);    } 问题2: 在pin_ctrl驱动中也看到了,先分配每个bank的irq domain, 建立映射。然后接着去初始化pin ctrl所对应的gpio, 去建立hwirq到vir irq之间的映射。    但是每个bank设置了__irq_do_set_handler, 同时pin ctrl也设置了对应的__irq_do_set_handler,同时gic controller也有对应的irq_handler. 那这样一来,当接在pin_ctrl上的外设产生中断后,中断的处理流程是怎么样的? 是从gic的irq_handler -> pin_ctrl -> bank -> 对应驱动的中断处理函数?   
  • linuxer_fy 主页

    " (1)首先找到root interrupt controller对应的irq domain。 (2)根据HW 寄存器信息和irq domain信息获取HW interrupt ID " -------------- (2)中的表述,有点绕人。 HW interrupt ID不是在CPU收到中断信号时,就获取到了吗? (2)中CPU根据irq domain获取HW interrupt ID如何理解?
    • linuxer_fy 主页

      @linuxer_fy:自己回答: 发生中断,首先从root interrupt controller的寄存器得到HW interrupt ID 经过irq_find_mapping得到IRQ number,再得到IRQ handler 进行处理 如果该IRQ handler是对应一个串级的interrupt controller,则继续上述1-4 每次都从当前interrupt controller的寄存器得到HW interrupt ID 如有不对,请指正。
  • WL__Linux

    博主的技术已经达到了敬仰的水平了: 我问两个简单的问题呢 (1)对于级联的中断控制器,您在上面的问题回答中说道 “同样的,irq_find_mapping将hw interrupt ID转变成IRQ Number(当然是在second gic对应的irq domain上了,传递给irq_find_mapping的domain参数是和上次不一样的)” 那么第二次的IRQ Number会和第一次的IRQ Num重合吗,还是在上一级的末尾进行继续申请。 (2)最近做开发遇到这样的问题,我通过gpio_to_irq()这个函数得到gpio0-gpio5-31的中断号,gpio1-0与gpio1-0的差值为33,多个那个中断号使谁的呢 (3)获取到gpio5_31的中断号为232 而 芯片手册中的网卡ID分别为(73,74)通过函数读到的中断号为238,写的中断号为239,那么他之前的中断号不是连续的吗
    • melo

      @WL__Linux:这个我可以做个尝试解释下。 驱动里最常见gpio_to_irq函数,追下去也是会跑到irq_create_mapping函数处,而内部这里wowo也讲了,会用irq_find_mapping来判断是否已经映射。 那么这里为什么会这么处理呢,什么情况会提前在你驱动申请前就映射,什么情况是在你驱动申请时才需要映射? 这个根据我的理解,是因为设备树使用习惯导致的。 内核起来的时候,start_kernel->init_IRQ->irqchip_init->of_irq_init of_irq_init会扫描设备树,并根据里面配置的级联中断信息按顺序做好映射的工作。(这个wowo也提到啦)。 但是我们还有的驱动,在使用gpio中断的时候,没有使用interrupts属性,而是在设备里简单的自定了一个中断gpio号,然后在驱动里取出gpio号,再手动的申请。 这两者的区别,会导致再分配的虚拟中断号中间可能被填充了包括pmic等其他申请的虚拟中断号。所以这里就是使用了interrupts属性,就会提前分配。如果没有使用,那么就只能根据驱动加载顺序依次动态申请了
  • test

    问个问题,级联的irq如何设置affinity? 直接在父节irq号上使用affinity吗?
  • sleep

    我是初学者,首先感觉大佬很强,文章写的也很细,但少了点框架性的概述,感觉文章结构,内容组织和语句上可以改进下。文章开始我觉得可以先描述下interrupt-controller,irq-domain,irq-desc等几个对象的组织方式和对应关系或所属关系,稍稍具象化一点,不然一直说添加domain,添加映射其实是比较难以理解这个概念的,脑子里没有这几个对象的数据结构组织关系。 还有比如说: “在second GIC初始化之后,该irq domain的HW interrupt ID和IRQ number的映射关系已经建立” 紧接着: “3、在各个硬件外设的驱动初始化过程中,创建HW interrupt ID和IRQ number的映射关系” 这个应该意思是HW interrupt ID和IRQ number在不同的阶段都可以进行映射(gic初始化或者device初始化)对吧。这里没有相关说明,单纯2各生硬句子让人有点混乱。 大佬可能是对从中断子系统和设备子系统初始化过程都很熟悉,所以有些高估我这种菜鸟的知识储备程度吧。当然都是菜鸡发言哈,还是感谢和敬仰大佬实力和分享精神的。