• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

arm64架构的linux断二

武飞扬头像
小坚学Linux
帮助1

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)。下面对这三种中断类型进行简要的介绍:

  1. 软件生成中断(SGI):SGI是由处理器上的软件或操作系统生成的中断,用于在特定的CPU上触发中断。它们是非可屏蔽中断(NMI),具有固定的优先级和中断号。在ARMv8-A体系结构中,每个处理器都支持4个SGI中断线,其中SGI 0用于调试控制,其他SGI可以用于特定的事件处理。

  2. 私有中断(PPI):PPI是由处理器上的硬件设备或系统外设生成的中断。PPI是由每个处理器的本地中断控制器(LPI)处理并响应的,因此不会发送到网络中的其他处理器。PPI的中断号和优先级是固定的,但不保证在所有处理器上相同,因为它们由LPI分配。

  3. 共享中断(SPI):SPI是由GICv3的分发器(Distributor)处理分配给多个处理器的中断。SPI可以由设备连接到任意处理器中的任意一个或多个中断信号线,因此在多核系统中,分配给不同处理器的SPI可以在生成它的设备上具有不同的优先级和中断号。

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

  1. Distributor registers:用于将中断向量路由到正确的 CPU 指令队列和 CPU 核中。也就是GICD_的系列寄存器。
  2. Redistributor registers:用于将来自 Distributor 的中断路由到对应的 CPU 核。也就是GICR_的系列寄存器。
  3. 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主要做了一下几件事:

  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控制器的初始化流程就讲完了。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgekcha
系列文章
更多 icon
同类精品
更多 icon
继续加载