文章目录
- 3. GICv3中断控制器
- 3.1 GICv3中断控制器设备树
- 3.2 GICv3中断控制器驱动
3. GICv3中断控制器
gic在soc中的位置如下:
GICv3(Generic Interrupt Controller Version 3)是一种基于ARM Cortex-A架构的中断控制器,它提供了高度灵活和可扩展的中断架构,适用于多核系统和虚拟化环境中,它还提供对TrustZone安全性扩展的支持。
GICv3 控制器接受系统级别中断的产生,并可以发信号通知给它所连接的每个内核,从而有可能导致IRQ或FIQ异常发生。GICv3 还支持虚拟中断,允许将虚拟机中的中断映射到真实机器上,并为每个虚拟处理器提供独立的中断处理能力。
GICv3 中,中断处理程序可以以不同的模式运行:Secure EL1、Non-secure EL1、Secure EL2 和 Non-secure EL2。这使得 GICv3 在处理不同的安全级别和特权级别时,有更强的灵活性和可定制性。
在GICv3中,中断被分为三种类型:软件生成中断(SGI)、私有中断(PPI)和共享中断(SPI)。下面对这三种中断类型进行简要的介绍:
-
软件生成中断(SGI):SGI是由处理器上的软件或操作系统生成的中断,用于在特定的CPU上触发中断。它们是非可屏蔽中断(NMI),具有固定的优先级和中断号。在ARMv8-A体系结构中,每个处理器都支持4个SGI中断线,其中SGI 0用于调试控制,其他SGI可以用于特定的事件处理。
-
私有中断(PPI):PPI是由处理器上的硬件设备或系统外设生成的中断。PPI是由每个处理器的本地中断控制器(LPI)处理并响应的,因此不会发送到网络中的其他处理器。PPI的中断号和优先级是固定的,但不保证在所有处理器上相同,因为它们由LPI分配。
-
共享中断(SPI):SPI是由GICv3的分发器(Distributor)处理分配给多个处理器的中断。SPI可以由设备连接到任意处理器中的任意一个或多个中断信号线,因此在多核系统中,分配给不同处理器的SPI可以在生成它的设备上具有不同的优先级和中断号。
GICv3包括了Distributor和Redistributor子系统。Distributor子系统接收来自外部设备的中断请求,进行中断的分类处理,再分配到不同的Redistributor进行处理。Redistributor子系统接收来自Distributor的中断请求,将其分配到不同的CPU处理器进行响应。GICv3 可以通过软件配置来实现中断控制器的定制化和协同工作。这些寄存器包括:
- Distributor registers:用于将中断向量路由到正确的 CPU 指令队列和 CPU 核中。也就是GICD_的系列寄存器。
- Redistributor registers:用于将来自 Distributor 的中断路由到对应的 CPU 核。也就是GICR_的系列寄存器。
- CPU interface registers:用于在 CPU 上启用、禁用、优先级排序和处理中断。也就是GICC_的系列寄存器。
当外部设备发出中断请求时,请求被分配给Distributor子系统,由该子系统根据优先级进行分类。分类后的中断请求发送给不同的Redistributor子系统,再由Redistributor子系统将中断请求发送给对应的CPU处理器进行处理。处理器通过在向量地址映射到对应的中断服务程序,来响应中断请求。
GICv3控制器会记录中断的状态,中断可以处于多种不同状态:
① 非活动状态(Inactive)–这意味着该中断未触发。
② 挂起(Pending)–这意味着中断源已被触发,但正在等待CPU核处理。待处理的中断要通过转发到CPU接口单元,然后再由CPU接口单元转发到内核。
③ 活动(Active)–描述了一个已被内核接收并正在处理的中断。
④ 活动和挂起(Active and pending)–描述了一种情况,其中CPU核正在为中断服务,而GIC又收到来自同一源的中断。
中断的优先级和可接收中断的核都在分发器(distributor)中配置。外设发给分发器的中断将标记为pending状态(或Active and Pending状态,如触发时果状态是active)。distributor确定可以传递给CPU核的优先级最高的pending中断,并将其转发给内核的CPU interface。通过CPU interface,该中断又向CPU核发出信号,此时CPU核将触发FIQ或IRQ异常。
作为响应,CPU核执行异常处理程序。异常处理程序必须从CPU interface寄存器查询中断ID,并开始为中断源提供服务。完成后,处理程序必须写入CPU interface寄存器以报告处理结束。然后CPU interface准备转发distributor发给它的下一个中断。在处理中断时,中断的状态开始为pending,active,结束时变成inactive。中断状态保存在distributor寄存器中。
3.1 GICv3中断控制器设备树
gic: interrupt-controller@30800000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0x30800000 0 0x20000>, /* GICD */
<0x0 0x30880000 0 0x80000>, /* GICR */
<0x0 0x30840000 0 0x10000>, /* GICC */
<0x0 0x30850000 0 0x10000>, /* GICH */
<0x0 0x30860000 0 0x10000>; /* GICV */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_LOW>;
its: gic-its@30820000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0x30820000 0x0 0x20000>;
};
};
这是飞腾的gic的设备树,跟瑞芯微的差不多,不过瑞芯微的的reg只有GICD和GICR,虽然飞腾的设备树多了这个多寄存器,实际上驱动也是没有去读取那些寄存器的。its的我们不用看,这是PCIE才用到的。
3.2 GICv3中断控制器驱动
驱动代码在drivers/irqchip/irq-gic-v3.c文件中,首先我们看看compatible 是怎么匹配的:
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
展开后为:
static const struct of_device_id __of_table_gic_v3 \
__used __section(__irqchip_of_table) \
= { .compatible = "arm,gic-v3", \
.data = gic_of_init }
那么gic_of_init 函数是怎么调用到的呢?其调用路径如下:
start_kernel (init\main.c) → init_IRQ (arch\arm64\kernel\irq.c) → irqchip_init (drivers\irqchip\irqchip.c) → of_irq_init(__irqchip_of_table);(drivers\of\irq.c) → desc->irq_init_cb = match->data;和 ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);
前面的调用路径都好理解,就是start_kernel一直调用到 of_irq_init这个函数,我们的宏申明了__irqchip_of_table这个段,所以在 of_irq_init这个函数里面会遍历整个设备树,找到所有compatible匹配的节点,并且判断是否为interrupt-controller,是否有data成员,如果都有,则把date成员赋值给desc->irq_init_cb,最后调用desc->irq_init_cb。这就会让"arm,gic-v3"的date成员gic_of_init函数调用起来,精简过的of_irq_init函数如下:
void __init of_irq_init(const struct of_device_id *matches)
{
...
for_each_matching_node_and_match(np, matches, &match) {
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np))
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
...
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
desc->irq_init_cb = match->data;
...
}
while (!list_empty(&intc_desc_list)) {
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);
...
}
}
}
现在我们看看gic_of_init函数主要是获取设备树中的信息,然后初始化gic控制器:
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *dist_base;
struct redist_region *rdist_regs;
u64 redist_stride;
u32 nr_redist_regions;
int err, i;
dist_base = of_iomap(node, 0);//获取GICD的地址,并且进行了映射
if (!dist_base) {
pr_err("%pOF: unable to map gic dist registers\n", node);
return -ENXIO;
}
err = gic_validate_dist_version(dist_base);//验证gic版本
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;
}
//redistributor-regions不存在,nr_redist_regions值为1,获取GICR的地址
for (i = 0; i < nr_redist_regions; i++) {
struct resource res;
int ret;
ret = of_address_to_resource(node, 1 + i, &res);
rdist_regs[i].redist_base = of_iomap(node, 1 + i);
if (ret || !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);
//初始化GIC控制器,设置GICD等寄存器来配置NMI、SGI、PPI和SPI中断
err = gic_init_bases(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)
iounmap(rdist_regs[i].redist_base);
kfree(rdist_regs);
out_unmap_dist:
iounmap(dist_base);
return err;
}
gic_of_init主要做了一下几件事:
- 获取GICD的地址,并且进行了映射,映射地址存到dist_base
- 调用函数gic_validate_dist_version验证gic版本
- 获取GICR的地址存到rdist_regs数组中
- 调用函数gic_init_bases初始化GIC控制器,设置GICD等寄存器来配置NMI、SGI、PPI和SPI中断
我们再看看gic_init_bases函数:
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
u32 typer;
int err;
if (!is_hyp_mode_available())
static_branch_disable(&supports_deactivate_key);
if (static_branch_likely(&supports_deactivate_key))
pr_info("GIC: Using split EOI/Deactivate mode\n");
//设置全局变量gic_data
gic_data.fwnode = handle;
gic_data.dist_base = dist_base;
gic_data.redist_regions = rdist_regs;
gic_data.nr_redist_regions = nr_redist_regions;
gic_data.redist_stride = redist_stride;
/*
* Find out how many interrupts are supported.
*/
typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
gic_data.rdists.gicd_typer = typer;
gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR),
gic_quirks, &gic_data);
pr_info("%d SPIs implemented\n", GIC_LINE_NR - 32);
pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR);
/*
* ThunderX1 explodes on reading GICD_TYPER2, in violation of the
* architecture spec (which says that reserved registers are RES0).
*/
if (!(gic_data.flags & FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539))
gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2);
//创建并且初始化domain,最重要的是gic_irq_domain_ops方法
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
gic_data.rdists.has_rvpeid = true;
gic_data.rdists.has_vlpis = true;
gic_data.rdists.has_direct_lpi = true;
gic_data.rdists.has_vpend_valid_dirty = true;
if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
err = -ENOMEM;
goto out_free;
}
irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
pr_info("Distributor has %sRange Selector support\n",
gic_data.has_rss ? "" : "no ");
if (typer & GICD_TYPER_MBIS) {
err = mbi_init(handle, gic_data.domain);
if (err)
pr_err("Failed to initialize MBIs\n");
}
//设置handle_arch_irq为gic_handle_irq,cpu触发irq就会跑到gic_handle_irq
set_handle_irq(gic_handle_irq);
gic_update_rdist_properties();
gic_dist_init();//初始化GICD分发器,配置控制器的中断路由
gic_cpu_init();//初始化GICR,配置PPI,
gic_smp_init();//初始化SGI中断
gic_cpu_pm_init();//初始化CPU的GIC控制器用于中断的电源管理特性
if (gic_dist_supports_lpis()) {
its_init(handle, &gic_data.rdists, gic_data.domain);
its_cpu_init();
} else {
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(handle, gic_data.domain);
}
gic_enable_nmi_support();//初始化NMI中断
return 0;
out_free:
if (gic_data.domain)
irq_domain_remove(gic_data.domain);
free_percpu(gic_data.rdists.rdist);
return err;
}
gic_init_bases主要做了一下几件事:
- 设置全局变量gic_data,
- 创建并且初始化domain,
- 调用函数set_handle_irq设置handle_arch_irq为gic_handle_irq,handle_arch_irq就是上面异常向量表的未知函数
- 调用函数gic_dist_ini初始化GICD分发器,配置控制器的中断路由
- 调用函数gic_cpu_ini初始化GICR,配置PPI,
- 调用函数gic_smp_ini初始化SGI中断
- 调用函数 gic_cpu_pm_init初始化CPU的GIC控制器用于中断的电源管理特性
- 调用函数 gic_enable_nmi_suppor初始化NMI中断
到这里,这个gic控制器的初始化流程就讲完了。