Linux: 中断实现简析

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文分析基于 linux-4.14.132 内核、ARM32 架构代码进行分析。

3. 实现

本篇以 ARM 架构的 GIC 芯片 为例进行分析。

3.1 硬件基础

使用 GIC 芯片的 SoC,典型的中断硬件拓扑结构如下:
在这里插入图片描述
其中:

SGI(Software-generated interrupt):用于处理期之间的通信,编号区间为 0~15 ,对于每个处理器,都是相同的编号区间;
PPI(Private peripheral interrupt):处理器私有中断,编号区间为 16~31 ,处理器私有编号,每个处理器都是相同的编号区间;
SPI(Shared peripheral interrupt):全局中断,可发送给任一处理器,编号区间为 32~1019 ,且编号全局唯一。

本文不讲述更多中断相关的硬件知识,读者可自行请参考手册

《ARM Architecture Reference Manual.pdf》
《DDI0406C_d_armv7ar_arm.pdf》
《IHI0048A_gic_architecture_spec_v1_0.pdf》

的相关章节。

3.2 中断初始化

3.2.1 早期初始化

start_kernel()
	local_irq_disable(); /* 禁用 BOOT CPU 上的中断 */
	early_boot_irqs_disabled = true;

	setup_arch()
		/* 中断向量表设置 */
		paging_init(mdesc)
			devicemaps_init(mdesc)
				void *vectors;
				
				vectors = early_alloc(PAGE_SIZE * 2);
				early_trap_init(vectors)
					vectors_page = vectors_base;

					for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
						((u32 *)vectors_base)[i] = 0xe7fddef1;
							
					memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
					memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
				
				map.pfn = __phys_to_pfn(virt_to_phys(vectors));
				map.virtual = 0xffff0000;
				map.length = PAGE_SIZE;
			#ifdef CONFIG_KUSER_HELPERS
				map.type = MT_HIGH_VECTORS;
			#else
				map.type = MT_LOW_VECTORS;
			#endif
				create_mapping(&map);
					
				if (!vectors_high()) {
					map.virtual = 0;
					map.length = PAGE_SIZE * 2;
					map.type = MT_LOW_VECTORS;
					create_mapping(&map);
				}

				/* Now create a kernel read-only mapping */
				map.pfn += 1;
				map.virtual = 0xffff0000 + PAGE_SIZE;
				map.length = PAGE_SIZE;
				map.type = MT_LOW_VECTORS;
				create_mapping(&map);
					
				early_abt_enable() @ arch/arm/mm/fault.c
					fsr_info[FSR_FS_AEA].fn = early_abort_handler;
					local_abt_enable();
					fsr_info[FSR_FS_AEA].fn = do_bad;
		/* 初始中断处理接口为 machine 定义的函数 */
		handle_arch_irq = mdesc->handle_irq;
	
	trap_init() // ARM32 为空操作

	early_irq_init()
		/*
		 * 初始化所有中断的默认 CPU 亲和性: 
		 * 如果没有通过内核命令行参数 irqaffinity= 设置默认的中断 CPU 亲和性,
		 * 则将中断默认的 CPU 亲和性设置为系统中所有 CPU 。
		 */
		init_irq_default_affinity()
			if (!cpumask_available(irq_default_affinity))
				zalloc_cpumask_var(&irq_default_affinity, GFP_NOWAIT);
			if (cpumask_empty(irq_default_affinity))
				cpumask_setall(irq_default_affinity);
		/* 探测架构预分配占用的 IRQ 中断数目: ARM32 下配置为 16 个 */
		initcnt = arch_probe_nr_irqs();
		/* 为探测到的、架构预分配占用的 IRQ 中断,分配中断描述符 */
		for (i = 0; i < initcnt; i++) {
			desc = alloc_desc(i, node, 0, NULL, NULL);
			set_bit(i, allocated_irqs);
			irq_insert_desc(i, desc);
				radix_tree_insert(&irq_desc_tree, irq, desc);
		}
		arch_early_irq_init() // ARM32 为空操作

3.2.2 GIC芯片初始化

3.2.2.1 BOOT CPU 对 GIC 的初始化

上接 3.2.1 章节的分析:

start_kernel()
	...
	init_IRQ()
void __init init_IRQ(void)
{
	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		irqchip_init();
	else
		machine_desc->init_irq();
	...
}

void __init irqchip_init(void)
{
	/*
	 * 表 __irqchip_of_table[] 内包含由 IRQCHIP_DECLARE() 声明的
	 * 中断控制器驱动列表(由 vmlinux.lds.h 中的 IRQCHIP_OF_MATCH_TABLE() 组织到一起),
	 * 而 DTS 声明中断控制器驱动实例。
	 * of_irq_init() 匹配中断控制器驱动到对应的DTS中的实例,并调用中断控制器
	 * 驱动的初始化接口,初始化中断控制器。
	 */
	of_irq_init(__irqchip_of_table);
	...
}

/*
 * 扫描 DT 匹配 @matches 的中断控制器,并初始化它们。
 *
 * DTS 声明的中断控制器:
 * gic: interrupt-controller@01c81000 {
 * 		compatible = "arm,gic-400";
 *      reg = <0x01c81000 0x1000>,
 *            <0x01c82000 0x2000>,
 *            <0x01c84000 0x2000>,
 *            <0x01c86000 0x2000>;
 *      interrupt-controller;
 *      #interrupt-cells = <3>;
 *      interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
 * };
 *
 * 匹配的中断控制器驱动声明:@drivers/irqchip/irq-gic.c
 * IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init); // AllWinner H3
 * ==>
 * static const struct of_device_id __of_table_gic_400
 * 		__used __section(__irqchip_of_table)
 * = { .compatible = "arm,gic-400", 
 *	   .data = (gic_of_init == (of_init_fn_2)NULL) ? gic_of_init : gic_of_init };
 */
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);

	/* 扫描 DTS 中匹配 @matches 的中断控制器,放入列表 @intc_desc_list */
	for_each_matching_node_and_match(np, matches, &match) {
		/* 属性 "interrupt-controller" 标记节点为中断控制器 */
		if (!of_property_read_bool(np, "interrupt-controller") ||
			!of_device_is_available(np))
			continue;
		
		...
		
		/* 为中断控制器分配描述对象 */ 
		desc = kzalloc(sizeof(*desc), GFP_KERNEL);

		desc->irq_init_cb = match->data; /* gic_of_init(),... */
		...
		desc->interrupt_parent = of_irq_find_parent(np);
		if (desc->interrupt_parent == np) /* 指定的父中断控制器和 @np 是相同节点,则置父中断控制器节点为 NULL */
			desc->interrupt_parent = NULL;
		list_add_tail(&desc->list, &intc_desc_list); /* 添加到中断控制器列表 */
	}

	/* 初始化中断控制器 */ 
	while (!list_empty(&intc_desc_list)) {
		/* 处理所有 interrupt_parent 为 @parent 的中断控制器 */ 
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			if (desc->interrupt_parent != parent) /* 当前只处理以 @parent 为父的中断控制器 */
				continue;
			
			list_del(&desc->list); /* 从中断控制器列表 @intc_desc_list 中移除 */

			...

			/* 初始化中断控制器 @desc */
			ret = desc->irq_init_cb(desc->dev,
						desc->interrupt_parent); /* gic_of_init(),... */

			/*
			 * 当前已经初始化的中断控制器,可能作为其它中断控制器的父。
			 * 将它添加父中断控制器列表 @intc_parent_list ,后续在此循
			 * 环内以父 @parent 的形式出现,作为初始化的时的第2个参数
			 * 传递给它的子中断控制器。
			 */ 
			list_add_tail(&desc->list, &intc_parent_list);
		}

		desc = list_first_entry_or_null(&intc_parent_list,
						typeof(*desc), list);
		/* @desc 将作为新的、可能的父中断控制器,先将其从父中断控制器列表 @intc_parent_list 中移除 */
		list_del(&desc->list);
		parent = desc->dev; /* 已经成功初始化过的中断控制器 @desc ,以新父中断控制器的身份重新出场 */
		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 芯片初始化。 
 * 
 * GIC 的 DTS 配置如下
 * gic: interrupt-controller@01c81000 {
 *		compatible = "arm,gic-400";
 *		reg = <0x01c81000 0x1000>,
 *			  <0x01c82000 0x2000>,
 *			  <0x01c84000 0x2000>,
 *			  <0x01c86000 0x2000>;
 *		interrupt-controller;
 *		#interrupt-cells = <3>;
 *		interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
 * };
 */
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
	gic = &gic_data[gic_cnt];
	
	ret = gic_of_setup(gic, node);
	...
	
	ret = __gic_init_bases(gic, -1, &node->fwnode);
	...

	if (parent) { /* 级联情形下的父中断控制器 */
		irq = irq_of_parse_and_map(node, 0);
		gic_cascade_irq(gic_cnt, irq);
	}

	gic_cnt++; /* 系统中注册的 ARM GIC 芯片 */
	return 0;
}

/* 读取并映射 Distributor 和 CPU interface 寄存器基地址 */ 
static int gic_of_setup(struct gic_chip_data *gic, struct device_node *node)
{
	/* 读取并映射 Distributor 寄存器基地址: reg[0] => <0x01c81000 0x1000> */
	gic->raw_dist_base = of_iomap(node, 0);
	
	/* 读取并映射 CPU interface 寄存器基地址: reg[1] => <0x01c82000 0x2000> */
	gic->raw_cpu_base = of_iomap(node, 1);

	/* 如果寄存器是各接口独立一份的,可以通过 "cpu-offset" 属性来指定个接口寄存器地址偏移 */
	if (of_property_read_u32(node, "cpu-offset", &gic->percpu_offset))
		gic->percpu_offset = 0;

	return 0;
}

static int __init __gic_init_bases(struct gic_chip_data *gic,
				   int irq_start,
				   struct fwnode_handle *handle)
{
	if (gic == &gic_data[0]) { /* 顶层 ROOT 中断控制器 */
		for (i = 0; i < NR_GIC_CPU_IF; i++)
			gic_cpu_map[i] = 0xff; /* 初始将中断派发给所有 CPU interface 上的 CPU */

	#ifdef CONFIG_SMP
		set_smp_cross_call(gic_raise_softirq); /* 用于处理器之间的通信:发送 SGI 中断到其它处理器 */
	#endif

		/* 
		 * CPU 热插拔状态 CPUHP_AP_IRQ_GIC_STARTING 处理接口,在
		 * 多核处理系统下,非 BOOT CPU 热插拔启动上线最终达到 
		 * CPUHP_AP_ONLINE 状态,在此之前会经过
		 *  CPUHP_AP_IRQ_GIC_STARTING 态,在该状态下会调用这里注
		 * 册的回调 gic_starting_cpu() 来为对应 CPU 初始化中断。
		 */
		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
					  "irqchip/arm/gic:starting",
					  gic_starting_cpu, NULL);
		
		/* 设置中断 GIC 中断处理入口,也即 ARM IRQ 中断处理入口 */
		set_handle_irq(gic_handle_irq);
			...
			handle_arch_irq = handle_irq;
	}

	/* 这是中断芯片名,这个名字会输出到 /proc/interrupts */
	name = kasprintf(GFP_KERNEL, "GICv2");
	gic_init_chip(gic, NULL, name, true); /* 设定中断芯片接口 */

	ret = gic_init_bases(gic, irq_start, handle);
	...

	return ret;
}

/* 初始化 GIC 中断芯片 irq_chip */
static void gic_init_chip(struct gic_chip_data *gic, struct device *dev,
			  const char *name, bool use_eoimode1)
{
	/* Initialize irq_chip */
	gic->chip = gic_chip;
	gic->chip.name = name;
	...

	if (use_eoimode1) {
		gic->chip.irq_mask = gic_eoimode1_mask_irq;
		gic->chip.irq_eoi = gic_eoimode1_eoi_irq;
		gic->chip.irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity;
	}

#ifdef CONFIG_SMP
	if (gic == &gic_data[0])
		gic->chip.irq_set_affinity = gic_set_affinity;
#endif
}

static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
			  struct fwnode_handle *handle)
{
	if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) { /* 每 {Distributor, CPU interface} 的寄存器基址情形(非 Banked) */
		...
	}  else { /* 非每 {Distributor, CPU interface} 的寄存器基址情形(Banked) */
		/* gic_chip_data::percpu_offset 为 0 的情形,
		 * 应该开启 CONFIG_GIC_NON_BANKED 配置 */
		gic->dist_base.common_base = gic->raw_dist_base;
		gic->cpu_base.common_base = gic->raw_cpu_base;
		gic_set_base_accessor(gic, gic_get_common_base);
	}
	
	/* 读取 GIC 实现支持的中断数目 */
	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32; /* 计算 GIC 芯片支持的中断数目 */
	if (gic_irqs > 1020) /* 1020~1023 被保留 */
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs; /* 记录 GIC 支持的中断数目 */

	if (handle) {		/* DT/ACPI */ /* 使用 DTS 的情形 */
		/* 为中断芯片注册中断域,细节见 3.3 小节 */
		gic->domain = irq_domain_create_linear(handle, gic_irqs,
						       &gic_irq_domain_hierarchy_ops,
						       gic);
	} else {		/* Legacy support */
		...
	}

	/*
	 * 初始化 GIC Distributor:
	 * . 设置所有 SPI 中断转发到哪些 CPU 中断接口
	 * . 配置所有 SPI 中断的电平触发方式,且低电平触发
	 * . 配置所有 SPI 中断为相同的优先级
	 * . 禁用所有 SPI 中断
	 */
	gic_dist_init(gic);
	ret = gic_cpu_init(gic);
	
	...
	return 0;
}

static void gic_dist_init(struct gic_chip_data *gic)
{
	unsigned int i;
	u32 cpumask;
	unsigned int gic_irqs = gic->gic_irqs;
	void __iomem *base = gic_data_dist_base(gic);
	
	writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);

	/* 
	 * 读取【当前CPU】某个 {SGI, PPI} 的非0派发配置,然后立即返回该配置值。
	 * 这些派发配置只会将中断派发给某个CPU (这里是 CPU 0),因为我们通常读
	 * 到的是当前CPU的PPI派发配置:它们是只读的,只会派发给对应的当前CPU
	 * (也即 BOOT CPU ,只有 BOOT CPU 的中断初始化流程才走这里。BOOT CPU 通常是 CPU 0)。
	 */ 
	cpumask = gic_get_cpumask(gic);
	/* 将 gic_get_cpumask() 读回的 8-bit 派发配置复制到 32-bit @cpumask 的所有 4个 8-bit 域 */
	cpumask |= cpumask << 8;
	cpumask |= cpumask << 16;
	/*
	 * 寄存器 GIC_DIST_TARGET[0 ~ 7] 是 (SGI + PPI) 中断的固有只读配置,
	 * GIC_DIST_TARGET[8~...] 设置所有 SPI 中断转发到哪些 CPU 中断接口。
	 *
	 * @cpumask 通常是 PPI 的配置,所以这里的配置是将所有的 SPI 派发给
	 * 当前 CPU (也即 BOOT CPU ,只有 BOOT CPU 的中断初始化流程才走这
	 * 里。BOOT CPU 通常是 CPU 0)。这也就是说,默认情况下,所有的 SPI
	 * 中断都会派发给当前的 BOOT CPU (BOOT CPU 通常是 CPU 0),到这里还
	 * 是没问题的,毕竟其它CPU核还没有跑起来。
	 */
	for (i = 32; i < gic_irqs; i += 4)
		writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);

	/*
	 * 配置 GIC 芯片 distributor:
	 * . 配置所有 SPI 中断的电平触发方式,且低电平触发
	 * . 配置所有 SPI 中断为相同的优先级
	 * . 禁用所有 SPI 中断
	 */
	gic_dist_config(base, gic_irqs, NULL);

	writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}

static int gic_cpu_init(struct gic_chip_data *gic)
{
	void __iomem *dist_base = gic_data_dist_base(gic);
	void __iomem *base = gic_data_cpu_base(gic);
	unsigned int cpu_mask, cpu = smp_processor_id();
	int i;

	if (gic == &gic_data[0]) {
		gic_check_cpu_features();
		cpu_mask = gic_get_cpumask(gic);
		gic_cpu_map[cpu] = cpu_mask; /* 当前 CPU @cpu 中断的默认转发配置:只转发给自身 */

		/*
		 * Clear our mask from the other map entries in case they're
		 * still undefined.
		 */
		for (i = 0; i < NR_GIC_CPU_IF; i++)
			if (i != cpu)
				gic_cpu_map[i] &= ~cpu_mask;
	}

	gic_cpu_config(dist_base, NULL);

	/*
	 * 只有优先级高于 GICC_INT_PRI_THRESHOLD 的中断才能传递给 GIC CPU 接口,
	 * 即 优先级值 < GICC_INT_PRI_THRESHOLD 的中断。
	 */
	writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
	gic_cpu_if_up(gic); /* 启用转发到 GIC CPU 接口的中断 */

	return 0;
}

void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
{
	int i;

	/*
	 * Deal with the banked PPI and SGI interrupts - disable all
	 * PPI interrupts, ensure all SGI interrupts are enabled.
	 * Make sure everything is deactivated.
	 */
	writel_relaxed(GICD_INT_EN_CLR_X32, base + GIC_DIST_ACTIVE_CLEAR);
	writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR);
	writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);

	/*
	 * Set priority on PPI and SGI interrupts
	 */
	/* 设置 SGI, PPI 中断的优先级为缺省优先级 */
	for (i = 0; i < 32; i += 4)
		writel_relaxed(GICD_INT_DEF_PRI_X4,
					base + GIC_DIST_PRI + i * 4 / 4);

	if (sync_access)
		sync_access();
}

static void gic_cpu_if_up(struct gic_chip_data *gic)
{
	void __iomem *cpu_base = gic_data_cpu_base(gic);
	u32 bypass = 0;
	u32 mode = 0;

	if (gic == &gic_data[0] && static_key_true(&supports_deactivate))
		mode = GIC_CPU_CTRL_EOImodeNS;

	/*
	* Preserve bypass disable bits to be written back later
	*/
	bypass = readl(cpu_base + GIC_CPU_CTRL);
	bypass &= GICC_DIS_BYPASS_MASK;

	/* 启用转发到 GIC CPU 接口的中断 */
	writel_relaxed(bypass | mode | GICC_ENABLE, cpu_base + GIC_CPU_CTRL);
}
3.2.2.2 非 BOOT CPU 对 GIC 的初始化
secondary_start_kernel()
	notify_cpu_starting(cpu)
		...
		while (st->state < target) {
			st->state++;
			/* 当  st->state == CPUHP_AP_IRQ_GIC_STARTING,调用 gic_starting_cpu() */
			ret = cpuhp_invoke_callback(cpu, st->state, true, NULL, NULL);
				gic_starting_cpu()
		}
static int gic_starting_cpu(unsigned int cpu)
{
	gic_cpu_init(&gic_data[0]); /* 见章节 3.2.2.1 分析 */
	return 0;
}

3.3 注册中断域

要为每个使用的中断芯片,注册一个中断域 irq_domainGIC 芯片也是一样。通过接口 irq_domain_create_linear() 注册中断域:

/*
 * @fwnode   : irq domain (中断控制器) 的 dts 配置节点指针
 * @size     : irq domain (中断控制器) 支持的中断数量
 * @ops      : irq domain (中断控制器) 接口
 * @host_data: 特定 irq domain (中断控制器) 的数据???
 */
static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode,
					 unsigned int size,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(fwnode, size, size, 0, ops, host_data);
}

struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	struct device_node *of_node = to_of_node(fwnode);
	struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	/* 
	 * 为 irq domain 以及其管理的 hwirq -> virq 的逆向映射表 分配空间。
	 * 其中 (sizeof(unsigned int) * size) 是逆向映射表的空间大小。
	 */
	domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
			      GFP_KERNEL, of_node_to_nid(of_node));
	
	if (fwnode && is_fwnode_irqchip(fwnode)) {
		...
	}  else if (of_node) { // Yes, we focus on this.
		char *name;

		name = kstrdup(of_node_full_name(of_node), GFP_KERNEL);

		strreplace(name, '/', ':');

		domain->name = name; /* 设定 irq domain 名称: 中断控制器 DTS 节点全路径 */
		domain->fwnode = fwnode; /* 设定 irq domain 关联的 DTS 节点 */
		domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	}
	
	of_node_get(of_node);

	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	/* 
	 * 设定 irq domain 的操作接口: 
	 * (如 gic_irq_domain_hierarchy_ops, sunxi_pinctrl_irq_domain_ops, ...) 
	 */
	domain->ops = ops;
	domain->host_data = host_data; /* 设定 irq domain 的私有数据 (&gic_data[0], ...) */
	domain->hwirq_max = hwirq_max; /* 设定 irq domain 管理的中断目数 */
	domain->revmap_size = size; /* 设定 irq domain 管理的映射条目数 */
	domain->revmap_direct_max_irq = direct_max;
	irq_domain_check_hierarchy(domain);

	utex_lock(&irq_domain_mutex);
	debugfs_add_domain_dir(domain); /* /sys/kernel/debug/domains/XXX */
	list_add(&domain->link, &irq_domain_list); /* 添加到系统全局 irq domain 列表 */
	mutex_unlock(&irq_domain_mutex);

	return domain;
}

static void irq_domain_check_hierarchy(struct irq_domain *domain)
{
	/* Hierarchy irq_domains must implement callback alloc() */
	/* 
	 * irq_domain_ops::alloc 接口由配置 CONFIG_IRQ_DOMAIN_HIERARCHY 使能;
	 * 同时,级联的中断域(中断控制器)必须实现 alloc 接口。
	 */
	if (domain->ops->alloc)
		domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
}

3.4 中断配置解析和映射

会发送中断信号到 GIC 的硬件模块,其 DTS 节点都会配置 interrupts 属性,我们这里以一个 STMMicro 网卡芯片为例,来说明中断配置的解析和映射建立过程。本章节讨论的是中断信号发送到 ROOT 中断控制器 GIC 的情形,对于级联的情形,将在后面的章节中讨论。
内核提供如下接口,来解析中断配置,以及建立 硬件中断号Linux虚拟中断号 映射。

/* include/linux/of_irq.h */

extern int of_irq_get(struct device_node *dev, int index);
extern int of_irq_get_byname(struct device_node *dev, const char *name);

extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
/* include/linux/platform_device.h */

extern int platform_get_irq(struct platform_device *, unsigned int);
extern int platform_get_irq_byname(struct platform_device *, const char *);

先来看例子网卡的 DTS 配置:

emac: ethernet@1c30000 {
	compatible = "allwinner,sun8i-h3-emac";
	...
	interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>;
	interrupt-names = "macirq";
	...
};

来看中断解析和映射建立的过程:

int irq = platform_get_irq_byname(pdev, "macirq");
int platform_get_irq_byname(struct platform_device *dev, const char *name)
{
	struct resource *r;

	if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {
		int ret;

		ret = of_irq_get_byname(dev->dev.of_node, name);
		if (ret > 0 || ret == -EPROBE_DEFER)
			return ret;
	}

	r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);
	return r ? r->start : -ENXIO;
}

int of_irq_get_byname(struct device_node *dev, const char *name)
{
	int index;

	if (unlikely(!name))
		return -EINVAL;

	index = of_property_match_string(dev, "interrupt-names", name);
	if (index < 0)
		return index;

	return of_irq_get(dev, index);
}

int of_irq_get(struct device_node *dev, int index)
{
	int rc;
	struct of_phandle_args oirq;
	struct irq_domain *domain;

	rc = of_irq_parse_one(dev, index, &oirq);
	if (rc)
		return rc;

	/* 找到 @dev 挂接的中断控制器对应的中断域对象 */
	domain = irq_find_host(oirq.np);
	if (!domain)
		return -EPROBE_DEFER;

	return irq_create_of_mapping(&oirq);
}

int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
	...

	/* Look for the interrupt parent. */
	p = of_irq_find_parent(device); /* 找到设备的中断控制器父节点:中断信号发给谁 */
	if (p == NULL)
		return -EINVAL;

	/* Get size of interrupt specifier */
	if (of_property_read_u32(p, "#interrupt-cells", &intsize)) {
		res = -EINVAL;
		goto out;
	}

	/* 读取中断配置项 */
	out_irq->np = p;
	out_irq->args_count = intsize;
	for (i = 0; i < intsize; i++) {
		res = of_property_read_u32_index(device, "interrupts",
						 (index * intsize) + i,
						 out_irq->args + i);
		if (res)
			goto out;
	}
	...
out:
	of_node_put(p);
	return res;	
}

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;

	of_phandle_args_to_fwspec(irq_data, &fwspec);
	return irq_create_fwspec_mapping(&fwspec);
}

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	/* 
	 * 例子中, @fwspec 的数据如下: 
	 * fwspec->fwnode = &gic;
	 * fwspec->param_count = 3;
	 * fwspec->param[0..2] = {GIC_SPI, 82, IRQ_TYPE_LEVEL_HIGH};
	 */
	
	if (fwspec->fwnode) {
		/* 找到中断挂接中断控制器的 irq_domain */
		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
	} else {
		...
	}

	/*
	 * 调用 irq_domain (中断控制器)的中断配置项解析接口,
	 * 提取 【硬件中断号】 和 【中断触发类型】 信息: 
	 * @fwspec ==> {@hwirq, @type} 
	 */
	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
		return 0;

	/*
	 * 分配一个空闲的 Linux 虚拟 IRQ 中断号,映射到 @hwirq ,
	 * 同时分配一个 IRQ 中断描述符 irq_desc 。
	 */
	if (irq_domain_is_hierarchy(domain)) {
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
	}  else {
		...
	}

	...

	/* Store trigger type */
	irqd_set_trigger_type(irq_data, type); /* 设置中断触发类型: RISING, FALLING, HIGH, LOW */

	return virq; /* 返回分配的 Linux IRQ 虚拟中断号 */
}

static int irq_domain_translate(struct irq_domain *d,
				struct irq_fwspec *fwspec,
				irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/* 调用 irq_domain (中断控制器)的中断配置解析接口: @fwspec ==> {@hwirq, @type} */
	if (d->ops->translate)
		return d->ops->translate(d, fwspec, hwirq, type); /* gic_irq_domain_translate() */
#endif
}

/* GIC 中断配置项解析:@fwspec ==> {@hwirq, @type} */
static int gic_irq_domain_translate(struct irq_domain *d,
				    struct irq_fwspec *fwspec,
				    unsigned long *hwirq,
				    unsigned int *type)
{
	if (is_of_node(fwspec->fwnode)) { /* 普通中断信号源设备 DTS 节点 */
		*hwirq = fwspec->param[1] + 16; /* 跳过 SGIs 中断编号区间 */
		if (!fwspec->param[0]) /* GIC_SPI */
			*hwirq += 16;
		
		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; /* 中断触发类型,如 IRQ_TYPE_LEVEL_HIGH */
		return 0;
	}
	...
}

/* 
 * 1. 中断描述符分配;
 * 2. 映射 硬件中断号 到 Linux虚拟中断号;
 * 3. 中断处理接口设置。
 */
static inline int irq_domain_alloc_irqs(struct irq_domain *domain,
			unsigned int nr_irqs, int node, void *arg/*struct irq_fwspec*/)
{
	return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false,
				       NULL);
}

int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
			    unsigned int nr_irqs, int node, void *arg,
			    bool realloc, const struct cpumask *affinity)
{
	int i, ret, virq;

	...
	/* 分配中断描述符,以及 Linux 虚拟中断号 */
	virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
					      affinity);

	/* 设置中断描述符的中断域(中断控制器) */
	if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {
		...
		ret = -ENOMEM;
		goto out_free_desc;
	}

	...
	/* 设置中断的 处理接口 和 flag 等 */
	ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);

	/* 建立中断域内 硬件中断号 到 Linux 虚拟中断号 的 映射关系 */
	for (i = 0; i < nr_irqs; i++)
		irq_domain_insert_irq(virq + i);

	return virq; /* 返回分配的起始 Linux 虚拟中断号 */
}

int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,
				    unsigned int irq_base,
				    unsigned int nr_irqs, void *arg)
{
	return domain->ops->alloc(domain, irq_base, nr_irqs, arg); /* gic_irq_domain_alloc(), ... */
}

static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
				unsigned int nr_irqs, void *arg)
{
	int i, ret;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	struct irq_fwspec *fwspec = arg;

	ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type);

	for (i = 0; i < nr_irqs; i++) {
		ret = gic_irq_domain_map(domain, virq + i, hwirq + i);
		if (ret)
			return ret;
	}

	return 0;
}

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
				irq_hw_number_t hw)
{
	struct gic_chip_data *gic = d->host_data;

	if (hw < 32) { /* 每 CPU 的私有中断: SGI + PPI */
		irq_set_percpu_devid(irq);
		/* 设置 SGI, PPI 中断处理接口,由它再调用irq_desc::action 接口 */
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_percpu_devid_irq, NULL, NULL);
		irq_set_status_flags(irq, IRQ_NOAUTOEN);
	} else { /* 全局于所有 CPU 的 SPI 中断 */
		/* 设置 SPI 中断处理接口,由它再调用 irq_desc::action 接口 */
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_fasteoi_irq, NULL, NULL);
		irq_set_probe(irq);
		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
	}
	return 0;
}

static void irq_domain_insert_irq(int virq)
{
	struct irq_data *data;
	
	for (data = irq_get_irq_data(virq); data; data = data->parent_data) {
		struct irq_domain *domain = data->domain;

		domain->mapcount++;
		irq_domain_set_mapping(domain, data->hwirq, data);
		...
	}

	irq_clear_status_flags(virq, IRQ_NOREQUEST);
}

static void irq_domain_set_mapping(struct irq_domain *domain,
				   irq_hw_number_t hwirq,
				   struct irq_data *irq_data)
{
	if (hwirq < domain->revmap_size) {
		domain->linear_revmap[hwirq] = irq_data->irq; /* 硬件中断号 到 Linux 虚拟中断号 的映射 */
	} else { /* 超过 irq_domain 注册时设定的中断数目,用基树建立映射关系 */
		mutex_lock(&revmap_trees_mutex);
		radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
		mutex_unlock(&revmap_trees_mutex);
	}
}

到此,完成 硬件中断号Linux虚拟中断号 映射的建立,同时分配了中段描述符 irq_desc ,函数的最终返回值为分配的 Linux虚拟中断号
另外,从代码分析,我们看到了 GIC 硬件中断号 DTS 配置值的一个规律:如果是 PPI 中断,DTS 配置值应该按实际硬件中断号减去 16;如果是 SPI 中断,DTS 配置值应该按实际硬件中断号减去 32

3.5 中断处理接口注册

在章节 3.4 中完成中断配置解析和映射后 ,剩下的就是注册中断处理接口。可以通过 request_irq()request_threaded_irq() 来注册中断处理接口,这里只分析线程化方式的 request_threaded_irq() ,传递给 request_threaded_irq()irq 参数,就是章节 3.4 申请的 Linux虚拟中断号

request_threaded_irq(irq, handler, thread_fn, irqflags, devname, dev_id)
		desc = irq_to_desc(irq);
		
		if (!handler) { /* 线程化中断的 @handler 通常设为 NULL */
			if (!thread_fn)
				return -EINVAL;
			handler = irq_default_primary_handler;
		}
		
		action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
		action->handler = handler; /* 设置中断处理接口 */
		action->thread_fn = thread_fn;
		action->flags = irqflags;
		action->name = devname;
		action->dev_id = dev_id;
		
		retval = __setup_irq(irq, desc, action);
			new->irq = irq; /* new = action */
			if (new->thread_fn && !nested) { /* 中断线程化 */
				ret = setup_irq_thread(new, irq, false);
					struct task_struct *t;
					struct sched_param param = {
						.sched_priority = MAX_USER_RT_PRIO/2,
					};
					
					t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
					sched_setscheduler_nocheck(t, SCHED_FIFO, &param);
					get_task_struct(t);
					new->thread = t;
					set_bit(IRQTF_AFFINITY, &new->thread_flags);
					return 0;
				...
			}
			
			old_ptr = &desc->action;
			old = *old_ptr;
			...
			irqd_get_trigger_type(&desc->irq_data);
			...
			*old_ptr = new;
			
			irq_pm_install_action(desc, new);
			
			desc->irq_count = 0;
			desc->irqs_unhandled = 0;
			
			...
			
			irq_setup_timings(desc, new);
			
			if (new->thread)
				wake_up_process(new->thread); /* 唤醒中断处理线程 */
			
			register_irq_proc(irq, desc); /* /proc/irq/N/* */
			irq_add_debugfs_entry(irq, desc);
			new->dir = NULL;
			register_handler_proc(irq, new);
			return 0;
		...
		return retval;

3.6 中断处理流程

先来看看中断发生时,ARM32 硬件架构会做哪些动作:
在这里插入图片描述其中 <exception_mode> 是指 CPU 的异常模式,如 IRQ 。简单总结一下,进入中断异常模式时,在 ARM32 架构做了如下动作:

. R14_<exception_mode> = 被中断的指令的地址(即处理中断后应返回的地址)
. SPSR_<exception_mode> = CPSR (被中断 CPU 模式的 CPSR,如 CPSR_svc, CPSR_usr)
. CPSR[4:0] = exception mode nuber(切换到异常模式)
. 如果时进入到 Reset 或 FIQ 异常模式,则禁用 FIQ (CPSR[6] = 1)
. 禁用 IRQ 中断 (CPSR[7] = 1)
...
. PC = 异常向量地址(即跳转到异常向量地址处执行)

这些动作都是硬件自动完成的,不需要软件的干预。另外,需要知道的是,在 CPU 各异常模式下,部分寄存器有独立的副本,详情如下:
在这里插入图片描述
可以看到,寄存器 r0 ~ r7 在所有 CPU 模式共用同一副本;r8 ~ r12 除了 FIQ 模式有自己独立副本外,其它模式下共用一个副本;r13 ~ r14User/System 模式共用一个副本外,其它模式下都有自己独立的副本;PC 各模式共用。
了解上述内容,对于理解中断的处理过程是必不可少的。接下来,看 ARM32 架构下中断处理的具体流程。本文只讨论 IRQ 中断的处理流程,对其它中断异常类型处理过程感兴趣的读者,可自行阅读相关源码。

3.6.1 ARM32 中断向量表

/* arch/arm/kernel/entry-armv.S */

	.section .vectors, "ax", %progbits
.L__vectors_start:
	W(b)	vector_rst /* 复位 */
	W(b)	vector_und /* 未定义指令异常向量表指针 */
	/*
	 * @ arch/arm/kernel/vmlinux.lds.S
	 *
	 * __stubs_start = .;
	 * .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
	 * 		*(.stubs)
	 * }
	 * . = __stubs_start + SIZEOF(.stubs);
	 * __stubs_end = .;
	 */
	W(ldr)	pc, .L__vectors_start + 0x1000 /* 软中断向量(swi) */
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq /* IRQ */
	W(b)	vector_fiq /* FIQ */

	.data
	.align	2

发生中断后,先切换到 SVC 模式,然后保存被中断的上下文到 SVC 模式栈(即进程内核栈),然后进一步跳转到 IRQ 中断处理入口 handle_arch_irq 。在进一步讨论之前,先解释下为什么要切换 SVC 模式,这主要是为了借用 SVC 模式栈(进程内核栈)进行中断处理,因为各异常模式配置的栈空间只有 3 个 u32 大小。更多关于中断异常模式栈空间的细节,可参考博文 Linux: ARM32 各 CPU 模式下栈配置 的章节 4.2.2 中断异常发生时各异常模式 CPU 栈配置

/* arch/arm/kernel/entry-armv.S */

	.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq /* r1 = gic_handle_irq() */
	mov	r0, sp
	badr	lr, 9997f
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif
9997:
	.endm

	...
	
	.data
	.align	2
	
	vector_stub	irq, IRQ_MODE, 4
	.long	__irq_usr			@  0  (USR_26 / USR_32)
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)
	.long	__irq_invalid			@  4
	.long	__irq_invalid			@  5
	.long	__irq_invalid			@  6
	.long	__irq_invalid			@  7
	.long	__irq_invalid			@  8
	.long	__irq_invalid			@  9
	.long	__irq_invalid			@  a
	.long	__irq_invalid			@  b
	.long	__irq_invalid			@  c
	.long	__irq_invalid			@  d
	.long	__irq_invalid			@  e
	.long	__irq_invalid			@  f
	
	.align	5
__irq_svc: /* 内核态(ARM CPU SVC模式)中断入口 */
	svc_entry /* 保存被中断模式上下文到 SVC 模式栈(即进程内核栈)上 */
	irq_handler /* 中断处理入口 */

#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0				@ if preempt count != 0
	movne	r0, #0				@ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif

	/* 恢复被中断模式上下文,退出中断 */
	svc_exit r5, irq = 1			@ return from exception
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

	...

	.align	5
__irq_usr: /* 用户态(ARM CPU User模式)中断入口 */
	usr_entry
	kuser_cmpxchg_check
	irq_handler /* 中断处理入口 */
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

3.6.2 进入中断处理主入口

现在 handle_arch_irq 指针的值,正是在 GIC 初始化阶段设置的 gic_handle_irq()

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		/* 读取硬件中断号 */
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK;

		if (likely(irqnr > 15 && irqnr < 1020)) { /* 处理 PPI, SPI */
			...
			handle_domain_irq(gic->domain, irqnr, regs);
			continue;
		}
		if (irqnr < 16) { /* 处理 SGI */
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			...
		#ifdef CONFIG_SMP	
			handle_IPI(irqnr, regs);
		#endif
			continue;
		}
	} while (1);
}

static inline int handle_domain_irq(struct irq_domain *domain,
				    unsigned int hwirq, struct pt_regs *regs)
{
	return __handle_domain_irq(domain, hwirq, true, regs);
}

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	unsigned int irq = hwirq;
	int ret = 0;

	irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
	if (lookup)
		irq = irq_find_mapping(domain, hwirq); /* 查找 硬件中断号 @hwirq 对应的 Linux 虚拟中断号 */
#endif

	...
	generic_handle_irq(irq); /* 处理中断 */
	...

	irq_exit(); /* 软中断, RCU 等等处理 */
	set_irq_regs(old_regs);
	return ret;
}

int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);
	return 0;
}

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	/*
	 * GIC v1 芯片:
	 * . PPI: handle_percpu_devid_irq()
	 * . SPI: handle_fasteoi_irq()
	 *
	 * 级联 GPIO 中断控制器:
	 * handle_fasteoi_irq(): 处理 LEVEL 触发中断(高低电平)
	 * handle_edge_irq(): 处理 EDGE 触发中断(上升、下降沿)
	 */
	desc->handle_irq(desc);
}

/* 处理 PPI 中断 */
void handle_percpu_devid_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	struct irqaction *action = desc->action;
	unsigned int irq = irq_desc_get_irq(desc);
	irqreturn_t res;

	__kstat_incr_irqs_this_cpu(desc); /* 更新中断 @irq 次数统计 */

	if (chip->irq_ack) /* 中断芯片 ACK 处理 */
		chip->irq_ack(&desc->irq_data);

	/* 调用 request_irq(), request_thread_irq() 注册的接口 */
	res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));

	if (chip->irq_eoi) /* 中断芯片 EOI 处理 */
		chip->irq_eoi(&desc->irq_data);
}

/* 处理 SPI 中断 */
void handle_fasteoi_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = desc->irq_data.chip;

	...
	
	kstat_incr_irqs_this_cpu(desc); /* 更新中断当前 CPU 的统计数据:SPI 中断可能派发到任一 CPU 核上 */
	if (desc->istate & IRQS_ONESHOT) /* 中断标记为只触发一次,触发一次中断后,屏蔽它 */
		mask_irq(desc);

	preflow_handler(desc);
	handle_irq_event(desc);
	...
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	rqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 标记一个中断正在处理中 */
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc);

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 中断处理完成,清除 "中断正在处理中" 标记 */
	return ret;
}

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);

	add_interrupt_randomness(desc->irq_data.irq, flags); /* 中断处理为 CRND 随机数做贡献 */

	...
	return retval;
}

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
	record_irq_time(desc);

	/* 调用注册到中端 irq 的所有 action handler */
	for_each_action_of_desc(desc, action) {
		irqreturn_t res;

		res = action->handler(irq, action->dev_id); /* 调用 (SGI) 中断处理接口 */
		switch (res) {
		 /* 线程化中断,主接口应该返回 IRQ_WAKE_THREAD , 
		  * 譬如线程化中断的默认主接口 irq_default_primary_handler()
		  */
		case IRQ_WAKE_THREAD:
			__irq_wake_thread(desc, action); /* 唤醒中断线程处理中断 */
			/* Fall through to add to randomness */
		case IRQ_HANDLED: /* 中断处理完毕 */
			*flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
	}
	
	return retval;
}

4. 中断控制器级联

中断控制器级联最常见的情形,是将 GPIO 中断控制器,级联到 GIC ,拓扑结构如下:

GPIO 中断信号 -> GPIO中断控制器 -> GIC -> CPU

我们还是以一个 GPIO pinctrl 驱动为例,来分析级联中断的初始化、中断接口注册、中断处理的3个过程。

4.1 级联中断控制器的初始化

先看级联 GPIO 中断控制器的 DTS 配置:

pio: pinctrl@01c20800 {
	...
	interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
				 <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
	...
	gpio-controller;
	#gpio-cells = <3>;
	interrupt-controller;
	#interrupt-cells = <3>;
	...
};

从 DTS 看到,这是一个 GPIO 中断控制器,它输出2个中断信号11,17GIC。看一下 级联的GPIO中断控制器 初始化细节:

/* 
 * 节选自全志 H3 的 gpiochip/pinctrl 复合驱动,代码均为开源代码。
 * 为方便理解,对逻辑上进行些改动,以突出中断相关部分。
 */
struct sunxi_pinctrl {
	...
	struct gpio_chip		*chip;
	...
	struct irq_domain		*domain;
	int				virq[2]; /* 记录 DTS 的配置的中断 11,17 的 Linux 虚拟中断号 */
	...
};

/* GPIO 中断控制器 中断域接口 */
static const struct irq_domain_ops sunxi_pinctrl_irq_domain_ops = {
	.xlate		= sunxi_pinctrl_irq_of_xlate,
};
 
int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	struct sunxi_pinctrl *pctl;
	int i;

	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
	
	...
	/* 设置将 GPIO PIN ID 映射为其对应 Linux 虚拟中断号 的接口 */
	pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq,
	...

	/* 
	 * DTS 配置的 GPIO 中断控制器输出到 GIC 的2个中断信号的解析
	 * 和映射:
	 * interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
	 *			    <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
	 */
	pctl->virq[0] = platform_get_irq(pdev, 0);
	pctl->virq[1] = platform_get_irq(pdev, 1);

	/* 创建 GPIO 中断控制器 的中断域 irq_domain */	
	pctl->domain = irq_domain_add_linear(node, 64/*64个GPIO,64个中断*/,
					     	&sunxi_pinctrl_irq_domain_ops, pctl);

	/* 为 GPIO 中断控制器的所有64个GPIO硬件中断,映射分配 Linux虚拟中断号 */
	for (i = 0; i < 64; i++) {
		int virq = irq_create_mapping(pctl->domain, i);
		/* 设置 GPIO 中断 的 【中断芯片底层操作接口】 和 【沿触发模式 中断处理接口】 */
		irq_set_chip_and_handler(virq, &sunxi_pinctrl_edge_irq_chip,
					 handle_edge_irq);
		/* 绑定 GPIO 中断 的 中断芯片底层数据 @pctl */
		irq_set_chip_data(virq, pctl);
	}
	
	/* 我们还要设置 GPIO 中断控制器输出到 GIC 信号的中断处理接口,默认的接口不是我们想要的。 */
	irq_set_chained_handler_and_data(pctl->virq[0],
						 sunxi_pinctrl_irq_handler, pctl);
	irq_set_chained_handler_and_data(pctl->virq[1],
						 sunxi_pinctrl_irq_handler, pctl);
}

/* 硬件中断号 到 Linux虚拟中断号的 映射过程 */
unsigned int irq_create_mapping(struct irq_domain *domain,
				irq_hw_number_t hwirq)
{
	struct device_node *of_node;
	int virq;

	...
	of_node = irq_domain_get_of_node(domain); /* 获取中断域的 DTS 节点 */

	virq = irq_find_mapping(domain, hwirq); /* 查找硬件中断号 @hwirq 在中断域 @domain 中的映射 */
	if (virq) { /* 硬件中断号 @hwirq 在中断域 @domain 中已经映射过了 */
		return virq; /* 已经映射过了,返回映射的 Linux 虚拟中断号 */
	}

	/*
	 * 为硬件中断号 @hwirq 映射一个虚拟中断号,
	 * 并分配并初始化一个中断描述符,插入全局中断描述符基树。
	 */
	virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);

	/* 
	 * . 建立中断域 @domain 内 【硬件中断号 @hwirq】 到 【Linux 
	 *   虚拟中断号 @virq】 的映射 ;
	 * . 设定中断 @virq 的中断域为 @domain 。
	 */
	if (irq_domain_associate(domain, virq, hwirq)) {
		irq_free_desc(virq);
		return 0;
	}
	
	return virq; /* 返回 @hwirq 映射的 Linux虚拟中断号 @virq */
}

到此,我们已经初始化好了级联的 GPIO 中断控制器,接下来看 GPIO 中断的使用过程。

4.2 级联中断使用范例

4.2.1 例1:USB PHY 芯片 GPIO 中断使用

先看 DTS 配置:

usbphy: phy@01c19400 {
	compatible = "allwinner,sun8i-h3-usb-phy";
	...
	usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */
	...
};

PG12 中断信号处理接口注册流程:

data->id_det_gpio = devm_gpiod_get_optional(dev, "usb0_id_det",
						    GPIOD_IN);
...
data->id_det_irq = gpiod_to_irq(data->id_det_gpio); /* 将 GPIO PIN 映射到 Linux 虚拟中断号 */
/* 注册 PG12 GPIO 中断处理接口 */
ret = devm_request_irq(dev, data->id_det_irq,
				sun4i_usb_phy0_id_vbus_det_irq,
				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
				"usb0-id-det", data);
...

gpiod_to_irq() 有点新鲜,但很常见,看一下它的具体细节:

int gpiod_to_irq(const struct gpio_desc *desc)
{
	struct gpio_chip *chip;
	int offset;

	chip = desc->gdev->chip;
	offset = gpio_chip_hwgpio(desc);
	if (chip->to_irq) {
		int retirq = chip->to_irq(chip, offset); /* sunxi_pinctrl_gpio_to_irq() */
		...
		return retirq;
	}
}

static int sunxi_pinctrl_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
	struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);
	struct sunxi_desc_function *desc;
	unsigned pinnum = pctl->desc->pin_base + offset;
	unsigned irqnum;

	...
	desc = sunxi_pinctrl_desc_find_function_by_pin(pctl, pinnum, "irq");
	if (!desc)
		return -EINVAL;

	irqnum = desc->irqbank * IRQ_PER_BANK + desc->irqnum;

	return irq_find_mapping(pctl->domain, irqnum); /* 返回 硬件中断号 @irqnum 映射的 Linux 软件中断号 */
}

4.2.2 例2:无线网卡唤醒 GPIO 中断使用

先看 DTS 配置:

brcmf: bcrmf@1 {
		reg = <1>;
		compatible = "brcm,bcm4329-fmac";
		interrupt-parent = <&pio>;
		interrupts = <6 10 IRQ_TYPE_LEVEL_LOW>; /* PG10 / EINT10 */
		interrupt-names = "host-wake";
	};

注意,interrupt-parent = <&pio>; 表明该设备的中断信号是传递给 GPIO 中断控制器 pio 的。看看中断配置解析映射的过程:

if (!of_find_property(np, "interrupts", NULL))
		return;

irq = irq_of_parse_and_map(np, 0); /* hwirq ==> irq */
irqf = irqd_get_trigger_type(irq_get_irq_data(irq));

sdio->oob_irq_supported = true;
sdio->oob_irq_nr = irq;
sdio->oob_irq_flags = irqf;
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
	struct of_phandle_args oirq;
	
	/*
	 * oirq.np = &pio;
	 * oirq.args_count = 3;
	 * oirq.args[0..2] = {6, 10, IRQ_TYPE_LEVEL_LOW};
	 */
	if (of_irq_parse_one(dev, index, &oirq))
		return 0;

	return irq_create_of_mapping(&oirq);
}

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;
	
	of_phandle_args_to_fwspec(irq_data, &fwspec);
	return irq_create_fwspec_mapping(&fwspec);
}

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	struct irq_domain *domain;
	
	if (fwspec->fwnode) {
		/* 找到中断挂接中断控制器的 irq_domain */
		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
	} else {
		...
	}

	/*
	 * 调用 irq_domain (中断控制器)的中断配置项解析接口,
	 * 提取 硬件中断号 和 中断触发类型 信息: 
	 * @fwspec ==> {@hwirq, @type} 
	 */
	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
		return 0;

	if (irq_domain_is_hierarchy(domain)) {
		...
	} else {
		/* Create mapping */
		/* 建立 硬件中断号 到 Linux虚拟中断号 的映射 */
		virq = irq_create_mapping(domain, hwirq);
		if (!virq)
			return virq;
	}

	irqd_set_trigger_type(irq_data, type); /* 设置中断触发类型: RISING, FALLING, HIGH, LOW */

	return virq; /* 返回分配的 Linux IRQ 虚拟中断号 */
}

static int irq_domain_translate(struct irq_domain *d,
				struct irq_fwspec *fwspec,
				irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
	...
#endif	
	if (d->ops->xlate) /* 级联的 GPIO 中断控制器中断域的接口 sunxi_pinctrl_irq_of_xlate() */
		return d->ops->xlate(d, to_of_node(fwspec->fwnode),
				     fwspec->param, fwspec->param_count,
				     hwirq, type);

	...
}

static int sunxi_pinctrl_irq_of_xlate(struct irq_domain *d,
				      struct device_node *node,
				      const u32 *intspec,
				      unsigned int intsize,
				      unsigned long *out_hwirq,
				      unsigned int *out_type)
{
	struct sunxi_pinctrl *pctl = d->host_data;
	struct sunxi_desc_function *desc;
	int pin, base;

	...
	base = PINS_PER_BANK * intspec[0]; /* intspec[0]: GPIO bank */
	pin = pctl->desc->pin_base + base + intspec[1]; /* intspec[1]: GPIO pin */

	/* 查询 GPIO PIN 的中断属性信息 */
	desc = sunxi_pinctrl_desc_find_function_by_pin(pctl, pin, "irq");
	if (!desc)
		return -EINVAL;

	*out_hwirq = desc->irqbank * PINS_PER_BANK + desc->irqnum; /* 返回硬件中断号 */
	*out_type = intspec[2]; /* 解析中断触发类型 */

	return 0;
}

中断信号处理接口注册:

ret = request_irq(pdata->oob_irq_nr, brcmf_sdiod_oob_irqhandler,
				  pdata->oob_irq_flags, "brcmf_oob_intr",
				  &sdiodev->func1->dev);

4.3 级联中断的处理过程

前一部分和章节 3.6 的流程一样:

gic_handle_irq()
	handle_domain_irq(gic->domain, irqnr, regs)
		__handle_domain_irq(domain, hwirq, true, regs)
			struct pt_regs *old_regs = set_irq_regs(regs);
			unsigned int irq = hwirq;
			int ret = 0;
			
			irq_enter();
			irq = irq_find_mapping(domain, hwirq); /* 查找 硬件中断号 @hwirq 对应的 Linux 虚拟中断号 */
			
			generic_handle_irq(irq);
				struct irq_desc *desc = irq_to_desc(irq);
				generic_handle_irq_desc(desc);
					desc->handle_irq(desc) = sunxi_pinctrl_irq_handler()
			irq_exit(); /* 软中断, RCU 等等处理 */
			set_irq_regs(old_regs);
			return ret;

sunxi_pinctrl_irq_handler() 处开始不同,继续看 sunxi_pinctrl_irq_handler() 细节:

static void sunxi_pinctrl_irq_handler(struct irq_desc *desc)
{
	unsigned int irq = irq_desc_get_irq(desc);

	...
	val = readl(pctl->membase + reg); /* 读取触发的 GPIO 中断 */
	
	if (val) {
		int irqoffset;

		chained_irq_enter(chip, desc);
		for_each_set_bit(irqoffset, &val, IRQ_PER_BANK) { /* 处理所有触发的 GPIO 中断 */
			int pin_irq = irq_find_mapping(pctl->domain,
						       bank * IRQ_PER_BANK + irqoffset); /* 找到硬件中断的 Linux 虚拟中断号 */
			/* okay, 这里没有无穷递归形成。 */
			generic_handle_irq(pin_irq); /* 处理一个 GPIO 中断 */
		}
		chained_irq_exit(chip, desc);
	}
}

int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);
	return 0;
}

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	/*
	 * GIC v1 芯片:
	 * . PPI: handle_percpu_devid_irq()
	 * . SPI: handle_fasteoi_irq()
	 * . 级联中断信号: sunxi_pinctrl_irq_handler()
	 *
	 * 级联 GPIO 中断控制器:
	 * handle_fasteoi_irq(): 处理 LEVEL 触发中断(高低电平)
	 * handle_edge_irq(): 处理 EDGE 触发中断(上升、下降沿)
	 */
	desc->handle_irq(desc);
}

/* 处理 GPIO 沿触发方式中断 */
void handle_edge_irq(struct irq_desc *desc)
{
	...
	kstat_incr_irqs_this_cpu(desc); /* 中断数据统计 */
	...

	desc->irq_data.chip->irq_ack(&desc->irq_data); /* sunxi_pinctrl_irq_ack() */

	do {
		...
		handle_irq_event(desc);
	}  while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));
	...
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	irqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 标记一个中断正在处理中 */
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc);

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 中断处理完成,清除 "中断正在处理中" 标记 */
	return ret;
}

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);

	add_interrupt_randomness(desc->irq_data.irq, flags); /* 中断处理为 CRND 随机数做贡献 */

	...
	return retval;
}

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
	/* 调用注册到中端 irq 的所有 action handler */
	for_each_action_of_desc(desc, action) {
		irqreturn_t res;
		
		/* sun4i_usb_phy0_id_vbus_det_irq() / brcmf_sdiod_oob_irqhandler() */
		res = action->handler(irq, action->dev_id);

		switch (res) {
		 /* 线程化中断,主接口应该返回 IRQ_WAKE_THREAD , 
		  * 譬如线程化中断的默认主接口 irq_default_primary_handler()
		  */
		case IRQ_WAKE_THREAD:
			__irq_wake_thread(desc, action); /* 唤醒中断线程处理中断 */

			/* Fall through to add to randomness */
		case IRQ_HANDLED: /* 中断处理完毕 */
			*flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
	}

	return retval;
}

4.3.1 GPIO 中断电平触发模式切换

前面看到的GPIO中断控制器处理流程,是关于沿触发方式的中断处理,那 GPIO的 电平触发方式 的处理是怎样的?首先,要将 GPIO 的中断触发方式进行切换,用户空间通过向 /sys/class/gpio/gpioN/edge 写入 risingfalling ,我们例子中的最终会调用接口 sunxi_pinctrl_irq_set_type()

static int sunxi_pinctrl_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct sunxi_pinctrl *pctl = irq_data_get_irq_chip_data(d);
	u32 reg = sunxi_irq_cfg_reg(d->hwirq, pctl->desc->irq_bank_base);
	u8 index = sunxi_irq_cfg_offset(d->hwirq);
	unsigned long flags;
	u32 regval;
	u8 mode;

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		mode = IRQ_EDGE_RISING;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		mode = IRQ_EDGE_FALLING;
		break;
	case IRQ_TYPE_EDGE_BOTH:
		mode = IRQ_EDGE_BOTH;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		mode = IRQ_LEVEL_HIGH;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		mode = IRQ_LEVEL_LOW;
		break;
	default:
		return -EINVAL;
	}

	raw_spin_lock_irqsave(&pctl->lock, flags);

	if (type & IRQ_TYPE_LEVEL_MASK)
		irq_set_chip_handler_name_locked(d, &sunxi_pinctrl_level_irq_chip,
						 handle_fasteoi_irq, NULL);
	else
		irq_set_chip_handler_name_locked(d, &sunxi_pinctrl_edge_irq_chip,
						 handle_edge_irq, NULL);

	...

	raw_spin_unlock_irqrestore(&pctl->lock, flags);

	return 0;
}

从上面的逻辑可以看出,处理电平模式 IRQ_TYPE_LEVEL_HIGHIRQ_TYPE_LEVEL_LOW ,最终会调用 handle_fasteoi_irq() 进行中断处理。
从前面的分析,我们了解到一个事实:

Linux 虚拟中断号 是全局唯一的,它不特定于某个中断域(中断芯片);
而 硬件中断号 是特定于某个中断域(中断芯片)的,中断域之间的 硬件中断号 可以是重复的。

5. SGI 中断处理

SGI 中断用于处理器核之前的通信,前面我们没有分析 SGI 中断的处理,现在来看一下:

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		/* 读取硬件中断号 */
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK;

		if (likely(irqnr > 15 && irqnr < 1020)) { /* 处理 PPI, SPI */
			...
		}

		if (irqnr < 16) { /* 处理 SGI */
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			...
		#ifdef CONFIG_SMP
			/* SGI 中断用于处理器间的通信,所以只有多核处理器架构下才有 */
			smp_rmb();
			handle_IPI(irqnr, regs);
		#endif
			continue;	
		}
		break;
	} while (1);
}

void handle_IPI(int ipinr, struct pt_regs *regs)
{
	unsigned int cpu = smp_processor_id();
	struct pt_regs *old_regs = set_irq_regs(regs);

	if ((unsigned)ipinr < NR_IPI) {
		trace_ipi_entry_rcuidle(ipi_types[ipinr]);
		__inc_irq_stat(cpu, ipi_irqs[ipinr]);
	}
	
	switch (ipinr) {
	case IPI_WAKEUP:
		break;
		
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
	case IPI_TIMER:
		irq_enter();
		tick_receive_broadcast();
		irq_exit();
		break;
#endif

	case IPI_RESCHEDULE: 
		scheduler_ipi();
		break;

	case IPI_CALL_FUNC:
		irq_enter();
		generic_smp_call_function_interrupt();
		irq_exit();
		break;

	case IPI_CPU_STOP:
		irq_enter();
		ipi_cpu_stop(cpu);
		irq_exit();
		break;

#ifdef CONFIG_IRQ_WORK
	case IPI_IRQ_WORK:
		irq_enter();
		irq_work_run();
		irq_exit();
		break;
#endif

	case IPI_COMPLETION:
		irq_enter();
		ipi_complete(cpu);
		irq_exit();
		break;

	case IPI_CPU_BACKTRACE:
		printk_nmi_enter();
		irq_enter();
		nmi_cpu_backtrace(regs);
		irq_exit();
		printk_nmi_exit();
		break;

	default:
		pr_crit("CPU%u: Unknown IPI message 0x%x\n",
		        cpu, ipinr);
		break;
	}
	
	if ((unsigned)ipinr < NR_IPI)
		trace_ipi_exit_rcuidle(ipi_types[ipinr]);
	set_irq_regs(old_regs);
}

6. 中断共享

在调用 request_irq() 传递 IRQF_SHARED 标记,和自身独特的数据指针标记。 这里不讨论中断共享的细节,感兴趣的读者可自行阅读代码分析。

7. 中断开关操作

先看一个简单中断相关硬件拓扑结构:

                                   GIC                                CPU
                -----------------------------------------       --------------
               |     -------------     ----------------   |    | core0  core2 |
外设中断信号 ---|-->| Distributors |-->| CPU Interfaces |--|--->|              |
               |    --------------     ----------------   |    | core1  core3 |
                -----------------------------------------       --------------

7.1 中断禁用和使能

local_irq_disable() /* 禁用 CPU 本地中断:某个 core 不接收中断输入信号 */
local_irq_enable() /* 禁用 CPU 本地中断:某个 core 接收中断输入信号 */

7.2 中断源屏蔽和使能

操作接口如下:

/* 仅列举两个典型接口,其它的读者可自行阅读源码 */
extern void disable_irq(unsigned int irq); /* 屏蔽中断信号源 @irq  */
extern void enable_irq(unsigned int irq); /* 使能中断信号源 @irq  */

中断源屏蔽是指屏蔽外设中断信号源的输入,这需要中断控制芯片的支持,具体来讲,就是中断芯片的 struct irq_chip 对象,需要实现 irq_disable/irq_mask 中的某个接口。如章节 4 例子中,级联 GPIO 中断控制器实现的 sunxi_pinctrl_irq_mask() 接口。
中断源使能是指开启外设中断信号源的输入,这需要中断控制芯片的支持,具体来讲,就是中断芯片的 struct irq_chip 对象,需要实现 irq_enable/irq_unmask 中的某个接口。如章节 4 例子中,级联 GPIO 中断控制器实现的 sunxi_pinctrl_irq_unmask() 接口。

8. 中断的 CPU 亲和性

中断的 CPU 亲和性是指系统将某个中断发送給哪些 CPU 核处理。中断亲和性配置,同样需要中断芯片的支持,具体来讲,就是中断芯片的 struct irq_chip 对象,需实现接口 irq_set_affinity 接口,如 GIC 驱动实现的 gic_set_affinity() 接口。
用户空间可通过如下接口之一,对中断的 CPU 亲和性进行配置:

/proc/irq/<N>/smp_affinity # 十六进制掩码形式配置接口,如 f
/proc/irq/<N>/smp_affinity_list # 列表形式配置接口,如 0-3

更多关于中断的 CPU 亲和性配置细节,可参考内核文档 Documentation/IRQ-affinity.txt
irqbalance,是一个有名的操作中断 CPU 亲和性的程序,该程序通过周期性读取中断当前统计数据,按中断在各 CPU 核上分布情况,配置各中断的 CPU 亲和性,力图将它们均匀的分布到各个 CPU 核上。

9. 中断信息用户空间接口

/proc/irq/default_smp_affinity

/proc/irq/<N>/affinity_hint
/proc/irq/<N>/effective_affinity
/proc/irq/<N>/effective_affinity_list
/proc/irq/<N>/node
/proc/irq/<N>/smp_affinity
/proc/irq/<N>/smp_affinity_list
/proc/irq/<N>/spurious

/sys/kernel/irq/<N>/actions
/sys/kernel/irq/<N>/chip_name
/sys/kernel/irq/<N>/hwirq
/sys/kernel/irq/<N>/name
/sys/kernel/irq/<N>/per_cpu_count
/sys/kernel/irq/<N>/type

/proc/interrupts

...

看一下 cat /proc/interrupts 输出,该文件输出了当前的中断统计信息:
请添加图片描述
上图中:

第1列,显示中断的 Linux虚拟中断号;
第2~5列,显示各中断在每个CPU 核上发生的次数;
第6列,显示中断信号发往的中断芯片名;
第7列,显示中断在信号发往的中断芯片内的硬件中断号;
第8列,显示中断的触发方式:电平触发、沿触发;
第9列,显示中断名称。
另外,第1列不是数字的行,显示的 SGI 中断的发生情况。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值