1.ARM GIC V3中断控制器介绍

GIC(Generic Interrupt Controller)是一个通用的中断控制器,用来接收硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。GIC V3是其中一个版本,支持的中断类型如下表:

中断类型

中断号描述
SGI (Software Generated Interrupt)0-15

软件触发中断,通常用于多核之间通讯,在Linux内核中通常被用作IPI(inter

-process interrupts)中断,并送达到系统指定的CPU,最多支持16个SGI中断,中断号0-15

PPI (Private Peripheral Interrupt)16-31每个处理器的私有外设中断,最多支持16个PPI中断,中断号16-31,PPI通常会送达到指定的CPU上
SPI (Shared Peripheral Interrupt)32-1019系统共享的外设中断

LPI (Locality-specific Peripheral

Interrupt)

8192-MAXLPI是基于消息的中断,它们的配置保存在表中而不是寄存器

GIC V3中断控制器的组成部分包括:distributor,redistributor,cpu interface,ITS,GIC V3中断控制器和处理器核心之间的关系图如下:

SPI 中断检测流程

  • 外设发起SPI中断,GIC检测到这个中断,并标记为pending状态
  • distributor对所有处于pending状态的中断确定目标CPU
  • 对每个CPU,distributor会从众多pending状态的中断中,选择优先级高的发送到对应的redistributor
  • redistributor将中断发送到目标CPU的CPU Interface上
  • CPU Interface将满足要求的中断发送给CPU
  • CPU进入中断异常后,内核中断处理程序读取GICC_IAR寄存器来响应该中断,寄存器返回硬件中断号
  • CPU处理完中断服务后,通过写GICC_EOIR寄存器,来给GIC发送一个EOI完成信号

PPI和SGI中断检测流程

  • GIC检测到PPI或者SGI中断,并标记为pending状态
  • redistributor选择优先级高的中断发送到对应的CPU Interface
  • CPU Interface将满足要求的中断发送给CPU
  • CPU进入中断异常后,内核中断处理程序读取GICC_IAR寄存器来响应该中断,寄存器返回硬件中断号
  • CPU处理完中断服务后,通过写GICC_EOIR寄存器,来给GIC发送一个EOI完成信号

LPI中断检测流程

1.forwarding方式,由以下寄存器实现

  • GICR_SERLPIR:将指定的LPI中断,设置为pending状态
  • GICR_INVLPIR:将指定的LPI中断,清除pending状态。寄存器内容和GICR_SERLPIR一致
  • GICR_INVLPIR:将缓存中,指定LPI的缓存给无效掉,使GIC重新从memory中载入LPI的配置
  • GICR_INVALLR:将缓存中,所有LPI的缓存给无效掉,使GIC重新从memory中,载入LPI中断的配
  • GICR_SYNCR:对redistributor的操作是否完成

2.使用ITS方式

  • 外设写GITS_TRANSLATER寄存器,给ITS提供中断事件类型EventID和发起中断的外设的DeviceID
  • ITS用DeviceID,通过查设备表,得到中断映射表的位置
  • ITS用EventID,通过查中断映射表,得到LPI中断号,以及中断所属的collection号
  • 使用collection号,从collection表格中redistributor的映射信息
  • ITS将中断信息发送给对应的redistributor
  • redistributor将该中断信息发送给CPU Interface
  • CPU Interface将满足要求的中断发送给CPU

2 ARM Linux系统中GIC V3初始化

以下所有内核源码来自Linux5.5.8

2.1 Linux系统中 gic-v3 DTS定义和设备节点

     Device Tree是用来描述硬件的数据结构,DTS即Device Tree Source 设备树源码,Linux源码中DTS对应的文件名后缀为.dts。 DTC是将.dts编译为.dtb的工具,.dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备。

 2.1.1 Linux系统中 gic-v3 DTS定义

	gic: interrupt-controller@2c010000 {
		compatible = "arm,gic-v3";
		#interrupt-cells = <3>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		interrupt-controller;
		redistributor-stride = <0x0 0x40000>;	// 256kB stride
		#redistributor-regions = <2>;
		reg = <0x0 0x2c010000 0 0x10000>,	// GICD
		      <0x0 0x2d000000 0 0x800000>,	// GICR 1: CPUs 0-31
		      <0x0 0x2e000000 0 0x800000>;	// GICR 2: CPUs 32-63
		      <0x0 0x2c040000 0 0x2000>,	// GICC
		      <0x0 0x2c060000 0 0x2000>,	// GICH
		      <0x0 0x2c080000 0 0x2000>;	// GICV
		interrupts = <1 9 4>;

		gic-its@2c200000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			#msi-cells = <1>;
			reg = <0x0 0x2c200000 0 0x200000>;
		};

		gic-its@2c400000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			#msi-cells = <1>;
			reg = <0x0 0x2c400000 0 0x200000>;
		};
	};

1) gic-v3设备节点中必须的属性:

  • interrupt-controller:该设备节点为中断控制器
  • compatible:必须包含字符串"arm,gic-v3",表示该设备为中断控制器arm gic-v3
  • #interrupt-cells :指定编码中断源所需的单元格数目,必须至少3个单元格,单元格的详细描述如下表
   单元格                                     描述
        1第一个单元格中的是中断类型:0 SPI,1 PPI,其他为保留值
        2第二个单元格中的值是中断号
        3第三个单元格中的值是一些中断标志,1为边沿触发,4为电平触发
  • reg:指定了GIC寄存器的物理基地址,这些寄存器包括:Distributor(GICD),Redistributors (GICR),CPU interface (GICC),Hypervisor interface (GICH),Virtual CPU interface (GICV),其中 GICC, GICH ,GICV 是可选的。
  • interrupts:中断源

2) gic-v3设备节点中可选的属性:

  • redistributor-stride:指定redistributor的步长,必须是64KB的倍数
  • #redistributor-regions:如果系统中有多个redistributor时,需要这个属性来描述多个redistributor区域

3) gic-v3设备节点中的子设备节点ITS(Interrupt Translation Services):一个GIC中有1到多个ITS设备,ITS设备用于将消息信号中断(MSI)路由到cpu,ITS设备节点的描述如下:

  • compatible:必须包含字符串"arm,gic-v3-its",表示这是一个ITS设备
  • msi-controller:标识该设备节点为MSI控制器
  • #msi-cells:必须是1,这里面是MSI设备的DeviceID
  • reg:ITS寄存器的物理基地址

2.1.2 Linux系统中 gic-v3 设备节点

      内核在创建设备节点的时候的主要数据结构为struct device_node,该结构体通过解析.dtb得到,以下是struct device_node的源码和注释:

struct property {
	/*该property的名称*/
	char	*name;
	/*该property的长度*/
	int	length;
	/*该property的值*/
	void	*value;
	/*和该property相连接的下一个property*/
	struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
	unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
	unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
	struct bin_attribute attr;
#endif
};

#if defined(CONFIG_SPARC)
struct of_irq_controller;
#endif

struct device_node {
	/*设备节点名称*/
	const char *name;
	phandle phandle;
	/*设备节点名称和地址*/
	const char *full_name;
	/*操作该设备节点的一些回调函数*/
	struct fwnode_handle fwnode;
    /*节点中的每一组数据( 比如compatible = "arm,cortex-a9-gic")通过结构体property表示,
     * property->next指向另外一组数据。
     */
	struct	property *properties;
	/*节点中被移除的property*/
	struct	property *deadprops;	/* removed properties */
	/*该设备结点的父设备节点*/
	struct	device_node *parent;
	/*该设备结点的子设备节点*/
	struct	device_node *child;
	/*该设备结点的兄弟设备节点*/
	struct	device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
	struct	kobject kobj;
#endif
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

系统解析2.1.1中的 gic-v3 DTS后得到的struct device_node如下:

  • device_node->name = "interrupt-controller"
  • device_node->full_name = "interrupt-controller@2c010000"
  • device_node->properties->name = "compatible"
  • device_node->properties->value = "arm,gic-v3"
  • device_node->properties->length = 10 (字符串"arm,gic-v3"的长度)
  • device_node->properties->next->name = "redistributor-stride"
  • device_node->properties->next->value = {0x0,0x0,0x0,0x0, 0x0,0x04,0x0,0x0}
  • device_node->properties->next->length = 4(一个字段占4个字节)
  • device_node->properties->next->next->name = "reg"

                                 ......

  • device_node->child->name = "gic-its"
  • device_node->child->full_name = "gic-itss@2c200000"

                                 ......

  • device_node->child->next->name = "gic-its"
  • device_node->child->next->full_name = "gic-itss@2c400000"

                                 ......

2.2 Linux系统中gic-v3初始化

2.2.1 struct of_device_id结构体

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

/*
 * This special of_device_id is the sentinel at the end of the
 * of_device_id[] array of all irqchips. It is automatically placed at
 * the end of the array by the linker, thanks to being part of a
 * special section.
 */
static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_table_end);

extern struct of_device_id __irqchip_of_table[];

      1) struct of_device_id结构体

  • name:设备名称
  • type:设备类型
  • compatible:对应DTS文件中设备结点的compatible,用来匹配适合的device node
  • data:对于GIC来说,data字段为初始化GIC的函数地址

      2) __irqchip_of_table:一个系统全局数组,数组中的每个对象是一个全局struct of_device_id静态常量,IRQCHIP_DECLARE宏用于初始化一个struct of_device_id的静态常量,并放置在__irqchip_of_table中。

2.2.2 宏IRQCHIP_DECLARE

/*
 * This macro must be used by the different irqchip drivers to declare
 * the association between their DT compatible string and their
 * initialization function.
 *
 * @name: name that must be unique across all IRQCHIP_DECLARE of the
 * same file.
 * @compstr: compatible string of the irqchip driver
 * @fn: initialization function
 */
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

#define OF_DECLARE_2(table, name, compat, fn) \
		_OF_DECLARE(table, name, compat, fn, of_init_fn_2)

#define _OF_DECLARE(table, name, compat, fn, fn_type)			\
	static const struct of_device_id __of_table_##name		\
		__used __section(__##table##_of_table)			\
		 = { .compatible = compat,				\
		     .data = (fn == (fn_type)NULL) ? fn : fn  }

以上是linux系统中宏IRQCHIP_DECLARE的定义,这个宏初始化了一个struct of_device_id的静态常量,并放置在__irqchip_of_table中。

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);

如上代码所示,IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init)声明了一个__of_table_gic_v3的struct of_device_id结构体,放在__irqchip_of_table中。__of_table_gic_v3.compatible为"arm,gic-v3",__of_table_gic_v3.data指向函数gic_of_init()的地址。

2.2.3 gic_of_init()函数分析

Linux系统启动后初始化GIC V3的函数调用顺序为init_IRQ()->irqchip_init()->of_irq_init()->gic_of_init()。以下是rqchip_init(),of_irq_init()函数相关代码及其注释:

extern struct of_device_id __irqchip_of_table[];

void __init irqchip_init(void)
{
	of_irq_init(__irqchip_of_table);
	acpi_probe_device_table(irqchip);
}

/**
 * of_irq_init - 在DT中扫描并初始化匹配的中断控制器
 * @matches: 用于扫描并匹配的中断控制器的所有设备节点的一个数据,即__irqchip_of_table
 *
 * 该函数扫描设备树以查找匹配的中断控制器节点,并执行它们的初始化函数。
 */
void __init of_irq_init(const struct of_device_id *matches)
{
	const struct of_device_id *match;
	struct device_node *np, *parent = NULL;
	struct of_intc_desc *desc, *temp_desc;
	struct list_head intc_desc_list, intc_parent_list;

	INIT_LIST_HEAD(&intc_desc_list);
	INIT_LIST_HEAD(&intc_parent_list);


    /*遍历__irqchip_of_table得到的中断控制器设备信息,并为每个中断控
     *制器设备分配一个struct of_intc_desc 结构体
     */
	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;

		/*
		 * Here, we allocate and populate an of_intc_desc with the node
		 * pointer, interrupt-parent device_node etc.
		 */
		desc = kzalloc(sizeof(*desc), GFP_KERNEL);
		if (!desc) {
			of_node_put(np);
			goto err;
		}
		
       /* match->data 中的内容是gic_of_init()函数地址,
        * 这里设置of_intc_desc的irq_init_cb 回调函数为gic_of_init()
        */
		desc->irq_init_cb = match->data;
		desc->dev = of_node_get(np);
		desc->interrupt_parent = of_irq_find_parent(np);
		if (desc->interrupt_parent == np)
			desc->interrupt_parent = NULL;
		 
		/*将of_intc_desc放到intc_desc_list链表中*/
		list_add_tail(&desc->list, &intc_desc_list);
	}

	/*
	 * The root irq controller is the one without an interrupt-parent.
	 * That one goes first, followed by the controllers that reference it,
	 * followed by the ones that reference the 2nd level controllers, etc.
	 */
	while (!list_empty(&intc_desc_list)) {
		/*
		 * Process all controllers with the current 'parent'.
		 * First pass will be looking for NULL as the parent.
		 * The assumption is that NULL parent means a root controller.
		 */
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			int ret;

			if (desc->interrupt_parent != parent)
				continue;

			list_del(&desc->list);

			of_node_set_flag(desc->dev, OF_POPULATED);

			pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
				 desc->dev,
				 desc->dev, desc->interrupt_parent);
			/*执行初始化中断控制器设备的回调函数gic_of_init()*/
			ret = desc->irq_init_cb(desc->dev,
						desc->interrupt_parent);
			if (ret) {
				of_node_clear_flag(desc->dev, OF_POPULATED);
				kfree(desc);
				continue;
			}

			/*
			 * This one is now set up; add it to the parent list so
			 * its children can get processed in a subsequent pass.
			 */
			list_add_tail(&desc->list, &intc_parent_list);
		}

		/* Get the next pending parent that might have children */
		desc = list_first_entry_or_null(&intc_parent_list,
						typeof(*desc), list);
		if (!desc) {
			pr_err("of_irq_init: children remain, but no parents\n");
			break;
		}
		list_del(&desc->list);
		parent = desc->dev;
		kfree(desc);
	}

	list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
err:
	list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
		list_del(&desc->list);
		of_node_put(desc->dev);
		kfree(desc);
	}
}

以下是gic_of_init()函数的代码和注释,gic_of_init()中调用gic_init_bases()来处理GIC V3初始化的核心工作:

struct redist_region {
	/*指向Redistributor域内存区间的地址*/
	void __iomem		*redist_base;
	/*Redistributor域物理地址*/
	phys_addr_t		phys_base;
	/*是否只有一个Redistributor域*/
	bool			single_redist;
};
/*GIC V3硬件设备相关的数据结构*/
struct gic_chip_data {
	/*操作该设备的一些回调方法*/
	struct fwnode_handle	*fwnode;
	/*指向Distributor内存区间的地址*/
	void __iomem		*dist_base;
	/*该gic中所有Redistributor域的信息*/
	struct redist_region	*redist_regions;
	/*该gic中所有Redistributor的信息*/
	struct rdists		rdists;
	/*该GIC对应的irq_domain,Linux系统中每个GIC对应一个irq_domain,
	 * 用于解析硬件中断号
	 */
	struct irq_domain	*domain;
	/*Redistributor域之间的步长,64KB的倍数*/
	u64			redist_stride;
	/*Redistributor域个数*/
	u32			nr_redist_regions;
	u64			flags;
	bool			has_rss;
	unsigned int		ppi_nr;
	struct partition_desc	**ppi_descs;
};

static struct gic_chip_data gic_data __read_mostly;

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;
    
	/*为node映射内存区间,这里返回的dist_base是Distributor的地址*/
	dist_base = of_iomap(node, 0);
	if (!dist_base) {
		pr_err("%pOF: unable to map gic dist registers\n", node);
		return -ENXIO;
	}
    /*检查GIC硬件版本是否是V3或者V4*/
	err = gic_validate_dist_version(dist_base);
	if (err) {
		pr_err("%pOF: no distributor detected, giving up\n", node);
		goto out_unmap_dist;
	}
    /*读取该设备节点中redistributor个数,如果该设备DT中没有"#redistributor-regions"字段,
     * 那就只有一个redistributor
     */
	if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
		nr_redist_regions = 1;
	
    /*根据redistributor个数分配redistributor数组*/
	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++) {
		struct resource res;
		int ret;
		
		ret = of_address_to_resource(node, 1 + i, &res);
		/*已分配的每个redistributor区域映射内存区间*/
		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;
	}
	 /*读取该设备节点中每个redistributor区域之间的步长,如果该设备DT中
	  * 没有"#redistributor-stride"字段,redist_stride大小为0
	  */
	if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
		redist_stride = 0;

	gic_enable_of_quirks(node, gic_quirks, &gic_data);
    /*开始正式初始化GIC V3设备*/
	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;
}

/*@dist_base:Distributor的地址,
 *@rdist_regs:GIC设备中所有Redistributor的地址信息
 *@nr_redist_regions:Redistributor的个数
 *@redist_stride:Redistributor区域之间的步长,
 *@handle:是该设备节点对应的数据结构struct device_node中的fwnode,
 *         指向与irq_domain关联的固件节点的指针
 */
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);

	/*在系统中为GIC V3注册一个irq domain的数据结构. irq_domain主要作用是映射硬件中断号*/
	gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
						 &gic_data);
	/*一个设备节点中可能有多个irq_domain,这些irq_domain用于不同的目的,DOMAIN_BUS_WIRED
	 * 表示该irq_domain用于wired IRQS
	 */
	irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
	gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
	gic_data.rdists.has_vlpis = true;
	gic_data.rdists.has_direct_lpi = true;

	if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
		err = -ENOMEM;
		goto out_free;
	}

	/*rss表示SGI中断亲和性的范围,GICD_TYPER_RSS值为GICD_TYPER寄存器的bit[26],
	 *  0表示中断路由(IRI) 支持affinity 0-15的SGI
	 *  1表示支持affinity 0 - 255的SGI
	 */
	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");
	}
    /* 设置中断回调函数gic_handle_irq,当发生中断时,
     * 系统会执行gic_handle_irq来处理中断
     */
	set_handle_irq(gic_handle_irq);
    /*更新Redistributor相关的属性*/
	gic_update_rdist_properties();
	/*1.设置核间通信,2.设置CPU启动时动态注册gic的回调函数gic_cpu_init*/
	gic_smp_init();
	/*初始化Distributor*/
	gic_dist_init();
	/*初始化CPU Interface*/
	gic_cpu_init();
	/*初始化GIC电源管理*/
	gic_cpu_pm_init();

	if (gic_dist_supports_lpis()) {
		/*初始化ITS*/
		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();

	return 0;

out_free:
	if (gic_data.domain)
		irq_domain_remove(gic_data.domain);
	free_percpu(gic_data.rdists.rdist);
	return err;
}err;
}

 

 

Logo

更多推荐