arm64架构的linux中断分析(二)

3. GICv3中断控制器

GIC,Generic Interrupt Controller。是ARM公司提供的一个通用的中断控制器。主要作用为接受硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。

当前GIC 有四个版本,GIC v1~v4, 主要区别如下表:
在这里插入图片描述

gic在soc中的位置如下:
在这里插入图片描述

GICv3(Generic Interrupt Controller Version 3)是一种基于ARM Cortex-A架构的中断控制器,它提供了高度灵活和可扩展的中断架构,适用于多核系统和虚拟化环境中,它还提供对TrustZone安全性扩展的支持。
GICv3 控制器接受系统级别中断的产生,并可以发信号通知给它所连接的每个内核,从而有可能导致IRQ或FIQ异常发生。GICv3 还支持虚拟中断,允许将虚拟机中的中断映射到真实机器上,并为每个虚拟处理器提供独立的中断处理能力。

3.1 中断类型

GICv3 中,中断处理程序可以以不同的模式运行:Secure EL1、Non-secure EL1、Secure EL2 和 Non-secure EL2。这使得 GICv3 在处理不同的安全级别和特权级别时,有更强的灵活性和可定制性。
在GICv3中,中断被分为三种类型:软件生成中断(SGI)、私有中断(PPI)和共享中断(SPI)。下面对这三种中断类型进行简要的介绍:

  1. 软件生成中断(SGI):软件生成中断是处理器间中断,即从一个内核产生的中断发送到其他内核。系统中的每个核心独立于其他核心处理一个SGI。SGI的优先级和其他设置对于每个内核也是独立的。sgi是通过写入产生中断的核心的CPU接口中的系统寄存器来生成的。SGI信号是边缘触发的。每个目标核最多可以记录16个SGI,其中每个SGI在IDO-ID15范围内具有不同的INTID。ICC_ASGI1R_EL1 、ICC_SGI1R_EL1 、ICC_SGI0R_EL1

  2. 私有中断(PPI):私有外围中断标识一个中断源,例如定时器,它是内核私有的,并且独立于另一个内核的相同源。ppi通常用于与特定核心紧密耦合的外围设备。连接到与一个核心相关的PPI输入的中断只被发送到该核心。每个核心独立于其他核心处理一个PPI。PPI的设置对于每个核心也是独立的。一个PPI对一个核心是唯一的。但是,到其他核心的ppi可以具有相同的INTID。每个目标核心最多可以记录16个PPI,其中每个PPI在ID16-ID31范围内具有不同的INTID。PPI信号默认为active-LOW电平敏感。

  3. 共享中断(SPI):共享外设中断是由整个系统都可以访问的外设产生的,比如USB接收器,它可以路由到几个核心。spi通常用于与特定核心没有紧密耦合的外设。您可以对每个SPI进行编程,以针对特定核心或任何核心。激活一个核心上的SPI将激活所有核心的SPI。也就是说,最多允许一个内核激活SPI。每个SPI的设置也在所有内核之间共享。spi是通过连接输入或通过写入ACE-Lite从编程接口生成的。GIC-600可以支持多达960个spi,对应于spi Collator上的外部spi信号。可用spi的数量取决于实现的配置。允许取值为ID32ID960,步长为32。第一个SPI的ID号是32。

  4. 特定于位置的外围中断(LPI):特定于位置的外设中断(lpi)总是基于消息的,可以来自外设,也可以来自PCle根复核。一个LPI只针对一个核心。lpi是在外设写入ITS时生成的。ITS包含寄存器来控制lpi的生成和维护。ITS提供INTID转换,如果外设也存在SMMU,则允许这些外设由虚拟机直接拥有。

在这里插入图片描述
在这里插入图片描述

3.2 中断模型

GICv3包括了Distributor和Redistributor子系统。Distributor子系统接收来自外部设备的中断请求,进行中断的分类处理,再分配到不同的Redistributor进行处理。Redistributor子系统接收来自Distributor的中断请求,将其分配到不同的CPU处理器进行响应。GICv3 可以通过软件配置来实现中断控制器的定制化和协同工作。这些寄存器包括:

  1. Distributor registers:用于将中断向量路由到正确的 CPU 指令队列和 CPU 核中。也就是GICD_的系列寄存器。 作用:SPIs 启用和禁用 、设置每个SPI的优先级 、每个SPI的路由信息(发给哪个cpu) 、每个SPI设置为电平敏感或边沿触发 、产生message-signaledSPI中断。 、控制每个SPI的状态(active、pending)。 、在每一个Security state 下,配置路由模型(affinity routing or legacy) 、配置每一个SPIS的中断分组。
  2. Redistributor registers:用于将来自 Distributor 的中断路由到对应的 CPU 核。也就是GICR_的系列寄存器。 作用:启用和禁用 SGI和PPI 、设置每个SGI和PPI的优先级 、设置每个 PPI为电平敏感或边沿触发 、将每个SGI和PPI分配给一个中断组 、控制SGI和PPI的状态 、控制内存中支持 LPI相关中断属性和挂起状态的数据结构的基地址 、为连接的 PE 提供申源管理支持。
  3. CPU interface registers:用于在 CPU 上启用、禁用、优先级排序和处理中断。也就是GICC_的系列寄存器。 作用:提供通用控制和配置以启用中断处理 、Acknowledge an interrupt 、执行优先级下降和中断停用(de-active) 、为 PE 设置中断优先级掩码(即设置最低优先级) 、定义PE 的抢占策略 、确定PE的最高的priority pending interrupt。
    在这里插入图片描述

当外部设备发出中断请求时,请求被分配给Distributor子系统,由该子系统根据优先级进行分类。分类后的中断请求发送给不同的Redistributor子系统,再由Redistributor子系统将中断请求发送给对应的CPU处理器进行处理。处理器通过在向量地址映射到对应的中断服务程序,来响应中断请求。

3.3 中断状态

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.4 中断安全模式

在这里插入图片描述
结合GICD_CTLR.DS为,在GICD_IGROUPR寄存器配置分组和是否安全。
在这里插入图片描述

3.5 GICv3中断控制器代码查看

3.5.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.5.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主要做了一下几件事:

  1. 获取GICD的地址,并且进行了映射,映射地址存到dist_base
  2. 调用函数gic_validate_dist_version验证gic版本
  3. 获取GICR的地址存到rdist_regs数组中
  4. 调用函数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主要做了一下几件事:

  1. 设置全局变量gic_data,
  2. 创建并且初始化domain,
  3. 调用函数set_handle_irq设置handle_arch_irq为gic_handle_irq,handle_arch_irq就是上面异常向量表的未知函数
  4. 调用函数gic_dist_ini初始化GICD分发器,配置控制器的中断路由
  5. 调用函数gic_cpu_ini初始化GICR,配置PPI,
  6. 调用函数gic_smp_ini初始化SGI中断
  7. 调用函数 gic_cpu_pm_init初始化CPU的GIC控制器用于中断的电源管理特性
  8. 调用函数 gic_enable_nmi_suppor初始化NMI中断

到这里,这个gic控制器的初始化流程就讲完了。

控制器层:
gic_handle_irq
中间层:
handle_domain_irq
__handle_domain_irq
generic_handle_irq
generic_handle_irq_desc
设备层:
desc->handle_irq(desc)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: "Linux ARM GIC 中断流程" 是指在 ARM 架构Linux 操作系统中,使用 Generic Interrupt Controller(GIC)处理器来管理系统中各种中断的流程。它负责将来自不同设备和外部事件的中断请求,分派给适当的处理程序来处理,并确保系统按顺序执行这些处理程序。中断处理是系统中一个重要的组成部分,它允许设备和软件互相通信,并提高了系统的稳定性和可靠性。 ### 回答2: Linux是一个非常流行的开源操作系统,可以在不同架构的计算机上运行。ARM架构是一种广泛使用的嵌入式系统架构,因此,许多嵌入式设备都使用Linux作为其操作系统。在ARM架构上,通常会使用GIC(通用中断控制器)来管理中断,这个过程可以分为中断触发、CPU响应和中断处理三个部分。 中断触发是指中断信号从设备到达GIC的过程。当一个设备需要发送一个中断时,它会向GIC发送一个中断请求信号,并指定中断号,这个中断号是唯一的,用于区分不同的中断。GIC会根据中断号去查找到这个中断对应的中断控制器,进而把这个请求传递给指定的CPU。 CPU响应是指CPU接收到中断请求信号后的响应过程。当GIC把中断请求传递给CPU时,CPU需要检查是否允许这个中断请求,也就是检查中断屏蔽寄存器(Interrupt Mask Register)。如果这个中断请求已被屏蔽,则CPU不会响应,否则,它会设置自己的中断挂起寄存器(Interrupt Pending Register),告诉GIC它已经准备好去处理这个中断中断处理是指CPU执行中断处理程序,处理具体的中断。当CPU设置了它的中断挂起寄存器后,GIC会向CPU发送一个中断信号。CPU会暂停当前的进程,并把当前的上下文信息(比如,寄存器值)保存到内存中。之后,CPU会跳转到中断处理程序(Interrupt Service Routine),开始执行具体的中断处理代码。中断处理程序完成后,CPU会从内存中恢复之前保存的上下文信息,并恢复之前进程的执行。 总的来说,ARM架构上的Linux操作系统通常使用GIC来管理中断,其中包括中断触发、CPU响应和中断处理三个方面。这个流程对于保证系统的稳定性和快速响应非常重要。 ### 回答3: 在ARM架构Linux系统中,GIC(Generic Interrupt Controller)被用来管理中断。当发生中断时,GIC会将中断信号发送到CPU,然后CPU会停止当前的进程并处理该中断。 GIC的中断流程如下: 1. 报告中断:设备或其他外部事件引发中断信号,设备向GIC发送中断信号,GIC会产生一个中断源标识符,然后将其发给CPU。 2. 响应中断:CPU根据中断源标识符查询GIC,查看中断请求的优先级和处理器状态,如果中断请求的优先级高于当前中断处理器,那么CPU会中止当前进程,执行中断处理程序。 3. 中断处理:中断处理程序会读取设备状态,进行相应的操作,处理完成后会发出一个中断信号,通知GIC中断已被处理。 4. 中断结束:GIC收到来自设备的中断完成信号后,将中断源标识符置为未激活状态。 在这个流程中,GIC起到了一种路由的作用,将中断信号从设备传输到CPU,同时根据中断请求的优先级来优先处理高优先级的中断。这样就可以保证系统的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值