往期内容
本专栏往期内容,interrtupr子系统:
- 深入解析Linux内核中断管理:从IRQ描述符到irq domain的设计与实现
pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序
1.数据结构描述
1.1 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。
1.2 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);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/* extended V2 interfaces to support hierarchy irq_domains */
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg);
void (*free)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs);
void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
#endif
};
}
xlate:大概就是将设备树中节点的hwirq and type解析出来。就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。
match:判断一个指定的interrupt controller(node参数)是否和一个irq domain匹配(d参数),如果匹配的话,返回。内核中很少定义这个callback函数,实际上struct irq_domain中有一个of_node指向了对应的interrupt controller的device node,因此,如果不提供该函数,那么default的匹配函数其实就是判断irq domain的of_node成员是否等于传入的node参数。
map:调用map函数的时机是在创建(或者更新)HW interrupt ID(hw参数)和IRQ number(virq参数)关系的时候。貌似还有的根据hirq找到对应的virq,应该是在irq_set_chip_and_handler调用时提供的handle函数中
从发生一个中断到调用该中断的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
就是根据传入的virq,设置对应的irq_desc中的irq_chip、handle函数、irq_data:irq_set_chip_data()、irq_set_chip_and_handler()
alloc:如果是gic_irq_domain_alloc
函数的话
不仅负责分配虚拟 IRQ 号并建立硬件中断号(hwirq)和虚拟 IRQ 号(virq)之间的映射,还会为分配的 IRQ 号设置相应的 irq_desc
和 irq_data
结构体。这包括配置中断处理程序 (handle_irq
)、中断类型、触发模式等与中断相关的详细信息。
当调用 gic_irq_domain_alloc
时,它通常会执行以下任务:
-
分配 IRQ 描述符 (
**irq_desc**
):- 为特定的虚拟 IRQ 号分配一个
irq_desc
结构体。这是中断子系统用来管理特定中断的信息块,包含状态、处理程序等。
- 为特定的虚拟 IRQ 号分配一个
-
设置中断处理程序 (
**handle_irq**
):irq_desc
结构体中的handle_irq
字段是一个函数指针,用于指定当中断发生时应该调用哪个处理函数。在 GIC 的情况下,这可能是一个标准的 GIC 处理程序,例如handle_fasteoi_irq
。
-
配置
**irq_data**
结构:-
irq_data
是irq_desc
的一部分,包含与中断相关的硬件信息。gic_irq_domain_alloc
会填充这个结构,包括:hwirq
(硬件中断号)。- 中断控制器的地址、数据和特定于硬件的配置。
-
-
设置中断触发类型和属性:
- 可能会设置中断的触发模式(边沿触发或电平触发)以及其他属性,如中断优先级和屏蔽状态。
-
建立映射关系:
- 将
irq_desc
和irq_data
与特定的hwirq
和virq
关联起来,这样当中断发生时,内核可以正确地调用相应的处理程序。
- 将
translate: 用于将硬件中断号(hwirq)从设备树或其他硬件描述符中翻译成域内部的表示。
2.irq_domain的用法
中断控制器驱动程序通过以下方式创建并注册一个irq_domain。
irq_domain_add_*()
irq_domain_create_*()
每个映射方法都有不 同的分配器函数(比如irq_domain_add_linear内部调用的是irq_domain_add_)
函数成功后会返回一个指向irq_domain的指针。 调用者必须向分配器函数提供一个irq_domain_ops结构体。
在大多数情况下,irq_domain在开始时是空的,没有任何hwirq和IRQ号之间的映射。 通过调用irq_create_mapping()将映射添加到irq_domain中,该函数接受 irq_domain和一个hwirq号作为参数。 如果hwirq的映射还不存在,那么它将分配 一个新的Linux irq_desc,将其与hwirq关联起来,并调用.map()回调,这样驱动 程序就可以执行任何必要的硬件设置。
一旦建立了映射,可以通过多种方法检索或使用它:
- irq_resolve_mapping()返回一个指向给定域和hwirq号的irq_desc结构指针, 如果没有映射则返回NULL。
- irq_find_mapping()返回给定域和hwirq的Linux IRQ号,如果没有映射则返回0。
- irq_linear_revmap()现与irq_find_mapping()相同,已被废弃。
- generic_handle_domain_irq()处理一个由域和hwirq号描述的中断。
请注意,irq域的查找必须发生在与RCU读临界区兼容的上下文中。
在调用irq_find_mapping()之前,至少要调用一次irq_create_mapping()函数, 以免描述符不能被分配。
如果驱动程序有Linux的IRQ号或irq_data指针,并且需要知道相关的hwirq号(比 如在irq_chip回调中),那么可以直接从irq_data->hwirq中获得。
3.irq_domain映射
系统中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初始化的过程中,创建映射
当一个hwirq被映射 时,会给hwirq分配一个irq_desc,并将irq号存储在表中:hwirq和irq之间的映射
3.1 注册映射
3.1.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,---callback函数
void *host_data)-----driver私有数据
{
return __irq_domain_add(of_node, size, size, 0, ops, host_data);
}
------------------------------------------------------------------
irq_domain_create_linear()
线性反向映射维护了一个固定大小的表,该表以hwirq号为索引。 当一个hwirq被映射 时,会给hwirq分配一个irq_desc,并将irq号存储在表中。
当最大的hwirq号固定且数量相对较少时,线性图是一个很好的选择(~<256)。 这种 映射的优点是固定时间查找IRQ号,而且irq_descs只分配给在用的IRQ。 缺点是该表 必须尽可能大的hwirq号。
irq_domain_add_linear()在功能上是等价的, 除了第一个参数不同–前者接受一个Open Firmware特定的 ‘struct device_node’ 而 后者接受一个更通用的抽象 ‘struct fwnode_handle’ 。
大多数驱动应该使用线性映射
3.1.2 树状映射
建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。实际上,内核中使用Radix Tree map的只有powerPC和MIPS的硬件平台。
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);
}
------------------------------------------------------------------
irq_domain_create_tree()
irq_domain维护着从hwirq号到Linux IRQ的radix的树状映射。 当一个hwirq被映射时, 一个irq_desc被分配,hwirq被用作radix树的查找键。
如果hwirq号可以非常大,树状映射是一个很好的选择,因为它不需要分配一个和最大hwirq 号一样大的表。 缺点是,hwirq到IRQ号的查找取决于表中有多少条目。
irq_domain_add_tree()和irq_domain_create_tree()在功能上是等价的,除了第一 个参数不同——前者接受一个Open Firmware特定的 ‘struct device_node’ ,而后者接受 一个更通用的抽象 ‘struct fwnode_handle’ 。
很少有驱动应该需要这个映射。
3.1.3 无映射
些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。例如PowerPC 系统使用的MPIC (Multi-Processor Interrupt Controller)。在这种情况下,不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了。
irq_domain_add_nomap()
当硬件中的hwirq号是可编程的时候,就可以采用无映射类型。 在这种情况下,最好将 Linux IRQ号编入硬件本身,这样就不需要映射了。 调用irq_create_direct_mapping() 会分配一个Linux IRQ号,并调用.map()回调,这样驱动就可以将Linux IRQ号编入硬件中。
大多数驱动程序无法使用此映射,现在它由CONFIG_IRQ_DOMAIN_NOMAP选项控制。 这类接口的逻辑很简单,根据自己的映射类型,初始化struct irq_domain中的各个成员,调用__irq_domain_add将该irq domain挂入irq_domain_list的全局列表。
3.1.4 传统映射类型
irq_domain_add_simple()
irq_domain_add_legacy()
irq_domain_create_simple()
irq_domain_create_legacy()
传统映射是已经为 hwirqs 分配了一系列 irq_descs 的驱动程序的特殊情况。 当驱动程 序不能立即转换为使用线性映射时,就会使用它。 例如,许多嵌入式系统板卡支持文件使用 一组用于IRQ号的定义(#define),这些定义被传递给struct设备注册。 在这种情况下, 不能动态分配Linux IRQ号,应该使用传统映射。
顾名思义,_legacy()系列函数已被废弃,只是为了方便对古老平台的支持而存在。 不应该增加新的用户。当_simple()系列函数的使用导致遗留行为时,他们也是如此。
传统映射假设已经为控制器分配了一个连续的IRQ号范围,并且可以通过向hwirq号添加一 个固定的偏移来计算IRQ号,反之亦然。 缺点是需要中断控制器管理IRQ分配,并且需要为每 个hwirq分配一个irq_desc,即使它没有被使用。
只有在必须支持固定的IRQ映射时,才应使用传统映射。 例如,ISA控制器将使用传统映射来 映射Linux IRQ 0-15,这样现有的ISA驱动程序就能得到正确的IRQ号。
大多数使用传统映射的用户应该使用irq_domain_add_simple()或 irq_domain_create_simple(),只有在系统提供IRQ范围时才会使用传统域,否则将使用 线性域映射。这个调用的语义是这样的:如果指定了一个IRQ范围,那么 描述符将被即时分配 给它,如果没有范围被分配,它将不会执行 irq_domain_add_linear() 或 irq_domain_create_linear(),这意味着 no irq 描述符将被分配。
一个简单域的典型用例是,irqchip供应商同时支持动态和静态IRQ分配。
为了避免最终出现使用线性域而没有描述符被分配的情况,确保使用简单域的驱动程序在任何 irq_find_mapping()之前调用irq_create_mapping()是非常重要的,因为后者实际上 将用于静态IRQ分配情况。
irq_domain_add_simple()和irq_domain_create_simple()以及 irq_domain_add_legacy()和irq_domain_create_legacy()在功能上是等价的,只 是第一个参数不同–前者接受Open Firmware特定的 ‘struct device_node’ ,而后者 接受一个更通用的抽象 ‘struct fwnode_handle’ 。
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
……
3.1.5 IRQ域层级结构
在某些架构上,可能有多个中断控制器参与将一个中断从设备传送到目标CPU。 x86平台上典型的中断传递路径:
Device --> IOAPIC -> Interrupt remapping Controller -> Local APIC -> CPU
涉及到的中断控制器有三个:
- IOAPIC 控制器
- 中断重映射控制器
- Local APIC 控制器
为了支持这样的硬件拓扑结构,使软件架构与硬件架构相匹配,为每个中断控制器建立一 个irq_domain数据结构,并将这些irq_domain组织成层次结构。
在建立irq_domain层次结构时,靠近设备的irq_domain为子域,靠近CPU的 irq_domain为父域。所以在上面的例子中,将建立如下的层次结构。
CPU Vector irq_domain (root irq_domain to manage CPU vectors)
^
|
Interrupt Remapping irq_domain (manage irq_remapping entries)
^
|
IOAPIC irq_domain (manage IOAPIC delivery entries/pins)
使用irq_domain层次结构的主要接口有四个:
- irq_domain_alloc_irqs(): 分配IRQ描述符和与中断控制器相关的资源来传递这些中断。
- irq_domain_free_irqs(): 释放IRQ描述符和与这些中断相关的中断控制器资源。
- irq_domain_activate_irq(): 激活中断控制器硬件以传递中断。
- irq_domain_deactivate_irq(): 停用中断控制器硬件,停止传递中断。
为了支持irq_domain层次结构,需要做如下修改:
- 一个新的字段 ‘parent’ 被添加到irq_domain结构中;它用于维护irq_domain的层次信息。
- 一个新的字段 ‘parent_data’ 被添加到irq_data结构中;它用于建立层次结构irq_data以 匹配irq_domain层次结构。irq_data用于存储irq_domain指针和硬件irq号。
- 新的回调被添加到irq_domain_ops结构中,以支持层次结构的irq_domain操作。
在支持分层irq_domain和分层irq_data准备就绪后,为每个中断控制器建立一个irq_domain结 构,并为每个与IRQ相关联的irq_domain分配一个irq_data结构。现在我们可以再进一步支持堆 栈式(层次结构)的irq_chip。也就是说,一个irq_chip与层次结构中的每个irq_data相关联。 一个子irq_chip可以自己或通过与它的父irq_chip合作来实现一个所需的操作。
通过堆栈式的irq_chip,中断控制器驱动只需要处理自己管理的硬件,在需要的时候可以向其父 irq_chip请求服务。所以我们可以实现更简洁的软件架构。
为了让中断控制器驱动程序支持irq_domain层次结构,它需要做到以下几点:
- 实现 irq_domain_ops.alloc 和 irq_domain_ops.free
- 可选择地实现 irq_domain_ops.activate 和 irq_domain_ops.deactivate.
- 可选择地实现一个irq_chip来管理中断控制器硬件。
- 不需要实现irq_domain_ops.map和irq_domain_ops.unmap,它们在层次结构 irq_domain中是不用的。
irq_domain层次结构绝不是x86特有的,大量用于支持其他架构,如ARM、ARM64等。
3.2 创建映射
上面是注册了一个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使用的
3.3 中断号映射库
目前Linux内核的设计使用了一个巨大的数字空间,每个独立的IRQ源都被分配了一个不 同的数字。 当只有一个中断控制器时,这很简单,但在有多个中断控制器的系统中,内核必须确保每 个中断控制器都能得到非重复的Linux IRQ号(数字)分配。
注册为唯一的irqchips的中断控制器编号呈现出上升的趋势:例如GPIO控制器等不同 种类的子驱动程序通过将其中断处理程序建模为irqchips,即实际上是级联中断控制器, 避免了重新实现与IRQ核心系统相同的回调机制。
在这里,中断号与硬件中断号离散了所有种类的对应关系:而在过去,IRQ号可以选择, 使它们与硬件IRQ线进入根中断控制器(即实际向CPU发射中断线的组件)相匹配,现 在这个编号仅仅是一个数字。
出于这个原因,我们需要一种机制将控制器本地中断号(即硬件irq编号)与Linux IRQ 号分开。
irq_alloc_desc*() 和 irq_free_desc*() API 提供了对irq号的分配,但它们不 提供任何对控制器本地IRQ(hwirq)号到Linux IRQ号空间的反向映射的支持。
irq_domain 库在 irq_alloc_desc*() API 的基础上增加了 hwirq 和 IRQ 号码 之间的映射。 相比于中断控制器驱动开放编码自己的反向映射方案,更喜欢用 irq_domain来管理映射。
irq_domain还实现了从抽象的irq_fwspec结构体到hwirq号的转换(到目前为止是 Device Tree和ACPI GSI),并且可以很容易地扩展以支持其它IRQ拓扑数据源。
这也使得用户在调用irq_alloc_desc后,hwirq和irq之间已经有了映射,再通过调用3.1中注册irq_domain的接口(比如irq_domain_add_legacy)获得已经有映射关系的irq_domain,不需要再调用irq_create_mapping函数建立HW interrupt ID和IRQ number的映射关系。
irq_alloc_desc*() 系列函数:
这些函数用于分配一系列的 IRQ 描述符,并返回一个起始的 IRQ 号码(irq_base)。当你使用这些 API 时,可以选择是否指定 hwirq 和 IRQ 号码之间的映射。
这些函数可以直接通过参数来设定映射关系,例如在分配描述符时提供的 hwirq 和 irq_base 值。
在调用这些 API 时,内核会自动处理并维护 hwirq 和 IRQ 号码之间的映射关系,这在某种程度上相当于调用了 irq_create_mapping()。
irq_create_mapping() 函数:
该函数是更直接的用于在现有的 irq_domain 中创建或查询硬件中断号(hwirq)和虚拟 IRQ 号之间的映射。
它将指定的 hwirq 映射到 irq_domain 中的一个对应的 IRQ 号码,并返回这个 IRQ 号码。
如果映射已经存在,它会直接返回现有的映射关系中的 IRQ 号码。
共同点和区别:
共同点:
两者都可以建立硬件中断号(hwirq)与虚拟 IRQ 号(virq)之间的映射,并在系统中维护这一关系。
区别:
irq_alloc_desc*() 不仅仅是用于映射,还涉及到分配 IRQ 描述符的工作。它为新分配的 IRQ 提供了内存空间并初始化描述符。
irq_create_mapping() 更专注于映射本身,通常在描述符已经分配好的情况下使用它来建立或查找 hwirq 和 virq 之间的映射。
3.5 其它情况
static struct irq_chip virtual_intc_irq_chip = {
.name = "100ask_virtual_intc",
.irq_ack = virtual_intc_irq_ack ,
.irq_mask = virtual_intc_irq_mask ,
.irq_mask_ack = virtual_intc_irq_mask_ack ,
.irq_unmask = virtual_intc_irq_unmask ,
.irq_eoi = virtual_intc_irq_eoi ,
};
static int virtual_intc_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
/* 1. 给virq提供处理函数
* 2. 提供irq_chip用来mask/unmask中断
*/
irq_set_chip_data(virq, h->host_data);
//irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_edge_irq); /* handle_edge_irq就是handleC */
irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC */
//irq_set_nested_thread(virq, 1);
//irq_set_noprobe(virq);
return 0;
}
static const struct irq_domain_ops virtual_intc_domain_ops = {
.xlate = irq_domain_xlate_onetwocell,
.map = virtual_intc_irq_map,
};
static int virtual_intc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int irq_to_parent;
//int irq_base;
/* 1. virutal intc 会向GIC发出n号中断 */
/* 1.1 从设备树里获得virq_n */
irq_to_parent = platform_get_irq(pdev, 0);
printk("virtual_intc_probe irq_to_parent = %d\n", irq_to_parent);
/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
irq_set_chained_handler_and_data(irq_to_parent, virtual_intc_irq_handler, NULL);
/* 2. 分配/设置/注册一个irq_domain */
//irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
//printk("virtual_intc_probe irq_base = %d\n", irq_base);
/* Usage:
* a. dts: 定义使用哪个hwirq
* b. 内核解析设备树时分配irq_desc,得到virq
* c. (hwirq, virq) ==>存入domain
*/
virtual_intc_domain = irq_domain_add_linear(np, 4,
&virtual_intc_domain_ops, NULL);
return 0;
}
在很多情况下,使用 irq_domain_add_linear
创建 IRQ 域后,确实需要调用 irq_create_mapping
或者 irq_domain_map
来建立硬件中断号(hwirq)和虚拟中断号(virq)之间的映射。
然而,在某些情况下,尤其是像你代码中描述的情形,如果设备树(DTS)已经定义了中断资源,并且系统在解析设备树的时候自动分配了中断描述符(irq_desc
),则可能不需要显式调用 irq_create_mapping
。具体原因如下:
\1. 设备树自动映射:
- 当设备树中已经定义了硬件中断号(hwirq)时,设备树解析器(OF)可能会自动为这些 hwirq 分配相应的虚拟中断号(virq),并将其存储在
irq_domain
中。 - 这种情况下,映射可能是在设备树解析过程中由框架自动完成的,因此不需要手动调用
irq_create_mapping
。 (上面代码应该就是处于这种情况,或者也有可能是irq_alloc_descs调用时已经完成了irq和hwirq之间的映射)
\2. 特定中断控制器驱动的逻辑:
- 某些中断控制器驱动在调用
irq_domain_add_linear
时,会直接处理硬件中断号(hwirq)的映射,可能在irq_domain_ops
回调函数中自动处理 hwirq 和 virq 的关联。 - 使用的
irq_domain_ops
(如virtual_intc_domain_ops
)可能已经内置了自动映射的逻辑,因此省去了显式调用irq_create_mapping
的步骤。
\3. 使用平台的现有机制:
- 如果基于某个平台或者已有的中断控制器框架,那么它们可能已经实现了自动分配和映射的机制。只要你按规定创建
irq_domain
,框架可能会处理其余的细节。
虽然大多数情况下需要调用 irq_create_mapping
来显式建立 hwirq 和 virq 的映射,但在某些特定环境下,尤其是当设备树和平台驱动框架已经处理了这些映射时,你可以不需要显式调用这个函数。