18 min read

inux中断-硬中断

0、前言

中断子系统有一次大改造,先mark一下

https://lore.kernel.org/all/20200521200513.656533920@linutronix.de/

arm64参考:Linux 中断管理机制

1、概述

广义上的中断主要分为同步中断和异步中断两种

  • 同步(synchronous)中断:当指令执行时由CPU控制单元产生的。
  • 异步(asynchronous)中断:由其他硬件设备依照CPU时钟信号随机产生的。

在x86和arm中,其实同步中断就是异常,而异步中断则是我们常说的中断(interrupt)。而我们本文中主要讨论的就是后者,异步中断。

也可以理解为异步中断是由中断控制器处理的,arm上是GIC(Generic Interrupt Controller),而x86上叫做APIC(Advanced Programmable Interrupt Controllers),而异步中断则是在CPU内完成的

2、硬件中断

个人理解认为,异步中断就是我们常提到的硬中断,由外设产生的中断信号,经由中断控制器交由对应的CPU进行处理。

而在linux中,我们又可以将硬件中断(异步中断)分为如下如下两类:

  • 可屏蔽中断:可以通过设置eflags寄存器的IF标志来屏蔽对应CPU的中断,sti开启中断,cli屏蔽中断。
    • 普通硬件中断:例如I/O设备完成数据传输、定时器到期等。
    • 核间中断:用于在多核处理器系统中实现核间通信。它们通常用于处理进程调度、进程迁移等任务。
  • 不可屏蔽中断:NMI通常用于处理关键错误和紧急任务,如硬件故障、系统崩溃等,需要立即得到处理,印象中很多BMC发过来的中断都是不可屏蔽中断。

3-中断控制器

3.1-APIC(x86)

可以参考:高级可编程中断控制器(APIC)

现代SMP架构的处理器的APIC由两部分组成,一个是local APIC,一个是I/O APIC。其中local-APIC是存在于CPU内的,外部的中断设备通常是通过I/O APIC设备,转发给对应的local APIC。

image-20240124193223874.png

通常来说LAPIC 主要处理以下中断:

  • APIC Timer 产生的中断(APIC timer generated interrupts)
  • Performance Monitoring Counter 在 overflow 时产生的中断(Performance monitoring counter interrupts)
  • 温度传感器产生的中断(Thermal Sensor interrupts)
  • LAPIC 内部错误时产生的中断(APIC internal error interrupts)
  • 本地直连 IO 设备 (Locally connected I/O devices) 通过 LINT0 和 LINT1 引脚发来的中断
  • 其他 CPU (甚至是自己,称为 self-interrupt)发来的 IPI(Inter-processor interrupts)
  • IOAPIC 发来的中断

而IOAPIC (I/O Advanced Programmable Interrupt Controller) 属于Intel芯片组的一部分。他负责连接外部的I\O设备,负责接收其发来的外部中断,通常IOAPIC 有 24 个 input 管脚(INTIN0~INTIN23),没有优先级之分。

3.1.1-APIC驱动

x86平台的APIC驱动主要放在arch/x86/kernel/apic/

3.2-GIC(ARM64)

对于ARM64来说,中断控制器被叫做GIC(Generic Interrupt Controller)

GIC的整体功能与APIC类似,但是对于GIC来说,是不存在IDT这个概念的,ARM64主要通过异常向量表来实现中断处理函数的处理。在GICv3(还没见过使用GICv4的soc)中中断主要分为如下几种

  • SGI (Software Generated Interrupt):

    软件生成的中断,用于处理器内核之间的通信。SGI是由软件显式触发的,通常用于实现进程调度、负载均衡等功能。一个处理器可以向其他处理器或自己发送SGI。在x86中被定义为IPI。

  • SPI (Shared Peripheral Interrupt):

    共享外设中断,用于处理与多个处理器核心共享的外设事件。SPI是系统范围内的中断,可以被路由到任何处理器。这些中断通常用于处理通用硬件事件,例如USB、网络、磁盘等。

  • PPI (Private Peripheral Interrupt):

    私有外设中断,用于处理与特定处理器核心关联的外设事件。PPI是针对单个处理器的,每个处理器都有自己的一组PPI。这些中断通常用于处理与特定处理器相关的硬件事件,例如定时器、错误检测等。

  • LPI (Locality-specific Peripheral Interrupt):

    局部特定外设中断,是GICv3引入的一种新中断类型。LPI用于处理与特定处理器组(例如NUMA节点)关联的外设事件。LPI相对于SPI具有更高的可扩展性,可以支持大量的中断源。这些中断通常用于处理大型多处理器系统中的硬件事件。

3.2.1-GIC驱动

GICv3的驱动主要在drivers/irqchip/irq-gic-v3.c中,GIC设备的初始化有两种方式,一种是通过dtsi设备树,一种是通过ACPI从BIOS里直接初始化,具体的调用如下

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

设备在初始化的过程中,或尝试解析对应的设备树文件,对GIC进行初始化

在设备树中会如下定义一个GIC,以rk3399具体说明(scripts/dtc/include-prefixes/arm64/rockchip/rk3399.dtsi)

	gic: interrupt-controller@fee00000 { # 定义一个名为gic的节点,表示一个位于0xfee00000地址的中断控制器。
		compatible = "arm,gic-v3";
		#interrupt-cells = <4>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		interrupt-controller; # 表示这个节点代表一个中断控制器。

		# 定义了GIC中的各个寄存器地址和大小。
		reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
		      <0x0 0xfef00000 0 0xc0000>, /* GICR */
		      <0x0 0xfff00000 0 0x10000>, /* GICC */
		      <0x0 0xfff10000 0 0x10000>, /* GICH */
		      <0x0 0xfff20000 0 0x10000>; /* GICV */
    # 定义了GIC的中断信息,这里指定了一个PPI类型的中断,中断号为9,触发类型为高电平触发。
		interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>; 
		# 定义了一个名为its的节点,表示一个位于0xfee20000地址的MSI控制器。
		its: msi-controller@fee20000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			#msi-cells = <1>;
			reg = <0x0 0xfee20000 0x0 0x20000>;
		};

		ppi-partitions { # 描述PPI中断的分区信息。定义了两个PPI分区:ppi_cluster0和ppi_cluster1。
			ppi_cluster0: interrupt-partition-0 { # 绑定在小核0-3上
				affinity = <&cpu_l0 &cpu_l1 &cpu_l2 &cpu_l3>;
			};

			ppi_cluster1: interrupt-partition-1 { # 绑定在大核0-1上
				affinity = <&cpu_b0 &cpu_b1>;
			};
		};
	};

这其中GIC的寄存器分别代表如下内容

  • GICD(GIC Distributor):GIC分发器,负责管理和分发中断。GICD维护了一个中断优先级表,可以将中断分发给特定的CPU或一组CPU。
  • GICR(GIC Redistributor):GIC再分发器,用于支持多个处理器。每个处理器都有一个关联的GICR,用于处理该处理器的SGI(Software Generated Interrupt)和PPI(Private Peripheral Interrupt)。
  • GICC(GIC CPU Interface):GIC CPU接口,提供了处理器和GIC之间的接口。处理器通过GICC接收中断,发送EOI(End of Interrupt)信号,以及获取当前正在处理的中断等。
  • GICH(GIC Virtual Interface):GIC虚拟接口,用于支持虚拟化。GICH提供了一个虚拟列表,用于存储来自虚拟机的中断。
  • GICV(GIC Virtual CPU Interface):GIC虚拟CPU接口,提供了虚拟机和GIC之间的接口。虚拟机通过GICV接收中断,发送EOI信号,以及获取当前正在处理的中断等。
  1. acpi

Linux内核可以通过ACPI来获取硬件信息和配置。Linux内核包含了一个ACPI解释器,可以解析和执行ACPI表中的AML代码。

IRQCHIP_ACPI_DECLARE(gic_v3, ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR,
		     acpi_validate_gic_table, ACPI_MADT_GIC_VERSION_V3,
		     gic_acpi_init);

4、中断映射

4.1-IDT x86

我们通过上述的期间可以实现中断硬件上的捕捉、分发,但是在linux中通常在接受到中断后还会继续执行软件操作,而内核则是通过中断描述符表(Interrupt Descriptor Table)来将不同的中断指向特定的程序入口,而中断处理的程序则被称为门(gates)。因此初始化IDT是内核启动阶段重要的工作。

IDT被CPU加载在idtr寄存器中,用以发生中断时分配中断的处理函数,通过idtr中的idt基地址+中断号偏移,从而获取中断处理函数的入口地址。

在x86架构的Linux内核中,IDT主要存储了中断向量与其对应的底层中断处理程序(也称为中断处理程序的入口点)之间的映射关系。当发生中断时,处理器会根据IDT找到对应的底层中断处理程序,并跳转到这个程序的入口点开始执行。然后,底层中断处理程序会调用内核的中断处理子系统,最后由内核的中断处理子系统调用相应的处理函数(例如my_irq_handler)来处理中断。

内核通过idt_setup_apic_and_irq_gates函数初始化IDT

void __init idt_setup_apic_and_irq_gates(void)
{
	int i = FIRST_EXTERNAL_VECTOR;
	void *entry;

	idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true); // 设置APIC and SMP idt,通常是错误中断、IPI

	for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) { // 将外部IRQ中断处理程序的入口设置到IDT中
		entry = irq_entries_start + IDT_ALIGN * (i - FIRST_EXTERNAL_VECTOR); // 中断处理函数入口
		set_intr_gate(i, entry);// 设置中断门
	}

#ifdef CONFIG_X86_LOCAL_APIC
	for_each_clear_bit_from(i, system_vectors, NR_VECTORS) { // 将本地APIC IRQ中断处理程序的入口设置到IDT中
		/*
		 * Don't set the non assigned system vectors in the
		 * system_vectors bitmap. Otherwise they show up in
		 * /proc/interrupts.
		 */
		entry = spurious_entries_start + IDT_ALIGN * (i - FIRST_SYSTEM_VECTOR); // 中断处理函数入口
		set_intr_gate(i, entry); // 设置中断门
	}
#endif
	......

	idt_setup_done = true; // 完成IDT的初始化设置
}

其中对应的底层处理函数入口具体被定义在了如下代码中

SYM_CODE_START(irq_entries_start)
    vector=FIRST_EXTERNAL_VECTOR
    .rept NR_EXTERNAL_VECTORS
	UNWIND_HINT_IRET_REGS
0 :
	ENDBR
	.byte	0x6a, vector
	jmp	asm_common_interrupt // 跳转到asm_common_interrupt标签对应的位置,这个位置定义了一段通用的IRQ处理程序
	/* Ensure that the above is IDT_ALIGN bytes max */
	.fill 0b + IDT_ALIGN - ., 1, 0xcc
	vector = vector+1
    .endr
SYM_CODE_END(irq_entries_start)
    
SYM_CODE_START(spurious_entries_start)
    vector=FIRST_SYSTEM_VECTOR
    .rept NR_SYSTEM_VECTORS
	UNWIND_HINT_IRET_REGS
0 :
	ENDBR
	.byte	0x6a, vector
	jmp	asm_spurious_interrupt
	/* Ensure that the above is IDT_ALIGN bytes max */
	.fill 0b + IDT_ALIGN - ., 1, 0xcc
	vector = vector+1
    .endr
SYM_CODE_END(spurious_entries_start)    

其中asm_common_interruptasm_spurious_interrupt被定义在了如下代码中

/* Device interrupts common/spurious */
DECLARE_IDTENTRY_IRQ(X86_TRAP_OTHER,	common_interrupt);
#ifdef CONFIG_X86_LOCAL_APIC
DECLARE_IDTENTRY_IRQ(X86_TRAP_OTHER,	spurious_interrupt);
#endif

继续通过宏展开将二者关联起来

#define DECLARE_IDTENTRY_IRQ(vector, func)				\
	DECLARE_IDTENTRY_ERRORCODE(vector, func)

#define DECLARE_IDTENTRY_ERRORCODE(vector, func)			\
	asmlinkage void asm_##func(void);				\
	asmlinkage void xen_asm_##func(void);				\
	__visible void func(struct pt_regs *regs, unsigned long error_code)

最后声明了一个名为common_interruptspurious_interrupt的函数,而这两个函数实际的定义则是

DEFINE_IDTENTRY_IRQ(common_interrupt)
{
	struct pt_regs *old_regs = set_irq_regs(regs); // 保存寄存器状态
	......
	desc = __this_cpu_read(vector_irq[vector]); // 获取vector中断号对应的中断描述符
	if (likely(!IS_ERR_OR_NULL(desc))) { // 如果中断描述符有效
		handle_irq(desc, regs); // 调用handle_irq处理函数,这里进一步的就会调用到注册过的irq回调函数
	} else {
		apic_eoi();

		if (desc == VECTOR_UNUSED) {
			pr_emerg_ratelimited("%s: %d.%u No irq handler for vector\n",
					     __func__, smp_processor_id(),
					     vector);
		} else {
			__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);// 当前CPU上的无效的中断描述符设置为VECTOR_UNUSED。
		}
	}

	set_irq_regs(old_regs); // 恢复寄存器状态
}

外部中断会先跳到common_interrupt的入口函数中,并且通过查找对应的vector_irq来获取中断描述符,再进一步通过注册中断的时候绑定的desc->handler来执行对应的中断处理函数。

以及

DEFINE_IDTENTRY_IRQ(spurious_interrupt)
{
	handle_spurious_interrupt(vector);
}

4.2-中断向量表arm64

在arm64中,多了一个irq_domian的概念,他是irq_desc->irq_data中的一个成员。

struct irq_data {
	u32			mask;
	unsigned int		irq;
	unsigned long		hwirq;
	struct irq_common_data	*common;
	struct irq_chip		*chip;
	struct irq_domain	*domain;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;
#endif
	void			*chip_data;
};

这里可以看到,irq就是中断在linux的中断虚拟中断号,而hwirq则是对应中断的实际中断号,irq_domain作为一个映射层,将硬件中断号映射到内核中的虚拟中断号。而硬件中断号hwirq则是定义在产生中断的设备所对应的设备树文件或者bios文件中的。

而中断映射的过程则是通过发生的硬件中断hwirq,找到内核对应的virq,并运行对应的中断处理函数。

而hwirq与virq的则是设备的驱动在初始化的时候可以调用 irq_of_parse_and_map 这个接口函数进行该 device node 中和中断相关的内容的解析,并建立映射关系。

irq_domain 中断号映射库

ARM64是通过异常向量表来实现中断的处理和映射的,其存在放VBAR_EL1VBAR_EL2之中。

而发生硬件中断后,则会根据中断向量表进行跳转,其被定义在了arch/arm64/kernel/entry.S

SYM_CODE_START(vectors)
	kernel_ventry	1, t, 64, sync		// Synchronous EL1t
	kernel_ventry	1, t, 64, irq		// IRQ EL1t
	kernel_ventry	1, t, 64, fiq		// FIQ EL1t
	kernel_ventry	1, t, 64, error		// Error EL1t

	kernel_ventry	1, h, 64, sync		// Synchronous EL1h
	kernel_ventry	1, h, 64, irq		// IRQ EL1h
	kernel_ventry	1, h, 64, fiq		// FIQ EL1h
	kernel_ventry	1, h, 64, error		// Error EL1h

	kernel_ventry	0, t, 64, sync		// Synchronous 64-bit EL0
	kernel_ventry	0, t, 64, irq		// IRQ 64-bit EL0
	kernel_ventry	0, t, 64, fiq		// FIQ 64-bit EL0
	kernel_ventry	0, t, 64, error		// Error 64-bit EL0

	kernel_ventry	0, t, 32, sync		// Synchronous 32-bit EL0
	kernel_ventry	0, t, 32, irq		// IRQ 32-bit EL0
	kernel_ventry	0, t, 32, fiq		// FIQ 32-bit EL0
	kernel_ventry	0, t, 32, error		// Error 32-bit EL0
SYM_CODE_END(vectors)

根据发生中断的级别不同,有不同的跳转逻辑,EL0下的IRQ会跳转到

asmlinkage void noinstr el0t_64_irq_handler(struct pt_regs *regs)
{
	__el0_irq_handler_common(regs);
}

EL1下的IRQ会跳转到

asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
	el1_interrupt(regs, handle_arch_irq);
}

5、中断注册

5.1-注册中断号

由于从硬件触发的中断对应着具体的中断号,这样才能在内核中通过注册对应中断号的软件处理函数,所以在连接好中断的硬件电路后,我需要为这个这个硬件中断分配一个中断号。

  • x86

对于x86来说,这个中断号是定义在BIOS里的,内核通过ACPI来获取对应设备的终端号。

  • ARM64

对于ARM64来说,通过设备树初始化的平台,可以通过定义在dtsi中的文件来解析具体的中断号

/* 从设备树节点中获取中断信息 */
int irq = irq_of_parse_and_map(dev_node, 0); // 第二个参数0表示获取第一个中断信息

对于使用能了ACPI的ARM设备,也会同x86一样,通过ACPI接口直接从BIOS中获取对应设备的中断号。

以UFS设备的中断注册为例

int platform_get_irq_optional(struct platform_device *dev, unsigned int num)
{
	int ret;
#ifdef CONFIG_SPARC
	/* sparc does not have irqs represented as IORESOURCE_IRQ resources */
	if (!dev || num >= dev->archdata.num_irqs)
		goto out_not_found;
	ret = dev->archdata.irqs[num];
	goto out;
#else
	struct fwnode_handle *fwnode = dev_fwnode(&dev->dev);
	struct resource *r;

	if (is_of_node(fwnode)) { // 如果是设备树节点
		ret = of_irq_get(to_of_node(fwnode), num); // 通过设备获取中断号
		if (ret > 0 || ret == -EPROBE_DEFER)
			goto out;
	}

	r = platform_get_resource(dev, IORESOURCE_IRQ, num);
	if (is_acpi_device_node(fwnode)) { // 如果是acpi节点
		if (r && r->flags & IORESOURCE_DISABLED) {
			ret = acpi_irq_get(ACPI_HANDLE_FWNODE(fwnode), num, r); // 通过ACPU获取中断号
			if (ret)
				goto out;
		}
	}
	......

#endif
out_not_found:
	ret = -ENXIO;
out:
	if (WARN(!ret, "0 is an invalid IRQ number\n"))
		return -EINVAL;
	return ret;
}
EXPORT_SYMBOL_GPL(platform_get_irq_optional);

5.2-注册对应中断处理函数

在我们获取到了具体设备中断所对应的中断号后,就可以利用这个中断号对中断处理函数进行注册。

#include <linux/interrupt.h>
#include <linux/kernel.h>

static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    printk(KERN_INFO "Interrupt handled: irq %d\n", irq);
    return IRQ_HANDLED;
}
#include <linux/interrupt.h>
#include <linux/module.h>

static int my_irq = 123; // Replace with the actual IRQ number

static int __init my_module_init(void)
{
    int ret;

    ret = request_irq(my_irq, my_interrupt_handler, IRQF_SHARED, "my_interrupt", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to register interrupt handler: %d\n", ret);
        return ret;
    }

    printk(KERN_INFO "Registered interrupt handler for IRQ %d\n", my_irq);
    return 0;
}

static void __exit my_module_exit(void)
{
    free_irq(my_irq, NULL);
    printk(KERN_INFO "Unregistered interrupt handler for IRQ %d\n", my_irq);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");

6、中断处理流程

6.1-x86

6.2-arm64

发生一个 EL0的irq时,根据中断向量表

kernel_ventry	0, t, 64, irq		// IRQ 64-bit EL0

可以看到会跳转到制定的汇编代码,并传入特定的参数

	.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
	.align 7
.Lventry_start\@:
	.if	\el == 0
	/*
	 * This must be the first instruction of the EL0 vector entries. It is
	 * skipped by the trampoline vectors, to trigger the cleanup.
	 */
	b	.Lskip_tramp_vectors_cleanup\@
	.if	\regsize == 64
	mrs	x30, tpidrro_el0
	msr	tpidrro_el0, xzr
	.else
	mov	x30, xzr
	.endif
.Lskip_tramp_vectors_cleanup\@:
	.endif

	sub	sp, sp, #PT_REGS_SIZE
#ifdef CONFIG_VMAP_STACK
	/*
	 * Test whether the SP has overflowed, without corrupting a GPR.
	 * Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
	 * should always be zero.
	 */
	add	sp, sp, x0			// sp' = sp + x0
	sub	x0, sp, x0			// x0' = sp' - x0 = (sp + x0) - x0 = sp
	tbnz	x0, #THREAD_SHIFT, 0f
	sub	x0, sp, x0			// x0'' = sp' - x0' = (sp + x0) - sp = x0
	sub	sp, sp, x0			// sp'' = sp' - x0 = (sp + x0) - x0 = sp
	b	el\el\ht\()_\regsize\()_\label

保存完寄存器状态后,则会跳转到el0t_64_irq_handler->__el0_irq_handler_common->el0_interrupt

static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
{
	el0_interrupt(regs, handle_arch_irq);
}

这里可以看到有绑定了一个中断的回调函数,这里的回调是对应的gic的处理函数,在gic初始化的过程中绑定

static int __init gic_init_bases(phys_addr_t dist_phys_base,
				 void __iomem *dist_base,
				 struct redist_region *rdist_regs,
				 u32 nr_redist_regions,
				 u64 redist_stride,
				 struct fwnode_handle *handle)
{
  ......
	set_handle_irq(gic_handle_irq);
  ......
}

通过gic_handle_irq->__gic_handle_irq_from_irqson进一步调用

static void __gic_handle_irq_from_irqson(struct pt_regs *regs)
{
	bool is_nmi;
	u32 irqnr;

	irqnr = gic_read_iar(); // 获取当前中断的硬件中断号

	is_nmi = gic_rpr_is_nmi_prio();

	if (is_nmi) {
		nmi_enter();
		__gic_handle_nmi(irqnr, regs); // 如果是nmi中断执行特定函数
		nmi_exit();
	}

	if (gic_prio_masking_enabled()) {
		gic_pmr_mask_irqs();
		gic_arch_enable_irqs();
	}

	if (!is_nmi)
		__gic_handle_irq(irqnr, regs); // 进一步执行中断调用
}

__gic_handle_irq中则利用irqnr进一步获取中断的号

static void __gic_handle_irq(u32 irqnr, struct pt_regs *regs)
{
	if (gic_irqnr_is_special(irqnr))
		return;

	gic_complete_ack(irqnr);

	if (generic_handle_domain_irq(gic_data.domain, irqnr)) {
		WARN_ONCE(true, "Unexpected interrupt (irqnr %u)\n", irqnr);
		gic_deactivate_unhandled(irqnr);
	}
}

int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
{
	return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
}
EXPORT_SYMBOL_GPL(generic_handle_domain_irq);

而在irq_resolve_mapping(domain, hwirq)则是通过domain,来查找hwirq对应的irq_desc,最终再通过最初绑定的handler来调用真正的中断处理函数。

8、irq优先级

对于linux kernel而言,本身是不存在中断优先级区分的,这部分工作则由中断控制器来实现,通过编程设置不同中断的优先级,来实现接收到的外部中断按照优先级发送给CPU进一步响应。

在arm64中,gicv3是在如下位置进行的中断优先级设定

gic_dist_init->gic_dist_config

void gic_dist_config(void __iomem *base, int gic_irqs,
		     void (*sync_access)(void))
{
	unsigned int i;

	/*
	 * Set all global interrupts to be level triggered, active low.
	 */
	for (i = 32; i < gic_irqs; i += 16)
		writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
					base + GIC_DIST_CONFIG + i / 4);

	/*
	 * Set priority on all global interrupts.
	 */
	for (i = 32; i < gic_irqs; i += 4)
		writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i); // 从32号中断开始,将所有的ppi外部中断设定为一样的优先级
  ......
}

需要补充说明的是,这里处理的其实都是irq优先级,这与中断的优先级是包含关系的,在arm64中:

中断和异常有一个固定的优先级顺序,这个优先级顺序是由ARM的硬件设计决定的,不同的中断和异常类型有不同的优先级。当多个中断或异常同时发生时,硬件会按照这个优先级顺序来决定首先处理哪一个。

以下是ARM架构中的中断和异常的优先级顺序:

  1. 复位(Reset):这是最高优先级的异常,当系统复位时会触发这个异常。
  2. 数据中止(Data Abort):这个异常是由于内存访问错误(如非法访问、访问未对齐等)引起的。
  3. FIQ(Fast Interrupt):这是一种高优先级的中断,通常用于需要快速响应的硬件事件。
  4. IRQ(Interrupt Request):这是一种普通优先级的中断,用于处理常规的硬件事件。
  5. 预取中止(Prefetch Abort):这个异常是由于指令预取错误(如访问非法地址等)引起的。
  6. 未定义指令(Undefined Instruction)和软件中断(Software Interrupt):这两种异常的优先级是最低的,当执行一条未定义的指令或者软件触发一个中断时,会产生这两种异常。