Linux中断子系统5

Linux中断子系统5(基于Linux6.6)---初始化中断控制器?

 


一、前情回顾

使用设备树的情况下,对所有的中断控制器进行了检索,提取到一个链表中。并对这些中断控制器中所谓的父中断控制器进行检索,并按照等级一次初始化。以gic为例:

 void __init of_irq_init(const struct of_device_id *matches)
{
    ......
 
	ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
 
    ......
}

二、什么是GIC

GIC 在 ARM 架构中通常有两个主要版本:GICv2GICv3,其中 GICv3 是最新的版本。

2.1、GICv2

GICv2 是较早的版本,支持单核或多核的 ARM 处理器,能够为每个处理器核提供中断处理能力。GICv2 通常由两个主要部分组成:

  • CPU Interface(CPU 接口):与处理器的每个核心连接,负责处理中断并将中断事件传递给 CPU。
  • Distributor(分配器):负责从外部设备接收中断请求,并将它们分配到 CPU 接口。

GICv2 在多核系统中工作时,所有 CPU 核共享一个分配器,但每个 CPU 核都有自己的接口。

2.2、GICv3

GICv3 引入了许多新的功能和改进,尤其是支持 ARMv8 架构中的 64 位处理器。与 GICv2 相比,GICv3 提供了更强大的功能,主要包括:

  • 更高效的中断路由:GICv3 允许中断在不同的 CPU 核之间动态地路由,并且支持为不同的核心配置不同的中断优先级。
  • 虚拟化支持:GICv3 为虚拟化环境提供了更好的支持,允许虚拟机(VM)和物理机共享中断资源。GICv3 引入了虚拟化控制器(VIC)来管理虚拟机的中断。
  • 增强的中断优先级控制:GICv3 提供了更灵活的中断优先级机制,支持更细粒度的优先级划分。

2.3、GICv3 框图结构

                   +-------------------+
                   |    Distributor    |  <-------------------+
                   |    (GICv3)        |                       |
                   +-------------------+                       |
                           |                                     |
                           |                                     |
            +--------------+--------------+                      |
            |                             |                      |
  +------------------+             +------------------+           |
  |  Interrupt       |             |  Interrupt       |           |
  |  Source          |             |  Source          |           |
  |  (External,      |             |  (External,      |           |
  |   Internal)      |             |   Internal)      |           |
  +------------------+             +------------------+           |
            |                             |                      |
  +------------------+             +------------------+           |
  |   Interrupt      |             |   Interrupt      |           |
  |   Line/Signal    |             |   Line/Signal    |           |
  |   (IRQ)          |             |   (IRQ)          |           |
  +------------------+             +------------------+           |
            |                             |                      |
   +-------------------+         +-------------------+            |
   |  Interrupt        |         |  Interrupt        |            |
   |  Controller/     |         |  Controller/      |            |
   |  (Processor      |         |  (Processor       |            |
   |  Core Interface) |         |  Core Interface)  |            |
   +-------------------+         +-------------------+            |
            |                             |                      |
   +-------------------+         +-------------------+            |
   | CPU Core 0        |         | CPU Core 1        |            |
   |                   |         |                   |            |
   +-------------------+         +-------------------+            |
            |                             |                      |
   +-------------------+         +-------------------+            |
   | CPU Core N        |         | CPU Core N        |            |
   |                   |         |                   |            |
   +-------------------+         +-------------------+            |
            |                             |                      |
           .                               .                      .
           .                               .                      .
           .                               .                      .

2.4、关键组件解释

  1. Distributor(分配器)

    • 这是 GIC 的核心部分,负责接收来自外部或内部设备的中断请求,并根据优先级、目标处理器等规则,将中断请求分发给相应的 CPU 核。Distributor 管理着中断的启用、禁用、路由、优先级等配置。
  2. Interrupt Sources(中断源)

    • 外部设备(如 I/O 设备、网络接口、外部总线等)和内部设备(如定时器、处理器内部异常等)产生中断请求。每个中断源通过中断线路(IRQ)向 GIC 的 Distributor 发出信号。
  3. Interrupt Lines/Signals(中断线路/信号)

    • 这些是硬件设备或系统内部各个部件产生的中断信号(IRQ)。这些信号将会传递到 GIC 的 Distributor。
  4. CPU Core Interface(CPU 核心接口)

    • 每个 CPU 核都有一个 CPU 接口,它负责接收来自 Distributor 的中断请求并将其传递给 CPU。每个 CPU 核接口可以独立配置,以处理从 Distributor 路由过来的中断信号。
  5. CPU Cores(处理器核心)

    • 处理器中的每个核心都可以通过 CPU 核心接口接收并处理中断。当中断被传递给某个核心时,处理器会暂停当前任务,转而执行中断服务例程(ISR)。

GICv3(Generic Interrupt Controller version 3)是 ARM 架构中用于中断管理的关键组件,它通过寄存器来控制中断的路由、优先级、使能等操作。GICv3 的寄存器可以大致分为几个主要部分,涵盖了 分配器(Distributor)CPU 接口(CPU Interface) 两个主要功能模块。

2.5、Distributor 寄存器

Distributor 部分负责管理中断的分发,包括中断使能、优先级设置、目标处理器选择等。主要寄存器包括:

(1) Control Register (GICD_CTLR)
  • 地址:0x0
  • 描述:控制 Distributor 的启用与禁用。它的主要作用是控制 GIC Distributor 的工作模式。
    • 位 [0]:ENABLE:启用或禁用中断分配器。
(2) Type Register (GICD_TYPER)
  • 地址:0x4
  • 描述:指示 GIC 分配器支持的中断数目、CPU 核心数等信息。
    • 位 [31:10]:LSB:表示支持的中断线的数量。
    • 位 [9:0]:CPUS:表示支持的 CPU 核心数。
(3) Interrupt Enable/Disable Registers (GICD_ISENABLER, GICD_ICENABLER)
  • 地址:0x100 - 0x17C(每个中断源的启用/禁用寄存器)
  • 描述:分别用来启用或禁用特定中断源(IRQ)。这些寄存器允许对中断进行按位启用或禁用。
    • 启用:GICD_ISENABLER,每个中断源对应一个位。
    • 禁用:GICD_ICENABLER,每个中断源对应一个位。
(4) Interrupt Set-Pending/ Clear-Pending Registers (GICD_ISPENDR, GICD ICPENDR)
  • 地址:0x200 - 0x27C
  • 描述:用于设置或清除中断的挂起状态。通过这些寄存器,可以手动触发或清除某个中断的挂起位。
    • 设置挂起:GICD_ISPENDR
    • 清除挂起:GICD_ICPENDR
(5) Interrupt Priority Registers (GICD_IPRIORITYR)
  • 地址:0x400 - 0x7F8
  • 描述:设置中断的优先级。每个中断源(IRQ)有一个对应的优先级,越小的值表示越高的优先级。每个寄存器控制多个中断的优先级。
    • 每个中断的优先级占用 8 位。
(6) Interrupt Target Registers (GICD_ITARGETSR)
  • 地址:0x800 - 0x9F8
  • 描述:设置每个中断源的目标处理器(CPU)。可以将中断分发到特定的 CPU 核心。
    • 每个寄存器对应 4 个中断源。
(7) Configuration Registers (GICD_ICFGR)
  • 地址:0xC00 - 0xCFC
  • 描述:设置中断的触发方式(边沿触发或电平触发)。每个寄存器控制多个中断的触发方式。

2.6、CPU Interface 寄存器

每个 CPU 核心都有自己的 CPU 接口,这部分负责接收从 Distributor 分发来的中断请求并将其送给处理器。主要寄存器包括:

(1) Control Register (GICC_CTLR)
  • 地址:0x0
  • 描述:控制 CPU 接口的启用、禁用,以及相关的中断处理模式。
    • 位 [0]:ENABLE:启用或禁用 CPU 接口。
    • 位 [3]:FIQEN:启用或禁用快速中断(FIQ)模式。
(2) Interrupt Priority Register (GICC_PRIORITYR)
  • 地址:0x4
  • 描述:设置当前 CPU 的中断优先级。每个 CPU 核都有一个独立的优先级寄存器。
(3) Interrupt Acknowledge Register (GICC_IAR)
  • 地址:0xC
  • 描述:用于在 CPU 接口中确认收到的中断。每个 CPU 核有一个 IAR,CPU 通过该寄存器读取中断 ID 并确认。
    • 返回值:当前中断 ID。
(4) End of Interrupt Register (GICC_EOIR)
  • 地址:0x10
  • 描述:用于告知 GIC 已完成对中断的处理。当 CPU 处理完一个中断后,必须写入此寄存器来清除中断。
(5) Run-Locked Register (GICC_RPR)
  • 地址:0x14
  • 描述:该寄存器用于读取中断处理程序的优先级(即当前正在执行的中断的优先级)。
(6) Running Priority Register (GICC_RPR)
  • 地址:0x14
  • 描述:这个寄存器可以读取当前正在执行的中断的优先级。

2.7、重要的中断管理寄存器

(1) CPU Interface Pending Register (GICC_HPPIR)
  • 地址:0x18
  • 描述:此寄存器返回当前 CPU 核所挂起的最高优先级的中断 ID。
(2) Binary Point Register (GICC_BPR)
  • 地址:0x20
  • 描述:用于设置 CPU 核的中断屏蔽级别。控制 CPU 能够接收的最小优先级。
(3) Software Generated Interrupt Register (GICC_SGI)
  • 地址:0xF00
  • 描述:该寄存器用于生成软件中断(SGI),即 CPU 可以通过软件触发中断。

三、初始化一个中断控制器

drivers/of/irq.c

 void __init of_irq_init(const struct of_device_id *matches)
{
    ......
 
    ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
 
    ......
}

drivers/irqchip/irq-gic-v3.c

 

static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
	phys_addr_t dist_phys_base;
	void __iomem *dist_base;
	struct redist_region *rdist_regs;
	struct resource res;
	u64 redist_stride;
	u32 nr_redist_regions;
	int err, i;

	dist_base = gic_of_iomap(node, 0, "GICD", &res);
	if (IS_ERR(dist_base)) {
		pr_err("%pOF: unable to map gic dist registers\n", node);
		return PTR_ERR(dist_base);
	}

	dist_phys_base = res.start;

	err = gic_validate_dist_version(dist_base);
	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;
	}

	for (i = 0; i < nr_redist_regions; i++) {
		rdist_regs[i].redist_base = gic_of_iomap(node, 1 + i, "GICR", &res);
		if (IS_ERR(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);

	err = gic_init_bases(dist_phys_base, 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 && !IS_ERR(rdist_regs[i].redist_base))
			iounmap(rdist_regs[i].redist_base);
	kfree(rdist_regs);
out_unmap_dist:
	iounmap(dist_base);
	return err;
}

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

drivers/irqchip/irq-gic-v3.c

static void __iomem *gic_of_iomap(struct device_node *node, int idx,
				  const char *name, struct resource *res)
{
	void __iomem *base;
	int ret;

	ret = of_address_to_resource(node, idx, res);//获取reg资源
	if (ret)
		return IOMEM_ERR_PTR(ret);

	gic_request_region(res->start, resource_size(res), name);
	base = of_iomap(node, idx);//映射reg资源

	return base ?: IOMEM_ERR_PTR(-ENOMEM);
}

3.1、读ID寄存器

GICv3 的 ID 寄存器 用于提供 GIC 版本和配置信息,通常有以下几个重要的寄存器:

  1. GICD_TYPER (Distributor Type Register)

    • 地址:0x4
    • 描述:用于获取 GIC Distributor 的特性信息,如支持的中断数、CPU 核心数等。
      • 位 [31:10]:LSB:表示中断行的数量。
      • 位 [9:0]:CPUS:表示支持的 CPU 核心数。
  2. GICC_IAR (Interrupt Acknowledge Register)

    • 地址:0xC
    • 描述:用于读取当前激活的中断 ID。此寄存器返回正在处理的中断的 ID。
  3. GICC_HPPIR (Highest Priority Pending Interrupt Register)

    • 地址:0x18
    • 描述:用于返回当前 CPU 核上挂起的最高优先级的中断 ID。

这些寄存器有助于获取 GIC 的基本信息以及处理中断时的相关 ID。

3.2、版本信息

可以看到和printk打印的信息一样。

3.3、关闭中断

在 GIC(Generic Interrupt Controller)中,关闭中断(禁用中断)通常是通过对特定的寄存器进行操作。不同于 VIC(Vectored Interrupt Controller),GIC 的中断控制通过 Distributor 和 CPU 接口的寄存器来实现。

在 GICv3 中,禁用中断的操作通常涉及以下几个方面:

1. 禁用特定中断(通过 Distributor 部分)

GIC 中的中断是通过 GICD(GIC Distributor)部分进行管理的。要禁用中断,通常需要操作 GICD_ICENABLER(Interrupt Clear Enable Register)寄存器。

操作步骤:
  • 禁用某个中断:通过设置 GICD_ICENABLER 中相应的位来禁用一个中断。
  • 寄存器格式GICD_ICENABLER 的每一位对应一个中断源,设置为 1 表示禁用该中断。

例如,禁用中断 n(假设中断 n 的编号为 n):

uint32_t reg_offset = 0x180 + (n / 32) * 4;   // 0x180 是 GICD_ICENABLER 寄存器的基地址
uint32_t bit = (1 << (n % 32));                // 获取中断 n 对应的位
*(volatile uint32_t *)(GICD_BASE + reg_offset) = bit;

2. 禁用 CPU 接口中的中断(通过 GICC)

在 GIC 中,CPU 接口的部分负责管理 CPU 上的中断。要禁用某个中断,可以使用 GICC_ICPENDRGICC_IAR(Interrupt Acknowledge Register)寄存器。

操作步骤:
  • 通过对 GICC 中的寄存器进行操作,确认中断被正确处理和禁用。

3. 禁用所有中断

如果你想禁用所有的中断,可以通过以下方式:

  • 禁用所有的挂起中断:可以通过写 1GICD_ICENABLER 中的所有位来禁用所有的中断。

例如:

*(volatile uint32_t *)(GICD_BASE + 0x180) = 0xFFFFFFFF;  // 禁用所有 32 个中断
  • 禁用特定 CPU 的所有中断:通过 GICC_CTLR 控制位来禁用某个 CPU 上的中断处理。
*(volatile uint32_t *)(GICC_BASE + 0x0) &= ~0x1;  // 通过清除 GICC_CTLR 的启用位来禁用 CPU 上的中断

GICv3(Generic Interrupt Controller version 3)中,清除中断通常不是通过一个专门的函数,而是通过操作相应的寄存器来完成的。清除中断的操作主要依赖于以下寄存器:

4. GICD_ICPENDR (Interrupt Clear-Pending Register) - Distributor 部分

  • 地址0x280 起,每个中断有一个对应的寄存器。
  • 作用:用于清除指定中断的挂起状态(即,将该中断标记为已处理,避免它再次被触发)。
  • 操作:写入相应的位将中断从挂起状态中清除。

例如:

  • 对于中断源 n,要清除其挂起状态,你需要写 1GICD_ICPENDR[n/32] 中的相应位。

5. GICC_EOIR (End of Interrupt Register) - CPU 接口部分

  • 地址0x10
  • 作用:通知 GIC 当前 CPU 已经完成对某个中断的处理。写入中断 ID 到此寄存器,表示该中断已经处理完毕。
  • 操作:当 CPU 完成对某个中断的处理时,必须写入该中断的 ID 到 GICC_EOIR,以清除该中断并允许处理下一个中断。

例如:

  • 假设当前处理中断的 ID 为 id,那么你需要执行:
  • *(volatile uint32_t *)(GICC_BASE + GICC_EOIR) = id;
    
    这会清除中断并使 GIC 知道该中断已经结束。

6. GICD_ISPENDR (Interrupt Set-Pending Register) 和 GICD_ICENABLER (Interrupt Clear Enable Register)

  • 这些寄存器用于控制中断的挂起和使能状态。在清除中断时,GICD_ISPENDR 设置和 GICD_ICENABLER 清除用于确保中断不会再次触发,直到被重新使能。

代码示例

在 GICv3 中,清除中断的操作一般如下:

清除 Distributor 部分的挂起中断
// 清除中断 n 的挂起状态
uint32_t reg_offset = 0x280 + (n / 32) * 4;
uint32_t bit = (1 << (n % 32));
*(volatile uint32_t *)(GICD_BASE + reg_offset) = bit;
清除 CPU 接口的处理中断
// 假设当前处理中断的 ID 为 id
*(volatile uint32_t *)(GICC_BASE + GICC_EOIR) = id;

 

3.4、中断控制器的注册

arch/arm64/boot/dts/rockchip/rk3568.dtsi 

	gic: interrupt-controller@fd400000 {
		compatible = "arm,gic-v3";
		#interrupt-cells = <3>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		interrupt-controller;

		reg = <0x0 0xfd400000 0 0x10000>, /* GICD */
		      <0x0 0xfd460000 0 0xc0000>; /* GICR */
		interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
		its: interrupt-controller@fd440000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			#msi-cells = <1>;
			reg = <0x0 0xfd440000 0x0 0x20000>;
		};
	};

在 GIC(Generic Interrupt Controller)中,与 VIC(Vectored Interrupt Controller)不同,它没有像 vic_register 这样的函数用于注册中断。然而,GIC 的中断管理过程通常涉及一系列配置寄存器和操作,这些操作是通过不同的初始化和配置函数完成的。

具体来说,GIC 的中断控制和配置可以通过以下几个步骤来完成:

3.4.1、GIC 中断注册和初始化的主要步骤

  1. GIC 初始化: 在使用 GIC 之前,需要先对 GIC 进行初始化。通常这包括设置 GIC 的分发器部分(GICD)和 CPU 接口部分(GICC)。

    初始化过程中,常见的步骤包括:

    • 配置中断源(IRQ)到相应的 CPU 和优先级。
    • 配置中断的触发模式(边沿触发或电平触发)。
    • 启动 CPU 接口,以允许 CPU 处理中断。

    一个典型的 GIC 初始化代码(伪代码)如下:

  • void gic_init(void) {
        // 初始化 GIC Distributor
        GICD_CTLR = 0; // 禁用 GICD
        // 配置中断源
        configure_interrupts();
        GICD_CTLR = 1; // 启用 GICD
    
        // 初始化 CPU 接口
        GICC_CTLR = 0; // 禁用 CPU 接口
        GICC_PMR = 0xF0; // 设置 CPU 接口的中断优先级掩码
        GICC_CTLR = 1; // 启用 CPU 接口
    }
    
  • 中断源配置: 对于每个中断源,需要通过配置 GICD 的相关寄存器来注册中断。这些寄存器包括:

    • GICD_ISENABLER:启用中断源。
    • GICD_ICENABLER:禁用中断源。
    • GICD_ICPENDR:清除挂起的中断。

    你可以使用类似下面的代码来启用一个中断源:

  • void gic_enable_interrupt(int irq_number) {
        // 根据 IRQ 编号配置 GICD_ISENABLER 寄存器,启用中断
        uint32_t reg_offset = 0x100 + (irq_number / 32) * 4;
        uint32_t bit = (1 << (irq_number % 32));
        *(volatile uint32_t *)(GICD_BASE + reg_offset) = bit;
    }
    
  • 中断处理程序的注册: 虽然 GIC 没有 vic_register 这样的 API,通常情况下,你需要手动编写中断服务例程(ISR)并将其与特定的中断号绑定。这个过程通常涉及以下几个步骤:

    • 在特定的中断向量表中定义 ISR。
    • 使用中断控制器的 API 来确保中断源与 ISR 绑定。

    在某些嵌入式操作系统或中间件中,可能会提供封装了 GIC 配置和 ISR 注册的高级 API。比如,FreeRTOS 或 Linux 会使用一些抽象层来处理 GIC 的中断注册。

  • 中断触发和处理: 一旦配置完毕,GIC 会自动将中断传递给相应的 CPU。如果是 ARM 的 GICv3,CPU 会通过读取 GICC_IAR(Interrupt Acknowledge Register)来获取正在处理中断的编号,并在中断服务程序中进行相应处理。

    中断处理完成后,CPU 会通过 GICC_EOIR(End of Interrupt Register)来结束中断处理,并通知 GIC 中断已被处理。

void handle_irq(void) {
    int irq = read_gicc_iar();
    if (irq == YOUR_INTERRUPT_NUMBER) {
        // 处理你的中断
    }
    write_gicc_eoir(irq); // 通知 GIC 中断处理完毕
}

3.5、中断级联

kernel/irq/chip.c
 void
irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handle,
				 void *data)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);    //这里获取到父中断的描述符
 
	if (!desc)
		return;
 
	desc->irq_common_data.handler_data = data;    //data就是这里的中断控制器结构体,代表一个中断控制器
	__irq_do_set_handler(desc, handle, 1, NULL);  //把中断级联的函数,绑定到它的父中断的某个中断源上
 
	irq_put_desc_busunlock(desc, flags);
}



 kernel/irq/chip.c
//绑定这中断控制器到,父中断控制器上
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
		     int is_chained, const char *name)
{
	if (!handle) {
		handle = handle_bad_irq;
	} else {
		struct irq_data *irq_data = &desc->irq_data;    //获取父中断的中断控制器
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY        //定义这个才会是级联中断控制器绑定,否则还是水平级联
		/*
		 * With hierarchical domains we might run into a
		 * situation where the outermost chip is not yet set
		 * up, but the inner chips are there.  Instead of
		 * bailing we install the handler, but obviously we
		 * cannot enable/startup the interrupt at this point.
		 */
        //这边是想要找到一个有用的父中断控制器(如果A的父亲没,检查A的爷爷...)
		while (irq_data) {
			if (irq_data->chip != &no_irq_chip)
				break;
			/*
			 * Bail out if the outer chip is not set up
			 * and the interrrupt supposed to be started
			 * right away.
			 */
			if (WARN_ON(is_chained))
				return;
			/* Try the parent */
			irq_data = irq_data->parent_data;
		}
#endif
		if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
			return;
	}
 
	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->irq_data.chip != &no_irq_chip)
			mask_ack_irq(desc);
		irq_state_set_disabled(desc);
		if (is_chained)
			desc->action = NULL;
		desc->depth = 1;
	}
	desc->handle_irq = handle;    //绑定中断控制器,绑定到父中断描述符的高级中断控制器上(代表这里是一个子中断控制器,而不是外设)
	desc->name = name;            //绑定名字
 
	if (handle != handle_bad_irq && is_chained) {
        //获取触发器类型
		unsigned int type = irqd_get_trigger_type(&desc->irq_data);
 
		/*
		 * We're about to start this interrupt immediately,
		 * hence the need to set the trigger configuration.
		 * But the .set_type callback may have overridden the
		 * flow handler, ignoring that we're dealing with a
		 * chained interrupt. Reset it immediately because we
		 * do know better.
		 */
		if (type != IRQ_TYPE_NONE) {
			__irq_set_trigger(desc, type);    //非NONE则,设置触发器
			desc->handle_irq = handle;        //设置高级函数
		}
 
        //这是一个中断设备,已经被初始化(使用了),所以不能在probe,request,线程话等等
		irq_settings_set_noprobe(desc);
		irq_settings_set_norequest(desc);
		irq_settings_set_nothread(desc);
		desc->action = &chained_action;       //这里先把中断执行函数设置为无效
		irq_activate_and_startup(desc, IRQ_RESEND);
	}
}

绑定到父中断控制器上的函数是什么?
上面对所有中断控制器级联方式绑定都一样,下面这个函数,就是根据具体要绑定的中断控制器来实现。

在 GICv3 (Generic Interrupt Controller v3) 中,vic_handle_irq_cascaded 是在 VIC (Vectored Interrupt Controller) 中处理级联中断的函数,而 GICv3 则采用不同的方式来处理级联中断。在 GICv3 中,处理级联中断的方式通常通过中断控制器的硬件寄存器和特定的 API 来管理。

GICv3 级联中断处理

在 ARM GICv3 中,级联中断通常是指在多个 GIC 中断控制器之间的中断传递,例如主 GIC (GIC Distributor) 和 CPU 处理器的接口(GICC)。在这种结构中,每个中断控制器可能会处理一部分中断源,然后通过一定的级联方式将它们传递给下一个层级的中断控制器或 CPU 接口。

中断级联处理的基本步骤

在 GICv3 中,CPU 通常会读取来自 GIC 的 Interrupt Acknowledge RegisterGICC_IAR),然后处理中断,最后通过 End of Interrupt RegisterGICC_EOIR)来结束中断。具体的级联方式取决于 GICv3 的设计,通常涉及以下几个方面:

  1. GIC Distributor(GICD):它负责处理各个中断源的分发、优先级设置、使能控制等。它还负责管理多个 CPU 接口的中断。

  2. CPU Interface(GICC):每个 CPU 接口(GICC)负责处理 CPU 内部的中断,读取中断源、响应中断,并最终通过执行 ISR 处理该中断。

  3. 中断源的分配和触发:GICv3 中的中断可以通过 SPI (Shared Peripheral Interrupts)PPI (Private Peripheral Interrupts)SGI (Software Generated Interrupts) 等不同的类型来处理。级联中断通常指的是 GICD 和 GICC 之间的交互,其中 GICD 将 SPI 中断分发到 CPU,CPU 通过 GICC 接口来处理中断。

处理级联中断

在 GICv3 的处理中,当中断从 GICD 级联到 GICC 后,CPU 需要通过以下步骤来处理中断:

  1. 中断请求处理

    • CPU 接口(GICC)会根据中断的优先级和触发方式来检查是否有挂起的中断。它会从 GICC_IAR(Interrupt Acknowledge Register)中获取当前触发的中断源。
  2. 处理中断

    • 一旦中断被识别,CPU 会根据中断的号,调用相应的中断服务程序(ISR)。
  3. 结束中断

    • 处理中断后,CPU 需要通过 GICC_EOIR(End of Interrupt Register)通知 GIC 该中断已处理完毕,从而允许 GIC 继续处理其他中断。
级联中断的实际代码实现

在处理级联中断时,通常 CPU 会通过 GICC_IAR 来获取中断号,之后通过调用相应的 ISR 来处理中断。这个过程类似于 VIC 中 vic_handle_irq_cascaded 的功能,但在 GICv3 中,通常通过操作相关的寄存器来完成。

下面是一个 GICv3 级联中断处理的简化伪代码:

  
drivers/irqchip/irq-gic-v3.c
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
		__gic_handle_irq_from_irqsoff(regs);
	else
		__gic_handle_irq_from_irqson(regs);
}
 
 
include/linux/irqdesc.h
static inline void *irq_desc_get_chip_data(struct irq_desc *desc)
{
	return desc->irq_data.chip_data;
}

static inline void *irq_desc_get_handler_data(struct irq_desc *desc)
{
	return desc->irq_common_data.handler_data;
}

 
 

set_handle_irq(gic_handle_irq); 

drivers/irqchip/irq-gic-v3.c

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)
{
	u32 typer;
	int err;
...
	set_handle_irq(gic_handle_irq);
...
}

3.6、中断映射并执行,中断处理函数

include/linux/irqdomain.h
/**
 * irq_find_mapping() - Find a linux irq from a hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 */
static inline unsigned int irq_find_mapping(struct irq_domain *domain,
					    irq_hw_number_t hwirq)
{
	unsigned int irq;

	if (__irq_resolve_mapping(domain, hwirq, &irq))
		return irq;

	return 0;
}
 
 
kernel/irq/irqdomain.c
/**
 * __irq_resolve_mapping() - Find a linux irq from a hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 * @irq: optional pointer to return the Linux irq if required
 *
 * Returns the interrupt descriptor.
 */
struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain,
				       irq_hw_number_t hwirq,
				       unsigned int *irq)
{
	struct irq_desc *desc = NULL;
	struct irq_data *data;
 
	/* Look for default domain if nececssary */
    /* 确认域有效,才能做反映射 */
	if (domain == NULL)
		domain = irq_default_domain;
	if (domain == NULL)
		return desc;
	if (irq_domain_is_nomap(domain)) {
		if (hwirq < domain->hwirq_max) {
			data = irq_domain_get_irq_data(domain, hwirq);
			if (data && data->hwirq == hwirq)
				desc = irq_data_to_desc(data);
			if (irq && desc)
				*irq = hwirq;
		}

		return desc;
	}

	rcu_read_lock();
 
	/* Check if the hwirq is in the linear revmap.  */
    /*  /* 检查硬件中断号,是不是在这个域范内内 */ */
	if (hwirq < domain->revmap_size)
		data = rcu_dereference(domain->revmap[hwirq]);
	else
		data = radix_tree_lookup(&domain->revmap_tree, hwirq);

	if (likely(data)) {
		desc = irq_data_to_desc(data);
		if (irq)
			*irq = data->irq;
	}

	rcu_read_unlock();
	return desc;
}
EXPORT_SYMBOL_GPL(__irq_resolve_mapping);
 

kernel/irq/irqdomain.c
/** 获取这中断控制器的数据
 * irq_domain_get_irq_data - Get irq_data associated with @virq and @domain
 * @domain:	domain to match
 * @virq:	IRQ number to get irq_data
 */
struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain,
					 unsigned int virq)
{
	struct irq_data *irq_data;
 
	for (irq_data = irq_get_irq_data(virq); irq_data;
	     irq_data = irq_data->parent_data)
		if (irq_data->domain == domain)    //保证找的到的这个中断控制器的是其父域下面的
			return irq_data;
 
	return NULL;
}
 
 
kernel/irq/irqdesc.c
int handle_irq_desc(struct irq_desc *desc)
{
	struct irq_data *data;

	if (!desc)
		return -EINVAL;

	data = irq_desc_get_irq_data(desc);
	if (WARN_ON_ONCE(!in_hardirq() && handle_enforce_irqctx(data)))
		return -EPERM;

	generic_handle_irq_desc(desc); //执行中断函数
	return 0;
}

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:	The irq number to handle
 *
 * Returns:	0 on success, or -EINVAL if conversion has failed
 *
 * 		This function must be called from an IRQ context with irq regs
 * 		initialized.
  */
int generic_handle_irq(unsigned int irq)   //上面获取到了存放的中断,这里则执行中断函数
{
	return handle_irq_desc(irq_to_desc(irq));
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

 
include/linux/irqdesc.h
/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt.
 */
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);        //这里就是执行高级中断 函数了
}

3.7、申请并初始化一个中断域

 drivers/irqchip/irq-gic-v3.c

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)
{
	u32 typer;
	int err;
...

	gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
						 &gic_data);
...
}

 include/linux/irqdomain.h

static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data);
}

kernel/irq/irqdomain.c 

struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	struct irq_domain *domain;

	domain = __irq_domain_create(fwnode, size, hwirq_max, direct_max,
				     ops, host_data);
	if (domain)
		__irq_domain_publish(domain);

	return domain;
}
EXPORT_SYMBOL_GPL(__irq_domain_add);

 kernel/irq/irqdomain.c

static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode,
					      unsigned int size,
					      irq_hw_number_t hwirq_max,
					      int direct_max,
					      const struct irq_domain_ops *ops,
					      void *host_data)
{
	struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	static atomic_t unknown_domains;

	if (WARN_ON((size && direct_max) ||
		    (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max) ||
		    (direct_max && (direct_max != hwirq_max))))
		return NULL;

	domain = kzalloc_node(struct_size(domain, revmap, size),
			      GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
	if (!domain)
		return NULL;

	if (is_fwnode_irqchip(fwnode)) {
		fwid = container_of(fwnode, struct irqchip_fwid, fwnode);

		switch (fwid->type) {
		case IRQCHIP_FWNODE_NAMED:
		case IRQCHIP_FWNODE_NAMED_ID:
			domain->fwnode = fwnode;
			domain->name = kstrdup(fwid->name, GFP_KERNEL);
			if (!domain->name) {
				kfree(domain);
				return NULL;
			}
			domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
			break;
		default:
			domain->fwnode = fwnode;
			domain->name = fwid->name;
			break;
		}
	} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
		   is_software_node(fwnode)) {
		char *name;

		/*
		 * fwnode paths contain '/', which debugfs is legitimately
		 * unhappy about. Replace them with ':', which does
		 * the trick and is not as offensive as '\'...
		 */
		name = kasprintf(GFP_KERNEL, "%pfw", fwnode);
		if (!name) {
			kfree(domain);
			return NULL;
		}

		domain->name = strreplace(name, '/', ':');
		domain->fwnode = fwnode;
		domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	}

	if (!domain->name) {
		if (fwnode)
			pr_err("Invalid fwnode type for irqdomain\n");
		domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
					 atomic_inc_return(&unknown_domains));
		if (!domain->name) {
			kfree(domain);
			return NULL;
		}
		domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	}

	fwnode_handle_get(fwnode);
	fwnode_dev_initialized(fwnode, true);

	/* Fill structure */
	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	domain->ops = ops;
	domain->host_data = host_data;
	domain->hwirq_max = hwirq_max;

	if (direct_max)
		domain->flags |= IRQ_DOMAIN_FLAG_NO_MAP;

	domain->revmap_size = size;

	/*
	 * Hierarchical domains use the domain lock of the root domain
	 * (innermost domain).
	 *
	 * For non-hierarchical domains (as for root domains), the root
	 * pointer is set to the domain itself so that &domain->root->mutex
	 * always points to the right lock.
	 */
	mutex_init(&domain->mutex);
	domain->root = domain;

	irq_domain_check_hierarchy(domain);

	return domain;
}

OK,接下来下篇将详细介绍GIC中断控制器映射以及反映射。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值