linux IRQ Management(四)- IRQ Domain
- 了解IRQ Domain(中断控制器)
1.如何理解中断号?
每个IRQ同时有"irq"和"hwirq"两个编号。
- "hwirq"是硬件中断号(物理中断号),即芯片手册上写的号码,Interrupt controller用hwirq来标识外设中断。在interrupt controller级联的情况下,仅仅用hwirq已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。
- "irq"是软件使用的中断号,或者说是逻辑/虚拟中断号。CPU需要为每一个外设中断编号,它是一个虚拟interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
如果芯片中有多个中断控制器,假设它们各自都对应16个IRQ,这16个IRQ的编号都是从0到15,那CPU收到中断后,软件单凭中断号是分不清这个中断到底是从哪个中断控制器派发过来的,它需要的是中断控制器自身的编号加上IRQ的编号。
2.IRQ Domain
早期的系统,只有一个中断控制器,接入中断控制器的物理中断号都是不同的。但是随着计算机系统的发展,系统中可以挂接更多的中断控制器。特别是嵌入式系统的出现,类似GPIO这种也可以视作一种中断控制器。每个中断控制器都有自己中断线的物理编号,且这些物理编号会有重复。此时,Linux Kernel发展出了IRQ Domain的概念,来区分这些相同的物理中断编号。
IRQ Domain,顾名思义,即中断控制域。每个中断控制器都有自己的struct irq_domain结构体,以及自己下属的物理中断号,也不用担心物理中断号重复无法区分的问题。以下图为例,IRQ Controller 1及其下属的几根中断输入线作为一个中断控制域,而IRQ Controller 2作为另外一个中断控制域。
对于驱动工程师而言,只希望得到一个IRQ number,而不关系具体是哪个interrupt controller上的哪个HW interrupt ID。因此kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制。
从kernel的角度来看,任何外部设备的中断都是一个异步事件,kernel都需要识别这个事件。内核用IRQ number来标识某一个设备的某个interrupt request。有了IRQ number就可以定位到该中断的描述符(struct irq_desc)。但是,对于中断控制器而言,它不并知道IRQ number,它只是知道HW interrupt number(中断控制器会为其支持的interrupt source进行编码,这个编码被称为Hardware interrupt number )。不同的软件模块用不同的ID来识别interrupt source,这样就需要映射了。如何将Hardware interrupt number 映射到IRQ number呢?这需要一个translation object,因此irq domain应用而生。
2.1.结构体
1).struct irq_domain(抽象为中断控制器)
struct irq_domain {
struct list_head link; //用于将irq_domain连接到全局链表irq_domain_list中
const char *name; //中断控制器名称
const struct irq_domain_ops *ops; //irq domain映射操作使用的方法集合
void *host_data; //指向一个struct gic_chip_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; //该irq domain支持中断数量的最大值。
unsigned int revmap_direct_max_irq;
unsigned int revmap_size; //线性映射的大小
struct radix_tree_root revmap_tree; //Radix Tree map使用到的radix tree root node
unsigned int linear_revmap[]; //线性映射使用的lookup table
};
在初始化中断控制器时,解析DTS信息中定义了几个中断控制器,每个中断控制器都注册一个struct irq_domain数据结构。
对GIC中断控制器而言,host_data成员指向一个struct gic_chip_data的数据结构,该结构体通过gic_init_bases(drivers/irqchip/irq-gic-v3.c)初始化。
2).struct gic_chip_data
50 struct gic_chip_data {
51 struct fwnode_handle *fwnode;
52 void __iomem *dist_base;
53 struct redist_region *redist_regions;
54 struct rdists rdists;
55 struct irq_domain *domain;
56 u64 redist_stride;
57 u32 nr_redist_regions;
58 bool has_rss;
59 unsigned int irq_nr;
60 struct partition_desc *ppi_descs[16];
61 };
在注册GIC irq domain时还有一个重要的数据结构irq_domain_ops,对于GIC,其irq domain的操作函数是gic_irq_domain_ops,定义如下:
1036 static const struct irq_domain_ops gic_irq_domain_ops = {
1037 .translate = gic_irq_domain_translate,
1038 .alloc = gic_irq_domain_alloc,
1039 .free = gic_irq_domain_free,
1040 .select = gic_irq_domain_select,
1041 };
3).struct irq_domain_ops:
struct irq_domain_ops {
//如果irq_domain不是HIERARCHY的,那么使用下面的函数
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);----使hwirq映射到virq
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);-----------------得到hwirq
//相反,如果是HIERARCHY的,那么使用下面的函数
#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); //同map的作用
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); //同unmap的作用
#endif
};
CONFIG_IRQ_DOMAIN_HIERARCHY:
We plan to use hierarchy irqdomain to suppport CPU vector assignment, interrupt remapping controller, IO-APIC controller, MSI interrupt and hypertransport interrupt etc on x86 platforms. So extend irqdomain interfaces to support hierarchy irqdomain. There are already many clients of current irqdomain interfaces. To minimize the changes, we choose to introduce new version 2 interfaces to support hierarchy instead of extending existing irqdomain interfaces.
According to Thomas’s suggestion, the most important design decision is to build hierarchy struct irq_data to support hierarchy irqdomain, so hierarchy irqdomain related data could be saved in struct irq_data.With support of hierarchy irq_data, we could also support stacked irq_chips. This is most useful in case of set_affinity().
-
非层次函数接口
-
xlate函数,在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,irq_domain_associate()调用map函数。
-
这些设定不适合由具体的硬件驱动来设定,因此在Interrupt controller,也就是irq domain的callback函数中设定。
2.2.GIC代码分析
2.2.1.gic_init_bases()
drivers/irqchip/irq-gic-v3.c:
1071 static int __init gic_init_bases(void __iomem *dist_base,
1072 struct redist_region *rdist_regs,
1073 u32 nr_redist_regions,
1074 u64 redist_stride,
1075 struct fwnode_handle *handle)
1076 {
1077 u32 typer;
1078 int gic_irqs;
1079 int err;
1080
1081 //初始化struct gic_chip_data gic_data;
1087 gic_data.fwnode = handle;
1088 gic_data.dist_base = dist_base;
1089 gic_data.redist_regions = rdist_regs;
1090 gic_data.nr_redist_regions = nr_redist_regions;
1091 gic_data.redist_stride = redist_stride;
1092
1093 /*
1094 * Find out how many interrupts are supported.
1095 * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)
1096 */
1097 typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
1098 gic_data.rdists.gicd_typer = typer;
1099 gic_irqs = GICD_TYPER_IRQS(typer);
1100 if (gic_irqs > 1020)
1101 gic_irqs = 1020;
1102 gic_data.irq_nr = gic_irqs;
1103
1104 gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,&gic_data);
1106 irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
1107 gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
1108 gic_data.rdists.has_vlpis = true;
1109 gic_data.rdists.has_direct_lpi = true;
1116 gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
1119
1120 if (typer & GICD_TYPER_MBIS) {
1121 err = mbi_init(handle, gic_data.domain);
1122 if (err)
1123 pr_err("Failed to initialize MBIs\n");
1124 }
1125
1126 set_handle_irq(gic_handle_irq);
1128 gic_update_vlpi_properties();
1129
1130 if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
1131 its_init(handle, &gic_data.rdists, gic_data.domain);
1132
1133 gic_smp_init();
1134 gic_dist_init();
1135 gic_cpu_init();
1136 gic_cpu_pm_init();
1138 return 0;
1145 }
分配一个irq domain的数据结构,并调用irq_domain_create_tree进行注册,添加到链表irq_domain_list。每个interrupt controller都会填充为一个irq domain,负责解析其下游的interrut source。如果interrupt controller有级联的情况,那么一个非root interrupt controller的中断控制器也是其parent irq domain的一个普通的interrupt source。
gic_init_bases函数重点分析:
-
确认支持SPI 中断号最大的值为多少,GICv3最多支持1020个中断(SPI+SGI+SPI).GICD_TYPER寄存器bit[4:0], 如果该字段的值为N,则最大SPI INTID为32(N + 1)-1。 例如,0x00011指定最大SPI INTID为127。
-
调用irq_domain_create_tree 向系统中注册一个irq domain的数据结构,该函数的重要两个参数为gic_irq_domain_ops,gic_data。irq_domain主要作用是将硬件中断号映射到IRQ number。
-
用于区分MSI域。 比如一个域用作PCI/MSI, 一个域用作wired IRQS.
-
判断GICD 是否支持rss, rss(Range Selector Support)表示SGI中断亲和性的范围 GICD_TYPER寄存器bit[26], 如果该字段为0,表示中断路由(IRI) 支持affinity 0-15的SGI,如果该字段为1, 表示支持affinity 0 - 255的SGI
-
判断是否支持通过写GICD寄存器生成消息中断。GICD_TYPER寄存器bit[16]
-
设置handle_arch_irq为gic_handle_irq。在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,从而进入GIC驱动,进行后续的中断处理。
-
更新vlpi相关配置。gic虚拟化相关。
-
初始化ITS。 Interrupt Translation Service, 用来解析LPI中断。 初始化之前需要先判断GIC是否支持LPI,该功能在ARM里是可选的。
-
gic_smp_init主要包含两个作用。
- 触发SGI中断,用于CPU之间通信。当一个CPU core上的软件控制行为需要传递到其他的CPU上的时候,就会调用这个callback函数(例如在某一个CPU上运行的进程调用了系统调用进行reboot)。对于GIC v3,这个callback定义为gic_raise_softirq.
- 设置CPU 上下线流程中和GIC相关的状态机。
-
初始化GICD。
-
初始化CPU interface.
-
初始化GIC电源管理。
2.2.2.__irq_domain_add()
第1104行函数irq_domain_create_tree调用__irq_domain_add,分配并初始化struct irq_domain,并将创建好的struct irq_domain加入全局链表irq_domain_list。
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct device_node *of_node = to_of_node(fwnode);
struct irq_domain *domain;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
of_node_get(of_node);
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->fwnode = fwnode;
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
irq_domain_check_hierarchy(domain);
mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list);
mutex_unlock(&irq_domain_mutex);
return domain;
}
3.Types of irq_domain mappings
注册中断处理函数是唯一的中断号是 Kernel分配的全局唯一虚拟中断号,Linux通过一定方式将其和中断控制域的物理中断号关联。对每个中断控制域来讲,这种映射/关联主要有以下三类:
- 线性映射(Linear):固定大小的数组映射(物理中断号为数组索引)。当该中断控制域的物理中断号较连续且数量不大时使用。
- 基树映射(Radix Tree): 与线性映射相反,物理中断号比较大,或者不太连续时使用。
- 直接映射(No Map/Diret): 有些中断控制器支持物理中断号编程,其被分配到的虚拟中断号可以被直接设定到中断控制器。
- Legacy映射(传统映射): 特殊的一种映射模式。当需要固定分配一部分中断编号范围时使用。设备驱动可以直接使用约定的虚拟中断号来进行IRQ操作。
3.1.Linear
The linear reverse map maintains a fixed size table indexed by the hwirq number. When a hwirq is mapped, an irq_desc is allocated for the hwirq, and the IRQ number is stored in the table.
The Linear map is a good choice when the maximum number of hwirqs is fixed and a relatively small number (~ < 256).
- The advantages of this map are fixed time lookup for IRQ numbers, and irq_descs are only allocated for in-use IRQs.
- The disadvantage is that the table must be as large as the largest possible hwirq number.
The majority of drivers should use the linear map.
HW interrupt ID作为index,通过查表可以获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要满足一定的条件:hw ID不能过大,而且ID排列最好是紧密的。对于线性映射,其接口API如下:
irq_domain_add_linear()
irq_domain_create_linear()
3.2.Tree
The irq_domain maintains a radix tree map from hwirq numbers to Linux IRQs. When an hwirq is mapped, an irq_desc is allocated and the hwirq is used as the lookup key for the radix tree.
The tree map is a good choice if the hwirq number can be very large since it doesn’t need to allocate a table as large as the largest hwirq number. The disadvantage is that hwirq to IRQ number lookup is dependent on how many entries are in the table.
Very few drivers should need this mapping.
建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。对于Radix Tree map,其接口API如下:
irq_domain_add_tree()
irq_domain_create_tree()
3.3.No Map
The No Map mapping is to be used when the hwirq number is programmable in the hardware. In this case it is best to program the Linux IRQ number into the hardware itself so that no mapping is required. Calling irq_create_direct_mapping() will allocate a Linux IRQ number and call the .map() callback so that driver can program the Linux IRQ number into the hardware.
Most drivers cannot use this mapping.
有些中断控制器很强,可以通过寄存器配置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()
3.4.Legacy
The Legacy mapping is a special case for drivers that already have a range of irq_descs allocated for the hwirqs. It is used when the driver cannot be immediately converted to use the linear mapping. For example, many embedded system board support files use a set of #defines for IRQ numbers that are passed to struct device registrations. In that case the Linux IRQ numbers cannot be dynamically assigned and the legacy mapping should be used.
The legacy map assumes a contiguous range of IRQ numbers has already been allocated for the controller and that the IRQ number can be calculated by adding a fixed offset to the hwirq number, and visa-versa. The disadvantage is that it requires the interrupt controller to manage IRQ allocations and it requires an irq_desc to be allocated for every hwirq, even if it is unused.
The legacy map should only be used if fixed IRQ mappings must be
supported. For example, ISA controllers would use the legacy map for
mapping Linux IRQs 0-15 so that existing ISA drivers get the correct IRQ
numbers.
- irq_domain_add_simple()
- irq_domain_add_legacy()
- irq_domain_add_legacy_isa()
如下图所示:
下图是针对3个控制器,结合nomap、linear、tree等3种映射方法,组成“irq_domain”的例子。
4.创建中断号映射
//创建HWIRQ映射,返回对应的虚拟中断号
extern unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq);
//根据Firmware Spec创建映射,一般与设备树DTS解析的信息有关
extern unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec);
//创建Direct Mapping
extern unsigned int irq_create_direct_mapping(struct irq_domain *host);
//创建固定映射
extern int irq_create_strict_mappings(struct irq_domain *domain,
unsigned int irq_base,
irq_hw_number_t hwirq_base,
int count);
4.1.irq_create_mapping
驱动调用该函数的时候必须提供HW interrupt ID,也就是意味着driver知道自己使用的HW interrupt ID,而一般情况下,HW interrupt ID其实对具体的driver应该是不可见的,不过有些场景比较特殊,例如GPIO类型的中断,它的HW interrupt ID和GPIO有着特定的关系,driver知道自己使用哪个GPIO,也就是知道使用哪一个HW interrupt ID了。
4.2.irq_create_strict_mappings
用来为一组HW interrupt ID建立映射。
4.3.irq_create_of_mapping
利用device tree进行映射关系的建立。
4.4.irq_create_direct_mapping
给no map那种类型interrupt controller使用。
5.Mapping 建立
5.1.概述
系统中HW interrupt ID和IRQ number的mapping 是在整个系统初始化的过程中建立起来的,过程如下:
-
DTS文件描述了系统中的interrupt controller以及外设IRQ的拓扑结构,在linux kernel启动的时候,由bootloader传递给kernel(实际传递的是DTB);
-
在Device Tree初始化的时候,形成了系统内所有的device node的树状结构,当然其中包括所有和中断拓扑相关的数据结构(所有的interrupt controller的node和使用中断的外设node);
-
在machine driver初始化的时候会调用of_irq_init函数,在该函数中会扫描所有interrupt controller的节点,并调用适合的interrupt controller driver进行初始化。毫无疑问,初始化需要注意顺序,首先初始化root,然后first level,second level,最好是leaf node。在初始化的过程中,一般会调用上节中的接口函数向系统增加irq domain。有些interrupt controller会在其driver初始化的过程中创建映射;
-
在各个driver初始化的过程中,创建映射。
5.2. device node转化为platform_device(重点分析irq)
相关代码:
drivers/of/platform.c
当系统启动到board文件时,会调用.init_machine,各个平台定义该函数,接着调用of_platform_populate()接口,加载平台总线和平台设备。函数调用关系:
of_platform_default_populate_init
---> of_platform_default_populate
---> of_platform_populate
---> of_platform_bus_create
---> of_platform_device_create_pdata
---> of_device_alloc
---> of_irq_to_resource_table
---> of_irq_to_resource
--->of_irq_get
---> of_irq_parse_one
---> irq_create_of_mapping
---> irq_create_fwspec_mapping
---> irq_domain_translate //解析参数
---> d->ops->translate (gic_irq_domain_translate)
---> d->ops->xlate
---> irq_domain_alloc_irqs
---> gic_irq_domain_alloc //执行软硬件的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数
实现如下:
1 struct platform_device *of_device_alloc(struct device_node *np,
2 const char *bus_id,
3 struct device *parent)
4 {
5 struct platform_device *dev;
6 int rc, i, num_reg = 0, num_irq;
7 struct resource *res, temp_res;
8 dev = platform_device_alloc("", -1);
9 /* count the io and irq resources */
10 ...
11 num_irq = of_irq_count(np); // 统计这个节点的interrupts属性中描述了几个中断
12 /* Populate the resource table */
13 if (num_irq || num_reg) {
14 res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
15 ...
16 dev->num_resources = num_reg + num_irq;
17 dev->resource = res;
18 ...
19 of_irq_to_resource_table(np, res, num_irq) // 解析interrupts属性,将每一个中断转化为resource结构体
20 }
21 ...
22 }
-
of_irq_count
这个函数会解析interrupts属性,并统计其中描述了几个中断。
-
of_irq_to_resource_table
得知interrupts中描述了几个中断,开始将这些中断转换为resource。
453 int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
454 int nr_irqs)
455 {
456 int i;
457
458 for (i = 0; i < nr_irqs; i++, res++)
459 if (of_irq_to_resource(dev, i, res) <= 0)
460 break;
461
462 return i;
463 }
- of_irq_to_resource:构建resource
350 int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
351 {
352 int irq = of_irq_get(dev, index);
356
357 /* Only dereference the resource if both the
358 * resource and the irq are valid. */
359 if (r && irq) {
360 const char *name = NULL;
361
362 memset(r, 0, sizeof(*r));
363 /*
364 * Get optional "interrupt-names" property to add a name
365 * to the resource.
366 */
367 of_property_read_string_index(dev, "interrupt-names", index,
368 &name);
369
370 r->start = r->end = irq;
371 r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
372 r->name = name ? name : of_node_full_name(dev);
373 }
374
375 return irq;
376 }
重点分析 of_irq_get,这个函数会获得device node,如pinctrl@11000000节点的interrupts属性的第index个中断的参数,这是通过of_irq_parse_one完成的,然后获得该中断所隶属的interrupt-controller的irq domain,利用该domain的of_xlate函数从前面表示第index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中,也就是gic的irq domain。
结合kernel代码分析:
of_irq_get
->of_irq_parse_one
-> irq_create_of_mapping
--> irq_create_fwspec_mapping
—>gic_irq_domain_translate
of_irq_get:
388 int of_irq_get(struct device_node *dev, int index)
389 {
390 int rc;
391 struct of_phandle_args oirq;
392 struct irq_domain *domain;
393
394 rc = of_irq_parse_one(dev, index, &oirq); // 获得interrupts的第index个中断参数,并封装到oirq中
398 domain = irq_find_host(oirq.np);
401
402 return irq_create_of_mapping(&oirq); //返回映射到的virq
403 }
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;
// 根据中断控制器的device_node找到所对应的irq domain,在前面GIC驱动注册irq domian的时候,
// 会将irq_domain的fwnode设置为中断控制器的device_node的fwnode成员
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;
}
// 对于GIC的irq domain来说,会调用d->ops->translate(d, fwspec, hwirq, type)
// 也就是gic_irq_domain_translate,对于没有定义translate的irq_domain,会调用d->ops->xlate
irq_domain_translate(domain, fwspec, &hwirq, &type);
...
// 从这个irq domain查询看该hwirq之前是否已经映射过,一般情况下都没有
virq = irq_find_mapping(domain, hwirq);
if (virq) {
...
return virq;
...
}
if (irq_domain_is_hierarchy(domain)) { // 对于GIC的irq domain这样定义了alloc的domain来说,走这个分支
virq = irq_domain_alloc_irqs(domain, , NUMA_NO_NODE, fwspec);
} else { // 其他没有定义irq_domain->ops->alloc的domain,走这个分支
/* Create mapping */
virq = irq_create_mapping(domain, hwirq);
}
irq_data = irq_get_irq_data(virq);
/* Store trigger type */
irqd_set_trigger_type(irq_data, type);
return virq; //返回映射到的virq
}
gic irq domain的translate过程:
1104 static int gic_irq_domain_translate(struct irq_domain *d,
1105 struct irq_fwspec *fwspec,
1106 unsigned long *hwirq,
1107 unsigned int *type)
1108 { // 检查描述中断的参数个数是否合法
1109 if (is_of_node(fwspec->fwnode)) {
1110 if (fwspec->param_count < 3)
1111 return -EINVAL;
1112
1113 switch (fwspec->param[0]) {
1114 case 0: /* SPI */
1115 *hwirq = fwspec->param[1] + 32;
1116 break;
1117 case 1: /* PPI */
1118 case GIC_IRQ_TYPE_PARTITION:
1119 *hwirq = fwspec->param[1] + 16;
1120 break;
1121 case GIC_IRQ_TYPE_LPI: /* LPI */
1122 *hwirq = fwspec->param[1];
1123 break;
1124 default:
1125 return -EINVAL;
1126 }
1127
1128 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
1130 /*
1131 * Make it clear that broken DTs are... broken.
1132 * Partitionned PPIs are an unfortunate exception.
1133 */
1134 WARN_ON(*type == IRQ_TYPE_NONE &&
1135 fwspec->param[0] != GIC_IRQ_TYPE_PARTITION);
1136 return 0;
1137 }
1138
1139 if (is_fwnode_irqchip(fwspec->fwnode)) {
1140 if(fwspec->param_count != 2)
1141 return -EINVAL;
1142
1143 *hwirq = fwspec->param[0];
1144 *type = fwspec->param[1];
1145
1146 WARN_ON(*type == IRQ_TYPE_NONE);
1147 return 0;
1148 }
1149
1150 return -EINVAL;
1151 }
}
6.Debug
CONFIG_IRQ_DOMAIN_DEBUG and CONFIG_GENERIC_IRQ_DEBUGFS is enabled. virq_debug_show and irq_debug_show functions show the debug information.
- /sys/kernel/debug/irq_domain_mapping
- /sys/kernel/irq
- /sys/kernel/irq/irqs/
- /sys/kernel/irq/domains
- /proc/irq
6.1.代码
- /kernel/irq/irqdomain.c
- /kernel/irq/debugfs.c
refer to
- https://l2h.site/2019/01/01/linux-interrupt-3/
- https://www.cnblogs.com/pengdonglin137/p/6847685.html
- https://blog.csdn.net/qq_33513098/article/details/88723282#s3c24xx_irq_map_of_441
更多推荐
所有评论(0)