Linux中断子系统5(基于Linux6.6)---初始化中断控制器?
一、前情回顾
使用设备树的情况下,对所有的中断控制器进行了检索,提取到一个链表中。并对这些中断控制器中所谓的父中断控制器进行检索,并按照等级一次初始化。以gic为例:
void __init of_irq_init(const struct of_device_id *matches)
{
......
ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
......
}
二、什么是GIC
GIC 在 ARM 架构中通常有两个主要版本:GICv2 和 GICv3,其中 GICv3 是最新的版本。
2.1、GICv2
GICv2 是较早的版本,支持单核或多核的 ARM 处理器,能够为每个处理器核提供中断处理能力。GICv2 通常由两个主要部分组成:
- CPU Interface(CPU 接口):与处理器的每个核心连接,负责处理中断并将中断事件传递给 CPU。
- Distributor(分配器):负责从外部设备接收中断请求,并将它们分配到 CPU 接口。
GICv2 在多核系统中工作时,所有 CPU 核共享一个分配器,但每个 CPU 核都有自己的接口。
2.2、GICv3
GICv3 引入了许多新的功能和改进,尤其是支持 ARMv8 架构中的 64 位处理器。与 GICv2 相比,GICv3 提供了更强大的功能,主要包括:
- 更高效的中断路由:GICv3 允许中断在不同的 CPU 核之间动态地路由,并且支持为不同的核心配置不同的中断优先级。
- 虚拟化支持:GICv3 为虚拟化环境提供了更好的支持,允许虚拟机(VM)和物理机共享中断资源。GICv3 引入了虚拟化控制器(VIC)来管理虚拟机的中断。
- 增强的中断优先级控制:GICv3 提供了更灵活的中断优先级机制,支持更细粒度的优先级划分。
2.3、GICv3 框图结构
+-------------------+
| Distributor | <-------------------+
| (GICv3) | |
+-------------------+ |
| |
| |
+--------------+--------------+ |
| | |
+------------------+ +------------------+ |
| Interrupt | | Interrupt | |
| Source | | Source | |
| (External, | | (External, | |
| Internal) | | Internal) | |
+------------------+ +------------------+ |
| | |
+------------------+ +------------------+ |
| Interrupt | | Interrupt | |
| Line/Signal | | Line/Signal | |
| (IRQ) | | (IRQ) | |
+------------------+ +------------------+ |
| | |
+-------------------+ +-------------------+ |
| Interrupt | | Interrupt | |
| Controller/ | | Controller/ | |
| (Processor | | (Processor | |
| Core Interface) | | Core Interface) | |
+-------------------+ +-------------------+ |
| | |
+-------------------+ +-------------------+ |
| CPU Core 0 | | CPU Core 1 | |
| | | | |
+-------------------+ +-------------------+ |
| | |
+-------------------+ +-------------------+ |
| CPU Core N | | CPU Core N | |
| | | | |
+-------------------+ +-------------------+ |
| | |
. . .
. . .
. . .
2.4、关键组件解释
-
Distributor(分配器):
- 这是 GIC 的核心部分,负责接收来自外部或内部设备的中断请求,并根据优先级、目标处理器等规则,将中断请求分发给相应的 CPU 核。Distributor 管理着中断的启用、禁用、路由、优先级等配置。
-
Interrupt Sources(中断源):
- 外部设备(如 I/O 设备、网络接口、外部总线等)和内部设备(如定时器、处理器内部异常等)产生中断请求。每个中断源通过中断线路(IRQ)向 GIC 的 Distributor 发出信号。
-
Interrupt Lines/Signals(中断线路/信号):
- 这些是硬件设备或系统内部各个部件产生的中断信号(IRQ)。这些信号将会传递到 GIC 的 Distributor。
-
CPU Core Interface(CPU 核心接口):
- 每个 CPU 核都有一个 CPU 接口,它负责接收来自 Distributor 的中断请求并将其传递给 CPU。每个 CPU 核接口可以独立配置,以处理从 Distributor 路由过来的中断信号。
-
CPU Cores(处理器核心):
- 处理器中的每个核心都可以通过 CPU 核心接口接收并处理中断。当中断被传递给某个核心时,处理器会暂停当前任务,转而执行中断服务例程(ISR)。
GICv3(Generic Interrupt Controller version 3)是 ARM 架构中用于中断管理的关键组件,它通过寄存器来控制中断的路由、优先级、使能等操作。GICv3 的寄存器可以大致分为几个主要部分,涵盖了 分配器(Distributor) 和 CPU 接口(CPU Interface) 两个主要功能模块。
2.5、Distributor 寄存器
Distributor 部分负责管理中断的分发,包括中断使能、优先级设置、目标处理器选择等。主要寄存器包括:
(1) Control Register (GICD_CTLR)
- 地址:
0x0 - 描述:控制 Distributor 的启用与禁用。它的主要作用是控制 GIC Distributor 的工作模式。
- 位 [0]:
ENABLE:启用或禁用中断分配器。
- 位 [0]:
(2) Type Register (GICD_TYPER)
- 地址:
0x4 - 描述:指示 GIC 分配器支持的中断数目、CPU 核心数等信息。
- 位 [31:10]:
LSB:表示支持的中断线的数量。 - 位 [9:0]:
CPUS:表示支持的 CPU 核心数。
- 位 [31:10]:
(3) Interrupt Enable/Disable Registers (GICD_ISENABLER, GICD_ICENABLER)
- 地址:
0x100-0x17C(每个中断源的启用/禁用寄存器) - 描述:分别用来启用或禁用特定中断源(IRQ)。这些寄存器允许对中断进行按位启用或禁用。
- 启用:
GICD_ISENABLER,每个中断源对应一个位。 - 禁用:
GICD_ICENABLER,每个中断源对应一个位。
- 启用:
(4) Interrupt Set-Pending/ Clear-Pending Registers (GICD_ISPENDR, GICD ICPENDR)
- 地址:
0x200-0x27C - 描述:用于设置或清除中断的挂起状态。通过这些寄存器,可以手动触发或清除某个中断的挂起位。
- 设置挂起:
GICD_ISPENDR。 - 清除挂起:
GICD_ICPENDR。
- 设置挂起:
(5) Interrupt Priority Registers (GICD_IPRIORITYR)
- 地址:
0x400-0x7F8 - 描述:设置中断的优先级。每个中断源(IRQ)有一个对应的优先级,越小的值表示越高的优先级。每个寄存器控制多个中断的优先级。
- 每个中断的优先级占用 8 位。
(6) Interrupt Target Registers (GICD_ITARGETSR)
- 地址:
0x800-0x9F8 - 描述:设置每个中断源的目标处理器(CPU)。可以将中断分发到特定的 CPU 核心。
- 每个寄存器对应 4 个中断源。
(7) Configuration Registers (GICD_ICFGR)
- 地址:
0xC00-0xCFC - 描述:设置中断的触发方式(边沿触发或电平触发)。每个寄存器控制多个中断的触发方式。
2.6、CPU Interface 寄存器
每个 CPU 核心都有自己的 CPU 接口,这部分负责接收从 Distributor 分发来的中断请求并将其送给处理器。主要寄存器包括:
(1) Control Register (GICC_CTLR)
- 地址:
0x0 - 描述:控制 CPU 接口的启用、禁用,以及相关的中断处理模式。
- 位 [0]:
ENABLE:启用或禁用 CPU 接口。 - 位 [3]:
FIQEN:启用或禁用快速中断(FIQ)模式。
- 位 [0]:
(2) Interrupt Priority Register (GICC_PRIORITYR)
- 地址:
0x4 - 描述:设置当前 CPU 的中断优先级。每个 CPU 核都有一个独立的优先级寄存器。
(3) Interrupt Acknowledge Register (GICC_IAR)
- 地址:
0xC - 描述:用于在 CPU 接口中确认收到的中断。每个 CPU 核有一个 IAR,CPU 通过该寄存器读取中断 ID 并确认。
- 返回值:当前中断 ID。
(4) End of Interrupt Register (GICC_EOIR)
- 地址:
0x10 - 描述:用于告知 GIC 已完成对中断的处理。当 CPU 处理完一个中断后,必须写入此寄存器来清除中断。
(5) Run-Locked Register (GICC_RPR)
- 地址:
0x14 - 描述:该寄存器用于读取中断处理程序的优先级(即当前正在执行的中断的优先级)。
(6) Running Priority Register (GICC_RPR)
- 地址:
0x14 - 描述:这个寄存器可以读取当前正在执行的中断的优先级。
2.7、重要的中断管理寄存器
(1) CPU Interface Pending Register (GICC_HPPIR)
- 地址:
0x18 - 描述:此寄存器返回当前 CPU 核所挂起的最高优先级的中断 ID。
(2) Binary Point Register (GICC_BPR)
- 地址:
0x20 - 描述:用于设置 CPU 核的中断屏蔽级别。控制 CPU 能够接收的最小优先级。
(3) Software Generated Interrupt Register (GICC_SGI)
- 地址:
0xF00 - 描述:该寄存器用于生成软件中断(SGI),即 CPU 可以通过软件触发中断。
三、初始化一个中断控制器
drivers/of/irq.c
void __init of_irq_init(const struct of_device_id *matches)
{
......
ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
......
}
drivers/irqchip/irq-gic-v3.c
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
phys_addr_t dist_phys_base;
void __iomem *dist_base;
struct redist_region *rdist_regs;
struct resource res;
u64 redist_stride;
u32 nr_redist_regions;
int err, i;
dist_base = gic_of_iomap(node, 0, "GICD", &res);
if (IS_ERR(dist_base)) {
pr_err("%pOF: unable to map gic dist registers\n", node);
return PTR_ERR(dist_base);
}
dist_phys_base = res.start;
err = gic_validate_dist_version(dist_base);
if (err) {
pr_err("%pOF: no distributor detected, giving up\n", node);
goto out_unmap_dist;
}
if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
nr_redist_regions = 1;
rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
GFP_KERNEL);
if (!rdist_regs) {
err = -ENOMEM;
goto out_unmap_dist;
}
for (i = 0; i < nr_redist_regions; i++) {
rdist_regs[i].redist_base = gic_of_iomap(node, 1 + i, "GICR", &res);
if (IS_ERR(rdist_regs[i].redist_base)) {
pr_err("%pOF: couldn't map region %d\n", node, i);
err = -ENODEV;
goto out_unmap_rdist;
}
rdist_regs[i].phys_base = res.start;
}
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
redist_stride = 0;
gic_enable_of_quirks(node, gic_quirks, &gic_data);
err = gic_init_bases(dist_phys_base, dist_base, rdist_regs,
nr_redist_regions, redist_stride, &node->fwnode);
if (err)
goto out_unmap_rdist;
gic_populate_ppi_partitions(node);
if (static_branch_likely(&supports_deactivate_key))
gic_of_setup_kvm_info(node);
return 0;
out_unmap_rdist:
for (i = 0; i < nr_redist_regions; i++)
if (rdist_regs[i].redist_base && !IS_ERR(rdist_regs[i].redist_base))
iounmap(rdist_regs[i].redist_base);
kfree(rdist_regs);
out_unmap_dist:
iounmap(dist_base);
return err;
}
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
drivers/irqchip/irq-gic-v3.c
static void __iomem *gic_of_iomap(struct device_node *node, int idx,
const char *name, struct resource *res)
{
void __iomem *base;
int ret;
ret = of_address_to_resource(node, idx, res);//获取reg资源
if (ret)
return IOMEM_ERR_PTR(ret);
gic_request_region(res->start, resource_size(res), name);
base = of_iomap(node, idx);//映射reg资源
return base ?: IOMEM_ERR_PTR(-ENOMEM);
}
3.1、读ID寄存器
GICv3 的 ID 寄存器 用于提供 GIC 版本和配置信息,通常有以下几个重要的寄存器:
-
GICD_TYPER (Distributor Type Register)
- 地址:
0x4 - 描述:用于获取 GIC Distributor 的特性信息,如支持的中断数、CPU 核心数等。
- 位 [31:10]:
LSB:表示中断行的数量。 - 位 [9:0]:
CPUS:表示支持的 CPU 核心数。
- 位 [31:10]:
- 地址:
-
GICC_IAR (Interrupt Acknowledge Register)
- 地址:
0xC - 描述:用于读取当前激活的中断 ID。此寄存器返回正在处理的中断的 ID。
- 地址:
-
GICC_HPPIR (Highest Priority Pending Interrupt Register)
- 地址:
0x18 - 描述:用于返回当前 CPU 核上挂起的最高优先级的中断 ID。
- 地址:
这些寄存器有助于获取 GIC 的基本信息以及处理中断时的相关 ID。
3.2、版本信息

可以看到和printk打印的信息一样。
3.3、关闭中断
在 GIC(Generic Interrupt Controller)中,关闭中断(禁用中断)通常是通过对特定的寄存器进行操作。不同于 VIC(Vectored Interrupt Controller),GIC 的中断控制通过 Distributor 和 CPU 接口的寄存器来实现。
在 GICv3 中,禁用中断的操作通常涉及以下几个方面:
1. 禁用特定中断(通过 Distributor 部分)
GIC 中的中断是通过 GICD(GIC Distributor)部分进行管理的。要禁用中断,通常需要操作 GICD_ICENABLER(Interrupt Clear Enable Register)寄存器。
操作步骤:
- 禁用某个中断:通过设置
GICD_ICENABLER中相应的位来禁用一个中断。 - 寄存器格式:
GICD_ICENABLER的每一位对应一个中断源,设置为1表示禁用该中断。
例如,禁用中断 n(假设中断 n 的编号为 n):
uint32_t reg_offset = 0x180 + (n / 32) * 4; // 0x180 是 GICD_ICENABLER 寄存器的基地址
uint32_t bit = (1 << (n % 32)); // 获取中断 n 对应的位
*(volatile uint32_t *)(GICD_BASE + reg_offset) = bit;
2. 禁用 CPU 接口中的中断(通过 GICC)
在 GIC 中,CPU 接口的部分负责管理 CPU 上的中断。要禁用某个中断,可以使用 GICC_ICPENDR 和 GICC_IAR(Interrupt Acknowledge Register)寄存器。
操作步骤:
- 通过对
GICC中的寄存器进行操作,确认中断被正确处理和禁用。
3. 禁用所有中断
如果你想禁用所有的中断,可以通过以下方式:
- 禁用所有的挂起中断:可以通过写
1到GICD_ICENABLER中的所有位来禁用所有的中断。
例如:
*(volatile uint32_t *)(GICD_BASE + 0x180) = 0xFFFFFFFF; // 禁用所有 32 个中断
- 禁用特定 CPU 的所有中断:通过
GICC_CTLR控制位来禁用某个 CPU 上的中断处理。
*(volatile uint32_t *)(GICC_BASE + 0x0) &= ~0x1; // 通过清除 GICC_CTLR 的启用位来禁用 CPU 上的中断
在 GICv3(Generic Interrupt Controller version 3)中,清除中断通常不是通过一个专门的函数,而是通过操作相应的寄存器来完成的。清除中断的操作主要依赖于以下寄存器:
4. GICD_ICPENDR (Interrupt Clear-Pending Register) - Distributor 部分
- 地址:
0x280起,每个中断有一个对应的寄存器。 - 作用:用于清除指定中断的挂起状态(即,将该中断标记为已处理,避免它再次被触发)。
- 操作:写入相应的位将中断从挂起状态中清除。
例如:
- 对于中断源
n,要清除其挂起状态,你需要写1到GICD_ICPENDR[n/32]中的相应位。
5. GICC_EOIR (End of Interrupt Register) - CPU 接口部分
- 地址:
0x10 - 作用:通知 GIC 当前 CPU 已经完成对某个中断的处理。写入中断 ID 到此寄存器,表示该中断已经处理完毕。
- 操作:当 CPU 完成对某个中断的处理时,必须写入该中断的 ID 到
GICC_EOIR,以清除该中断并允许处理下一个中断。
例如:
- 假设当前处理中断的 ID 为
id,那么你需要执行:
-
这会清除中断并使 GIC 知道该中断已经结束。*(volatile uint32_t *)(GICC_BASE + GICC_EOIR) = id;
6. GICD_ISPENDR (Interrupt Set-Pending Register) 和 GICD_ICENABLER (Interrupt Clear Enable Register)
- 这些寄存器用于控制中断的挂起和使能状态。在清除中断时,
GICD_ISPENDR设置和GICD_ICENABLER清除用于确保中断不会再次触发,直到被重新使能。
代码示例
在 GICv3 中,清除中断的操作一般如下:
清除 Distributor 部分的挂起中断
// 清除中断 n 的挂起状态
uint32_t reg_offset = 0x280 + (n / 32) * 4;
uint32_t bit = (1 << (n % 32));
*(volatile uint32_t *)(GICD_BASE + reg_offset) = bit;
清除 CPU 接口的处理中断
// 假设当前处理中断的 ID 为 id
*(volatile uint32_t *)(GICC_BASE + GICC_EOIR) = id;
3.4、中断控制器的注册
arch/arm64/boot/dts/rockchip/rk3568.dtsi
gic: interrupt-controller@fd400000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0xfd400000 0 0x10000>, /* GICD */
<0x0 0xfd460000 0 0xc0000>; /* GICR */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
its: interrupt-controller@fd440000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0xfd440000 0x0 0x20000>;
};
};
在 GIC(Generic Interrupt Controller)中,与 VIC(Vectored Interrupt Controller)不同,它没有像 vic_register 这样的函数用于注册中断。然而,GIC 的中断管理过程通常涉及一系列配置寄存器和操作,这些操作是通过不同的初始化和配置函数完成的。
具体来说,GIC 的中断控制和配置可以通过以下几个步骤来完成:
3.4.1、GIC 中断注册和初始化的主要步骤
-
GIC 初始化: 在使用 GIC 之前,需要先对 GIC 进行初始化。通常这包括设置 GIC 的分发器部分(
GICD)和 CPU 接口部分(GICC)。初始化过程中,常见的步骤包括:
- 配置中断源(IRQ)到相应的 CPU 和优先级。
- 配置中断的触发模式(边沿触发或电平触发)。
- 启动 CPU 接口,以允许 CPU 处理中断。
一个典型的 GIC 初始化代码(伪代码)如下:
-
void gic_init(void) { // 初始化 GIC Distributor GICD_CTLR = 0; // 禁用 GICD // 配置中断源 configure_interrupts(); GICD_CTLR = 1; // 启用 GICD // 初始化 CPU 接口 GICC_CTLR = 0; // 禁用 CPU 接口 GICC_PMR = 0xF0; // 设置 CPU 接口的中断优先级掩码 GICC_CTLR = 1; // 启用 CPU 接口 } -
中断源配置: 对于每个中断源,需要通过配置
GICD的相关寄存器来注册中断。这些寄存器包括:- GICD_ISENABLER:启用中断源。
- GICD_ICENABLER:禁用中断源。
- GICD_ICPENDR:清除挂起的中断。
你可以使用类似下面的代码来启用一个中断源:
-
void gic_enable_interrupt(int irq_number) { // 根据 IRQ 编号配置 GICD_ISENABLER 寄存器,启用中断 uint32_t reg_offset = 0x100 + (irq_number / 32) * 4; uint32_t bit = (1 << (irq_number % 32)); *(volatile uint32_t *)(GICD_BASE + reg_offset) = bit; } -
中断处理程序的注册: 虽然 GIC 没有
vic_register这样的 API,通常情况下,你需要手动编写中断服务例程(ISR)并将其与特定的中断号绑定。这个过程通常涉及以下几个步骤:- 在特定的中断向量表中定义 ISR。
- 使用中断控制器的 API 来确保中断源与 ISR 绑定。
在某些嵌入式操作系统或中间件中,可能会提供封装了 GIC 配置和 ISR 注册的高级 API。比如,FreeRTOS 或 Linux 会使用一些抽象层来处理 GIC 的中断注册。
-
中断触发和处理: 一旦配置完毕,GIC 会自动将中断传递给相应的 CPU。如果是 ARM 的 GICv3,CPU 会通过读取
GICC_IAR(Interrupt Acknowledge Register)来获取正在处理中断的编号,并在中断服务程序中进行相应处理。中断处理完成后,CPU 会通过
GICC_EOIR(End of Interrupt Register)来结束中断处理,并通知 GIC 中断已被处理。
void handle_irq(void) {
int irq = read_gicc_iar();
if (irq == YOUR_INTERRUPT_NUMBER) {
// 处理你的中断
}
write_gicc_eoir(irq); // 通知 GIC 中断处理完毕
}
3.5、中断级联
kernel/irq/chip.c
void
irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handle,
void *data)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0); //这里获取到父中断的描述符
if (!desc)
return;
desc->irq_common_data.handler_data = data; //data就是这里的中断控制器结构体,代表一个中断控制器
__irq_do_set_handler(desc, handle, 1, NULL); //把中断级联的函数,绑定到它的父中断的某个中断源上
irq_put_desc_busunlock(desc, flags);
}
kernel/irq/chip.c
//绑定这中断控制器到,父中断控制器上
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
int is_chained, const char *name)
{
if (!handle) {
handle = handle_bad_irq;
} else {
struct irq_data *irq_data = &desc->irq_data; //获取父中断的中断控制器
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY //定义这个才会是级联中断控制器绑定,否则还是水平级联
/*
* With hierarchical domains we might run into a
* situation where the outermost chip is not yet set
* up, but the inner chips are there. Instead of
* bailing we install the handler, but obviously we
* cannot enable/startup the interrupt at this point.
*/
//这边是想要找到一个有用的父中断控制器(如果A的父亲没,检查A的爷爷...)
while (irq_data) {
if (irq_data->chip != &no_irq_chip)
break;
/*
* Bail out if the outer chip is not set up
* and the interrrupt supposed to be started
* right away.
*/
if (WARN_ON(is_chained))
return;
/* Try the parent */
irq_data = irq_data->parent_data;
}
#endif
if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
return;
}
/* Uninstall? */
if (handle == handle_bad_irq) {
if (desc->irq_data.chip != &no_irq_chip)
mask_ack_irq(desc);
irq_state_set_disabled(desc);
if (is_chained)
desc->action = NULL;
desc->depth = 1;
}
desc->handle_irq = handle; //绑定中断控制器,绑定到父中断描述符的高级中断控制器上(代表这里是一个子中断控制器,而不是外设)
desc->name = name; //绑定名字
if (handle != handle_bad_irq && is_chained) {
//获取触发器类型
unsigned int type = irqd_get_trigger_type(&desc->irq_data);
/*
* We're about to start this interrupt immediately,
* hence the need to set the trigger configuration.
* But the .set_type callback may have overridden the
* flow handler, ignoring that we're dealing with a
* chained interrupt. Reset it immediately because we
* do know better.
*/
if (type != IRQ_TYPE_NONE) {
__irq_set_trigger(desc, type); //非NONE则,设置触发器
desc->handle_irq = handle; //设置高级函数
}
//这是一个中断设备,已经被初始化(使用了),所以不能在probe,request,线程话等等
irq_settings_set_noprobe(desc);
irq_settings_set_norequest(desc);
irq_settings_set_nothread(desc);
desc->action = &chained_action; //这里先把中断执行函数设置为无效
irq_activate_and_startup(desc, IRQ_RESEND);
}
}
绑定到父中断控制器上的函数是什么?
上面对所有中断控制器级联方式绑定都一样,下面这个函数,就是根据具体要绑定的中断控制器来实现。
在 GICv3 (Generic Interrupt Controller v3) 中,vic_handle_irq_cascaded 是在 VIC (Vectored Interrupt Controller) 中处理级联中断的函数,而 GICv3 则采用不同的方式来处理级联中断。在 GICv3 中,处理级联中断的方式通常通过中断控制器的硬件寄存器和特定的 API 来管理。
GICv3 级联中断处理
在 ARM GICv3 中,级联中断通常是指在多个 GIC 中断控制器之间的中断传递,例如主 GIC (GIC Distributor) 和 CPU 处理器的接口(GICC)。在这种结构中,每个中断控制器可能会处理一部分中断源,然后通过一定的级联方式将它们传递给下一个层级的中断控制器或 CPU 接口。
中断级联处理的基本步骤
在 GICv3 中,CPU 通常会读取来自 GIC 的 Interrupt Acknowledge Register(GICC_IAR),然后处理中断,最后通过 End of Interrupt Register(GICC_EOIR)来结束中断。具体的级联方式取决于 GICv3 的设计,通常涉及以下几个方面:
-
GIC Distributor(GICD):它负责处理各个中断源的分发、优先级设置、使能控制等。它还负责管理多个 CPU 接口的中断。
-
CPU Interface(GICC):每个 CPU 接口(
GICC)负责处理 CPU 内部的中断,读取中断源、响应中断,并最终通过执行 ISR 处理该中断。 -
中断源的分配和触发:GICv3 中的中断可以通过 SPI (Shared Peripheral Interrupts)、PPI (Private Peripheral Interrupts) 和 SGI (Software Generated Interrupts) 等不同的类型来处理。级联中断通常指的是 GICD 和 GICC 之间的交互,其中 GICD 将 SPI 中断分发到 CPU,CPU 通过 GICC 接口来处理中断。
处理级联中断
在 GICv3 的处理中,当中断从 GICD 级联到 GICC 后,CPU 需要通过以下步骤来处理中断:
-
中断请求处理:
- CPU 接口(GICC)会根据中断的优先级和触发方式来检查是否有挂起的中断。它会从
GICC_IAR(Interrupt Acknowledge Register)中获取当前触发的中断源。
- CPU 接口(GICC)会根据中断的优先级和触发方式来检查是否有挂起的中断。它会从
-
处理中断:
- 一旦中断被识别,CPU 会根据中断的号,调用相应的中断服务程序(ISR)。
-
结束中断:
- 处理中断后,CPU 需要通过
GICC_EOIR(End of Interrupt Register)通知 GIC 该中断已处理完毕,从而允许 GIC 继续处理其他中断。
- 处理中断后,CPU 需要通过
级联中断的实际代码实现
在处理级联中断时,通常 CPU 会通过 GICC_IAR 来获取中断号,之后通过调用相应的 ISR 来处理中断。这个过程类似于 VIC 中 vic_handle_irq_cascaded 的功能,但在 GICv3 中,通常通过操作相关的寄存器来完成。
下面是一个 GICv3 级联中断处理的简化伪代码:
drivers/irqchip/irq-gic-v3.c
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
__gic_handle_irq_from_irqsoff(regs);
else
__gic_handle_irq_from_irqson(regs);
}
include/linux/irqdesc.h
static inline void *irq_desc_get_chip_data(struct irq_desc *desc)
{
return desc->irq_data.chip_data;
}
static inline void *irq_desc_get_handler_data(struct irq_desc *desc)
{
return desc->irq_common_data.handler_data;
}
set_handle_irq(gic_handle_irq);
drivers/irqchip/irq-gic-v3.c
static int __init gic_init_bases(phys_addr_t dist_phys_base,
void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
u32 typer;
int err;
...
set_handle_irq(gic_handle_irq);
...
}
3.6、中断映射并执行,中断处理函数
include/linux/irqdomain.h
/**
* irq_find_mapping() - Find a linux irq from a hw irq number.
* @domain: domain owning this hardware interrupt
* @hwirq: hardware irq number in that domain space
*/
static inline unsigned int irq_find_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
unsigned int irq;
if (__irq_resolve_mapping(domain, hwirq, &irq))
return irq;
return 0;
}
kernel/irq/irqdomain.c
/**
* __irq_resolve_mapping() - Find a linux irq from a hw irq number.
* @domain: domain owning this hardware interrupt
* @hwirq: hardware irq number in that domain space
* @irq: optional pointer to return the Linux irq if required
*
* Returns the interrupt descriptor.
*/
struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq,
unsigned int *irq)
{
struct irq_desc *desc = NULL;
struct irq_data *data;
/* Look for default domain if nececssary */
/* 确认域有效,才能做反映射 */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL)
return desc;
if (irq_domain_is_nomap(domain)) {
if (hwirq < domain->hwirq_max) {
data = irq_domain_get_irq_data(domain, hwirq);
if (data && data->hwirq == hwirq)
desc = irq_data_to_desc(data);
if (irq && desc)
*irq = hwirq;
}
return desc;
}
rcu_read_lock();
/* Check if the hwirq is in the linear revmap. */
/* /* 检查硬件中断号,是不是在这个域范内内 */ */
if (hwirq < domain->revmap_size)
data = rcu_dereference(domain->revmap[hwirq]);
else
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
if (likely(data)) {
desc = irq_data_to_desc(data);
if (irq)
*irq = data->irq;
}
rcu_read_unlock();
return desc;
}
EXPORT_SYMBOL_GPL(__irq_resolve_mapping);
kernel/irq/irqdomain.c
/** 获取这中断控制器的数据
* irq_domain_get_irq_data - Get irq_data associated with @virq and @domain
* @domain: domain to match
* @virq: IRQ number to get irq_data
*/
struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain,
unsigned int virq)
{
struct irq_data *irq_data;
for (irq_data = irq_get_irq_data(virq); irq_data;
irq_data = irq_data->parent_data)
if (irq_data->domain == domain) //保证找的到的这个中断控制器的是其父域下面的
return irq_data;
return NULL;
}
kernel/irq/irqdesc.c
int handle_irq_desc(struct irq_desc *desc)
{
struct irq_data *data;
if (!desc)
return -EINVAL;
data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_hardirq() && handle_enforce_irqctx(data)))
return -EPERM;
generic_handle_irq_desc(desc); //执行中断函数
return 0;
}
/**
* generic_handle_irq - Invoke the handler for a particular irq
* @irq: The irq number to handle
*
* Returns: 0 on success, or -EINVAL if conversion has failed
*
* This function must be called from an IRQ context with irq regs
* initialized.
*/
int generic_handle_irq(unsigned int irq) //上面获取到了存放的中断,这里则执行中断函数
{
return handle_irq_desc(irq_to_desc(irq));
}
EXPORT_SYMBOL_GPL(generic_handle_irq);
include/linux/irqdesc.h
/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt.
*/
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc); //这里就是执行高级中断 函数了
}
3.7、申请并初始化一个中断域
drivers/irqchip/irq-gic-v3.c
static int __init gic_init_bases(phys_addr_t dist_phys_base,
void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
u32 typer;
int err;
...
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
...
}
include/linux/irqdomain.h
static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data);
}
kernel/irq/irqdomain.c
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;
domain = __irq_domain_create(fwnode, size, hwirq_max, direct_max,
ops, host_data);
if (domain)
__irq_domain_publish(domain);
return domain;
}
EXPORT_SYMBOL_GPL(__irq_domain_add);
kernel/irq/irqdomain.c
static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode,
unsigned int size,
irq_hw_number_t hwirq_max,
int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irqchip_fwid *fwid;
struct irq_domain *domain;
static atomic_t unknown_domains;
if (WARN_ON((size && direct_max) ||
(!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max) ||
(direct_max && (direct_max != hwirq_max))))
return NULL;
domain = kzalloc_node(struct_size(domain, revmap, size),
GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
if (!domain)
return NULL;
if (is_fwnode_irqchip(fwnode)) {
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
switch (fwid->type) {
case IRQCHIP_FWNODE_NAMED:
case IRQCHIP_FWNODE_NAMED_ID:
domain->fwnode = fwnode;
domain->name = kstrdup(fwid->name, GFP_KERNEL);
if (!domain->name) {
kfree(domain);
return NULL;
}
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
break;
default:
domain->fwnode = fwnode;
domain->name = fwid->name;
break;
}
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
is_software_node(fwnode)) {
char *name;
/*
* fwnode paths contain '/', which debugfs is legitimately
* unhappy about. Replace them with ':', which does
* the trick and is not as offensive as '\'...
*/
name = kasprintf(GFP_KERNEL, "%pfw", fwnode);
if (!name) {
kfree(domain);
return NULL;
}
domain->name = strreplace(name, '/', ':');
domain->fwnode = fwnode;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
if (!domain->name) {
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
atomic_inc_return(&unknown_domains));
if (!domain->name) {
kfree(domain);
return NULL;
}
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
fwnode_handle_get(fwnode);
fwnode_dev_initialized(fwnode, true);
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->hwirq_max = hwirq_max;
if (direct_max)
domain->flags |= IRQ_DOMAIN_FLAG_NO_MAP;
domain->revmap_size = size;
/*
* Hierarchical domains use the domain lock of the root domain
* (innermost domain).
*
* For non-hierarchical domains (as for root domains), the root
* pointer is set to the domain itself so that &domain->root->mutex
* always points to the right lock.
*/
mutex_init(&domain->mutex);
domain->root = domain;
irq_domain_check_hierarchy(domain);
return domain;
}
OK,接下来下篇将详细介绍GIC中断控制器映射以及反映射。
235

被折叠的 条评论
为什么被折叠?



