1. ARM64异常处理之中断处理
    1. 中断的处理过程
    2. 树莓派4B上的中断源
  2. 树莓派4B的Legacy Interrupt Routing
    1. Legacy IRQ status registers
    2. EL1的Nonsecure generic timer的中断处理流程
    3. 中断现场
    4. 中断实验1:在树莓派上实现generic timer
    5. 中断实验2:使用汇编函数的方式来保存和恢复中断现场
  3. GIC中断控制器
    1. GIC支持的中断类型
    2. 中断的触发类型
    3. 中断号
    4. 中断优先级
    5. 中断状态
    6. 中断路由
    7. GICV2中断控制器
    8. 配置
    9. 中断状态时序图
    10. GICv2寄存器
      1. GICD_CTLR
      2. GICD_ITARGETSRn
      3. GICD_TYPER (Interrupt Controller Type Register)
      4. GICD_ICFGRn
      5. GICD_ISENABLERn
      6. GICD_ICENABLERn
      7. GICD_ISACTIVERn
      8. GICD_ICACTIVERn
      9. GICD_IPRIORITYRn
      10. GICC_CTLR
      11. GICC_PMR
      12. GICC_IAR
      13. GICC_EOIR
    11. 树莓派4B的GIC400
      1. 访问GIC-400寄存器
      2. GIC400的初始化流程
        1. 注册中断
        2. 中断响应
    12. GIC中断实验1:实现generic timer
    13. GIC中断实验2:实现树莓派上的System Timer
  4. GICv3中断控制器
    1. GICv3比GICv2改进了哪些内容
    2. GICv3支持的中断类型
    3. 有线中断与基于消息的中断
    4. 中断号分配
    5. 中断状态机
    6. 中断亲和性路由层级(Affinity routing)
    7. GIC-500的中断亲和性路由
    8. 特殊中断号
      1. 1021号中断的使用1
      2. 1021号中断的使用2
    9. GICC与ICC
    10. 中断优先级
      1. 中断优先级分组寄存器(ICC_BPR0_EL1/ICC_BPR1_EL1)
      2. 中断优先级阈值与运行中断优先级
      3. 中断优先级的抢占
    11. GICv3内部架构
    12. ITS服务(Interrupt translation service)
      1. 配置表和pending表
      2. Device Table的创建
      3. Interrupt translation table的创建
      4. Collection Table的创建
      5. ITS Command Queue
      6. ITS在Linux中的实现
        1. irq domain中断控制域
        2. ITS驱动框架
        3. 创建MSI中断的API
        4. Platform device中使用MSI中断的例子-SMMUv3
        5. platform请求分配MSI中断流程图
        6. PCI设备分配MSI中断流程图
        7. IO设备如何触发中断?
        8. ITS Debug Tips
Cover image for ARM Interrupt Controller

ARM Interrupt Controller

字数 9.6k
阅读
访客
时间轴

时间轴

2025-10-22

init

本文介绍了ARM64异常处理中的中断机制,包括PSTATE状态位、GIC控制器与传统中断方式的区别,并详细讨论了树莓派4B上Legacy Interrupt的中断源分类、路由机制及中断状态寄存器的读取流程。最后以generic timer为例,本文总结了EL1中断处理的完整流程、中断现场保存与恢复机制以及裸机环境下的初始化推荐顺序。

参考文档:

ARM64异常处理之中断处理

  • ARM核心两个和中断相关的管脚:nIRQnFIQ
  • 每个CPU核心有一对这样的中断相关的管脚

ARM® Cortex®-A72 MPCore Processor Revision: r0p3 Technical Reference Manual
ARM® Cortex®-A72 MPCore Processor Revision: r0p3 Technical Reference Manual

  • PSTATE状态中有两个比特位和中断相关
    • I 用来屏蔽IRQ中断
    • F 用来屏蔽FIQ中断

ARM64中的GIC控制器

  • ARM提供了标准的GIC控制器,例如树莓派4b上支持GIC-400
  • 树莓派3b上支持传统的中断方式(legacy interrupt)

Legacy Interrupt

Legacy Interrupt
Legacy Interrupt

中断的处理过程

中断处理的过程
中断处理的过程

Interrupt Handler in C code
Interrupt Handler in C code

当处理器接受一个异常到 AArch64 执行状态时,所有的 PSTATE 中断掩码都会自动设置。这意味着后续的异常将被禁用。如果软件要支持异常嵌套,例如,允许高优先级的中断去打断对低优先级源的处理,那么软件需要显式地重新启用中断。

对于下面的指令:

1
MSR DAIFClr, #imm

Handling nested Interrupts
Handling nested Interrupts

嵌套处理程序需要一些额外的代码。它必须在堆栈上保存 SPSR_EL1 和 ELR_EL1 的内容。在确定(并清除)中断源之后,我们还必须重新启用 IRQ。

树莓派4B上的中断源

树莓派4B上的中断源
树莓派4B上的中断源

  • ARM Core n (ARM核心的中断源)

    • PNS timer IRQ对应 A Non-secure EL1 physical timer
    • PS timer IRQ对应A secure EL1 physical timer
    • HP timer IRQ(Hypervisor)对应A Non-secure EL2 physical timer
    • V timer IRQ 对应 A virtual timer
    • PMUPerformance Monitor Unit(性能监控单元)
    • repeated 4 times 表示每个核心都有一组这样的中断源,树莓派有4个核心因此repeated 4 times

    Generic Timer
    Generic Timer

  • ARM_LOCAL (只有CPU才能访问的中断源)

  • ARMC (CPU和GPU都能访问的中断源)

  • VideoCore (GPU核心的中断 源)

    包括的中断如下表:

    VC(VideoCore) peripheral IRQs

    VideoCore Peripheral IRQs
    VideoCore Peripheral IRQs

    VideoCore Peripheral IRQs
    VideoCore Peripheral IRQs

  • ETH_PCIe (PCIe的中断)

树莓派4BLegacy Interrupt Routing

Legacy Interrupt Routing
Legacy Interrupt Routing

ARM Core IRQs直接路由到pre-core routing

ARMC和VC通过ARMC routing 硬件单元路由

Legacy IRQ status registers

Legacy IRQ status registers
Legacy IRQ status registers

  • FIQn/IRQn_PENDING2

  • FIQn/IRQn_PENDING0

  • FIQn/IRQn_PENDING1

  • FIQ/IRQ_SOURCEn

    当source 寄存器的bit8被设置了,那么你需要读PENDING2状态寄存器

    如果PENDING2的bit24置位了,那么你需要读PENDING0的状态寄存器

    如果PENDING2的bit25置位了,那么你需要读PENDING1的寄存器

    软件需要一步一步读取中断状态寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
       ┌──────────────┐
│ SOURCE寄存器 │ ← ARM_LOCAL_IRQ_SOURCE0
└───────┬──────┘

┌──────────┴──────────┐
│ │
本地中断? bit8=1?
(定时器等) (有外设/GPU中断)
│ │
↓ ↓
handle_timer_irq() ┌───────────────┐
│ PENDING2寄存器│
└───────┬───────┘

┌───────────┬───────────┐
│ │ │
bit24=1? bit25=1? 其它位?
去PENDING0 去PENDING1 直接是GPU中断

Example

树莓派文档中判断类型的例子
树莓派文档中判断类型的例子

以ARM Core的generic timer为例

  • Cortex-A72支持4个ARM Core的generic timer
    • CNT_PS_IRQ Secure EL1 Physical Timer Event Interrupt
    • CNT_PNS_IRQ Nonsecure EL1 Physical Timer Event interrupt
    • CNT_HP_IRQ Hypervisor Physical Timer Event interrupt, EL2
    • CNT_V_IRQ Virtual Timer Event interrupt EL1

CNTP_CTL_ELx和CNTP_TVAL_ELx
CNTP_CTL_ELx和CNTP_TVAL_ELx

Timer支持两种触发方式

Timer的两种触发方式
Timer的两种触发方式

CNTP_CTL_EL0
CNTP_CTL_EL0

XXX_ELn→ 表示这个系统寄存器 可在 ELn 及更高特权级访问

  • 比如CNTP_CTL_EL0就是 EL0 和 EL1 都能访问

CNTP_TVAL_EL0
CNTP_TVAL_EL0

EL1的Nonsecure generic timer的中断处理流程

  1. 初始化timer,设置cntp_ctl_el0寄存器的enable域为1
  2. 给timer的TimeValue一个初值,设置cntp_tval_el0寄存器
  3. 打开树莓派中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器中的CNT_PNS_IRQ为1

TIMER_CNTRLx
TIMER_CNTRLx

TIMER_CNTRLx
TIMER_CNTRLx

  1. 打开PSTATE寄存器中的IRQ中断总开关

  2. Timer中断发生

  3. CPU跳转到el1_irq汇编函数

  4. 保存中断上下文(使用kernel_entry宏)

  5. 跳转到中断处理函数

  6. 读取ARM_LOCAL中中断状态寄存器IRQ_SOURCE0

  7. 判断是否CNT_PNS_IRQ中断发生

  8. 如果是,重新设置TimeValue

  9. 返回到el1_irq汇编函数

  10. 恢复中断上下文

  11. 返回中断现场

中断现场

  • 中断发生瞬间,CPU的状态包括:
    • PSTATE寄存器
    • PC值
    • SP值
    • x0~x30寄存器
  • 使用一个栈框数据结构来描述需要保存的中断现场(struct pt_regs)

栈框
栈框

保存中断现场

保存中断现场
保存中断现场

恢复中断现场

恢复中断现场
恢复中断现场

中断实验1:在树莓派上实现generic timer

实验1
实验1

树莓派firmware启动时默认加载GIC控制器而不是使用Legacy Interrupt因此可以在QEMU上跑

  1. 初始化timer,设置cntp_ctl_el0寄存器的enable域为1

    初始化timer
    初始化timer

  2. 给timer的TimeValue一个初值,设置cntp_tval_el0寄存器

    设置cntp_tval_el0
    设置cntp_tval_el0

  3. 打开树莓派中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器中的CNT_PNS_IRQ为1

    CNT_PNS_IRQ宏定义
    CNT_PNS_IRQ宏定义

    树莓派TIMER_CNTRLx的地址
    树莓派TIMER_CNTRLx的地址

    设置TIMER_CNTRL0的CNT_PNS_IRQ
    设置TIMER_CNTRL0的CNT_PNS_IRQ

  4. 打开PSTATE寄存器中的IRQ中断总开关

    打开PSTATE寄存器的IRQ中断总开关
    打开PSTATE寄存器的IRQ中断总开关

  5. Timer中断发生

  6. CPU跳转到el1_irq汇编函数

    异常向量表
    异常向量表

    el1_irq处理逻辑
    el1_irq处理逻辑

  7. 保存中断上下文(使用kernel_entry宏)

    中断保存上下文的宏定义
    中断保存上下文的宏定义

  8. 跳转到中断处理函数

  9. 读取ARM_LOCAL中中断状态寄存器IRQ_SOURCE0

    ARM_LOCAL_IRQ_SOURCE0
    ARM_LOCAL_IRQ_SOURCE0

    中断转发函数
    中断转发函数

  10. 判断是否CNT_PNS_IRQ中断发生

  11. 如果是,重新设置TimeValue

timer的中断处理函数
timer的中断处理函数

  1. 返回到el1_irq汇编函数
  2. 恢复中断上下文
  3. 返回中断现场

image-20250827234104251
image-20250827234104251

恢复中断现场的宏定义
恢复中断现场的宏定义

另一种实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include "timer.h"
#include "asm/arm_local_reg.h"
#include "io.h"
#include "irq.h"
#include "mydef.h"
#include "printk.h"

static inline unsigned long read_cntfrq(void) {
unsigned long v;
asm volatile("mrs %0, cntfrq_el0" : "=r"(v));
return v;
}
// ms -> ticks
static inline unsigned long ms_to_ticks(unsigned int ms) {
unsigned long f = read_cntfrq();
return (f / 1000UL) * (unsigned long)ms;
}

// 设置cntp_ctl_el0 enable域为1 开启EL1 Nonsecure generic timer
static int generic_timer_init(void) {
asm volatile("mov x0, #1\n"
"msr cntp_ctl_el0, x0"
:
:
: "memory");
return 0;
}
// 设置cntp_ctl_el0 enable域为0 关闭EL1 Nonsecure generic timer
static int generic_timer_deinit(void) {
asm volatile("mov x0, #0\n"
"msr cntp_ctl_el0, x0"
:
:
: "memory");
return 0;
}
// %[name] → 引用约束变量
// %w[name] → 引用 低 32 位寄存器(如 w0, w1)
// %x[name] → 引用 完整 64 位寄存器(如 x0, x1)

static int generic_timer_reset(unsigned int val) {
asm volatile("msr cntp_tval_el0, %x[timer_val]"
:
: [timer_val] "r"(val)
: "memory");
return 0;
}

static void enable_timer_interrupt(void) { writel(CNT_PNS_IRQ, TIMER_CNTRL0); }

static void (*timer_callback)(void) = NULL;
void timer_init(unsigned int ms, void (*callback)(void)) {

// 初始化timer, cntp_ctl_el0 enable为1
unsigned int ticks = ms_to_ticks(ms); // ms -> tick
generic_timer_reset(ticks);
// 打开中断控制器和timer相关的中断
enable_timer_interrupt();
timer_callback = callback;
}

void timer_start(void) {
raw_local_irq_disable(); // 关闭PSTATE中断控制器总开关,防止初始化时被打断
generic_timer_init(); // 打开cntp_ctl_el0.enable=1,启动计数
raw_local_irq_enable(); // 打开PSTATE中断控制器总开关,防止初始化时被打断
}

void timer_stop(void) {
raw_local_irq_disable(); // 关闭PSTATE中断控制器总开关,防止初始化时被打断
generic_timer_deinit();
raw_local_irq_enable(); // 打开PSTATE中断控制器总开关,防止初始化时被打断
}

void timer_reset(unsigned int ms) {
unsigned int ticks = ms_to_ticks(ms); // ms -> tick
generic_timer_reset(ticks);
}

void handle_timer_irq(void) {
generic_timer_deinit();
if (timer_callback) {
timer_callback();
timer_callback = NULL;
}
printk("Core0 Timer interrupt received\r\n");
}

void kernel_main(void) {
uart_init();
init_printk_done();
printk("Welcome to arm64 mini OS!\r\n");

raw_local_irq_enable(); // 打开PSTATE中断控制器总开关
printk("timer test\n");
timer_init(2000, test_function);
timer_start();
asm volatile("wfi");
/* my test*/
my_test();
// data_abort在QEMU中不起作用
// trigger_sync_data_abort();

trigger_sync_instruction_alignment();

while (1) {
uart_send(uart_recv());
}
}


顺序(推荐的顺序)

  1. cntp_tval_el0← 写定时器值
  2. TIMER_CNTRL0.CNT_PNS_IRQ← 打开外设中断路径
  3. PSTATE.I← 关闭 CPU IRQ 总开关
  4. cntp_ctl_el0.enable← 启动定时器
  5. PSTATE.I← 打开 CPU IRQ 总开关

这种顺序保证 在 CPU 开 IRQ 前,外设和定时器都准备好了,所以一旦中断触发,CPU 能立刻收到并处理。

裸机/内核初始化阶段,定时器还没准备好,如果这时候 CPU 允许 IRQ,就可能被 别的外设中断打断,导致:

  • 初始化流程被中断,配置寄存器可能只做了一半。
  • 定时器或者外设中断配置还没完成,就有中断 pending → 结果是中断丢失或乱序执行。

中断实验2:使用汇编函数的方式来保存和恢复中断现场

实验2
实验2

关键在于使用函数方式lr寄存器被破坏

kernel_entry
kernel_entry

el1_irq
el1_irq

kernel_exit
kernel_exit

GIC中断控制器

  • 传统的中断控制器,例如树莓派4b上的legacy interrupt controller
    • 中断enable寄存器
    • 中断disable寄存器
    • 中断状态状态寄存器
  • 传统的使用简单状态寄存器的方式来管理中断,变得越来越难管理
    • 中断源变得越来越多
    • 不同类型的中断,比如多核间的中断,中断优先级,软件定义的中断等、

GIC版本
GIC版本

GIC 主要有两个主要的功能块

  • Distributor:系统中的所有中断源都连接到 distributor。Distributor 提供寄存器来控制各个中断的属性,例如优先级、状态、安全性、路由信息和启用状态。distributor 通过附加的 CPU 接口确定将哪个中断转发到内核。
  • CPU Interface:cpu 通过它接收中断。CPU 接口提供寄存器来屏蔽、识别和控制转发到该内核的中断状态。系统中的每个内核都有一个单独的 CPU 接口。

GIC支持的中断类型

  • SGISoftware Generated Interrupt),软件产生的中断,软中断,用于给其他CPU核心发送中断信号

  • PPIPrivate Peripheral Interrupt),私有的外设中断,该中断时某个指定的CPU独有的

  • SPIShared Peripheral Interrupt),共享的外设中断,所有CPU都可以访问这个中断

  • LPILocality-specific Peripheral Interrupt),本地特殊外设中断,GICv3新增的中断类型基于消息传递的中断类型

    GIC支持的中断类型
    GIC支持的中断类型

中断的触发类型

每个中断类型要么是Edge-triggered的要么是Level-sensitive的:

中断的触发类型
中断的触发类型

GICD_ICFGRn寄存器控制

  • Edge-triggered: 当 GIC 检测到相关输入的上升沿时被认为是有效的,并且在清除之前保持有效
  • Level-sensitive: 仅当 GIC 的相关输入为高电平时才有效

中断号

中断号
中断号

banked interrupt 意思是对于 SGI 和 PPI,虽然它们的 ID 在整个系统里一样(例如 Timer PPI 在所有核都是 ID30),但是 它们是私有的,每个 CPU 都有自己的一份,互不干扰。

  • SGI (0–15):某核触发 SGI0,不会影响别的核的 SGI0。
  • PPI (16–31):核 0 的 Timer PPI(比如 ID30)和核 1 的 Timer PPI(ID30)是独立的,它们不会共享。

中断号 1020~1023是保留的

Special Interrupt numbers
Special Interrupt numbers

中断优先级

每个中断优先级设置在GICD_IPRIORITYRn寄存器中

优先级字段的宽度

  • 每个中断优先级字段是8bit
  • 实际实现支持的优先级级数 = 2ⁿ,n ∈ [4, 8](即 16 ~ 256 级)。
  • 如果硬件实现的比 8 小,比如只实现 5 bit,则写入低 3 bit 是 RAZ/WI(读为 0、写无效)。

数值大小与优先级

  • 数值越小,优先级越高
  • 最大优先级值依赖实现
  • 初始化默认应当给所有中断一个中等优先级,避免打断系统调度,常见是0xa0或0x80

中断状态

  • inactive (不活跃状态):中断处于无效状态

  • pending (等待状态):中断处于有效状态,但是等待CPU响应该中断

  • active (活跃状态):CPU已经响应该中断

  • active and pending (活跃并等待状态):CPU正在响应该中断,但是该中断源又发送中断过来

    中断状态机
    中断状态机

中断路由

Distributor
Distributor

GICD_ITARGETSRn (Interrupt Processor Targets Registers)

GICD_ITARGETSRn
GICD_ITARGETSRn

GICD_ITARGETSRn寄存器用来配置Distributor可以把中断路由到哪个CPU上,是一个 32 位寄存器,通常每个中断有一个对应的寄存器

  • 8bit来表示一个中断源,共四个中断源,每个bit代表能路由的CPU
  • byte-accessible
  • 某个bit设置了,说明该中断源可以路由到这个CPU上
  • 前32个中断源的路由配置是硬件设置好的,RO
  • 第33~1019号中断,可以由软件来配置其路由,RW

对应计算方法

计算方法
计算方法

假设m是中断ID, n是对应寄存器号

  • GICD_ITARGETSRn寄存器与中断源的关系
    • 每个GICD_ITARGETSRn控制四个中断源
    • n = m DIV 4
    • 计算n后要加上GICD_ITARGETSRn寄存器的起始地址偏移0x800
  • Priority字段的字节偏移
    • m MOD 4用来确定目标中断源对应的字节偏移
      • 偏移 0 对应寄存器的 [7:0] 位(即最低字节),对应中断 ID m % 4 == 0
      • 偏移 1 对应寄存器的 [15:8] 位,适用于 m % 4 == 1
      • 偏移 2 对应寄存器的 [23:16] 位,适用于 m % 4 == 2
      • 偏移 3 对应寄存器的 [31:24] 位,适用于 m % 4 == 3

GICD_ITARGETSRn 是一个数组寄存器

CPU targets
CPU targets

GICV2中断控制器

GICV2中断控制器
GICV2中断控制器

  • The Distributor registers (GICD_) 包含了中断设置和配置
  • The CPU Interface registers (GICC_) 包含CPU相关的特殊设置

GIC logical partitioning
GIC logical partitioning

中断传递的优先级和目标 core 都在 distributor 中配置。

外围设备 distributor 发送的中断都处于待处理状态。distributor 确定最高优先级的待处理的中断,可以传递到 core 并将其转发到 CPU 接口,在 CPU 接口处,中断依次发送给 core,此时 core 接受 FIQ 或 IRQ 异常。core 执行异常处理程序作为响应。处理程序从 CPU 接口寄存器中查询中断 ID 并开始执行中断服务程序。完成后,处理程序必须写入 CPU 接口寄存器以报告处理结束。

Distributor 提供了报告不同中断 ID 的当前状态的寄存器。在多核/多处理器系统中,单个 GIC 可以由多个内核共享(在 GICv2 中最多 8 个)。GIC 提供寄存器来控制 SPI 所对应的内核。这个机制使操作系统能够跨内核共享和分发中断,并且协调工作。

配置

GIC 的寄存器实现都是外部 memory_map 形式。所有的核都可以访问公共的 Distributor,但是 CPU 接口是banked 的,即每个核使用相同的地址访问自己的私有 CPU 接口。一个内核不可能访问另一个内核的 CPU 接口。

Distributor 包含许多寄存器,您可以使用它们来配置各个中断的属性。这些可配置的属性有:

  • 中断优先级GICD_IPRIORITY),distributor 使用它来确定哪个中断接下来被转发到 CPU 接口。
  • 中断配置GICD_ICFGR)。这决定了中断是电平还是边缘敏感。不适用于 SGI。
  • 一个中断目标 coreGICD_ITARGETSR)。这确定了中断可以路由到哪些 core。仅适用于 SPI。
  • 中断启用或禁用状态GICD_ISENABLERGICD_ICENABLER)。只有那些在 distributor 中启用的中断。当它们处于待处理状态时有资格被路由到 cpu 接口。
  • 中断安全 (GICD_IGROUPR) 确定中断是否分配给安全或非安全。
  • 中断状态

Distributor 还提供了优先级屏蔽,通过它可以防止低于某个优先级的中断到达内核。distributor 在确定是否可以将挂起的中断转发到特定内核时使用它每个内核上的 CPU 接口有助于微调中断控制和处理核

初始化

Distributor 和 CPU interface 在复位时都被禁用。GIC 必须在复位后初始化,然后才能向内核提供中断。

在 Distributor 中,软件必须配置优先级、目标 core、安全性并启用各个中断。随后必须通过其控制寄存器(GICD_CTLR) 启用 distributor

对于每个 CPU interface,软件必须对优先级掩码和优先级抢占进行设置。每个 CPU interface 本身必须通过控制寄存器 (GICD_CTLR) 启用。

在 cpu 处理中断之前,软件通过设置使 cpu准备好接受中断向量表中的有效中断向量,并清除 PSTATE 中的中断屏蔽位,并设置路由控制。

可以通过禁用 Distributor 来禁用系统中的整个中断机制。也可以通过禁用其 CPU 接口来禁用对单个内核的中断传递。也可以在分配器中禁用(或启用)各个中断。

对于一个中断到达核心,单个中断、分配器和 CPU 接口必须全部启用。中断也需要有足够的优先级,即高于内核的优先掩码。

中断处理

当 core 接受了中断,就跳转到顶层中断向量表并且开始执行。顶层的中断处理程序从 CPU interface 读取寄存器以获得中断 ID

读取寄存器除了返回中断 ID 外,还会导致中断在 distributor 中被标记为活 active。一旦知道中断 ID(识别中断源),顶层处理程序就可以调度特定于设备的中断处理程序来服务中断。

当中断处理程序完成执行时,顶层处理程序将相同的中断 ID 写入 CPU 接口块中的中断结束 (EoI) 寄存器,指示中断处理结束。

除了移除活动状态,使最终的中断状态为 Inactive 或 pending(如果状态为 active and pending),这使 CPU 接口能够将更多挂起的中断转发到 core。这结束了单个中断的处理。

在同一个内核上可能有多个中断等待服务,但是 CPU 接口一次只能发出一个中断信号。顶层中断处理程序可以重复上述顺序,直到读取到特殊中断 ID 值 1023表示有在这个核心上没有更多的中断挂起。这个特殊的中断 ID 被称为虚假的中断标识。虚假中断 ID 是一个保留值,不能分配给系统。当顶层处理程序读取了虚假中断 ID 后,它可以完成它的执行,并准备内核在中断之前恢复它正在执行的任务。

通用中断控制器 (GIC)通常管理来自多个中断源的输入。并将它们分发给 IRQ 或 FIQ 。

中断状态时序图

中断状态时序图
中断状态时序图

M和N分别代表两个中断,Input表示中断信号,State表示中断状态机,nFIQCPU[n]表示连接到CPU核心的FIQ信号

假设M和N都是SPI中断,且N的优先级高于M

GICv2寄存器

GIC寄存器后缀
GIC寄存器后缀

有些寄存器是按中断号来描述的,比如使用某几个比特位来描述一个中断号的相关属性,同一个寄存器可以n个,例如GICD_ISENABLERn寄存器,它是用来使能某个中断号的。“n”表示它有n个这样的寄存器

GICD_ISENABLERn
GICD_ISENABLERn

GICD_ISENABLERn的Set-enable bits
GICD_ISENABLERn的Set-enable bits

计算方法:根据中断号计算对应寄存器

计算方法
计算方法

对于有些寄存器,会提示:

A register bit corresponding to an unimplemented interrupt is RAZ/WI.

RAZ = Read-As-Zero
如果你读这个寄存器位,返回值永远是 0,即使你写过别的值。

WI = Write-Ignored
如果你往这个位写入数据,硬件会忽略,不会改变,也不会报错。

GICD_CTLR

Distributor Control Register

image-20250830231416178
image-20250830231416178

GICD_CTLR bit
GICD_CTLR bit

GICD_ITARGETSRn

Interrupt Processor Targets Registers,见中断路由章节

GICD_TYPER (Interrupt Controller Type Register)

描述这个 GIC 的能力和配置参数,比如有多少中断、多少个 CPU 接口、是否支持某些特性等等。

GICD_TYPER
GICD_TYPER

GICD_TYPER bit
GICD_TYPER bit

GICD_ICFGRn

Interrupt Configuration Registers

GIC用来配置中断触发类型的寄存器

每个GICD_ICFGRn32 位寄存器

GICD_ICFGRn
GICD_ICFGRn

分布方式

  • 每个中断需要 2 bitInt_config)来描述,所以一个寄存器可以配置 16 个中断

  • n个寄存器(ICFGRn)对应的中断范围是:

    1
    中断 ID = n*16  ~ (n*16 + 15)

Int_config字段编码

根据 ARM GICv2 TRM(Table 4-18):

GICD_ICFG Int_config.field
GICD_ICFG Int_config.field

对于PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)来说

Bit[1]Bit[0]含义
0reversedLevel-sensitive
1reservedEdge-triggered

GICD_ISENABLERn

Interrupt Set-Enable Registers

负责打开中断转发

  • GICD_ISENABLERn32位寄存器

  • 每个GICD_ISENABLERn控制 32 个中断源

    GICD_ISENABLERn
    GICD_ISENABLERn

  • 写 1 → 使能对应的中断(让 Distributor 可以把它转发给 CPU interface)

  • 写 0 → 没有作用

  • → 可以看到某个中断当前是否被使能(1=已使能,0=未使能)

根据中断号计算GICD_ISENABLERn的n

GICD_ISENABLERn计算
GICD_ISENABLERn计算

GICD_ICENABLERn

Interrupt Clear-Enable Registers

  • W1C (写 1 清除),禁用某个中断(禁止分发到 CPU)

GICD_ICEABLERn
GICD_ICEABLERn

GICD_ICEABLERn Clear-enable bit
GICD_ICEABLERn Clear-enable bit

GICD_ICENABLERn计算
GICD_ICENABLERn计算

GICD_ISACTIVERn

Interrupt Set-Active Registers

  • W1S(写1设置),软件可以把一个中断状态人为标记为 active

GICD_ISACTIVERn
GICD_ISACTIVERn

GICD_ISACTIVERn Set-active bits
GICD_ISACTIVERn Set-active bits

GICD_ISACTIVERn计算
GICD_ISACTIVERn计算

GICD_ICACTIVERn

Interrupt Clear-Active Registers

  • W1C(写1清除),清除中断的 active 状态。

  • 和 EOI(End of Interrupt)有点类似,但 EOI 是 CPU interface 寄存器,只能由当前 CPU 对自己接收的中断做“中断服务完成”的动作;

  • ICACTIVERnDistributor,软件可以全局地强制清除某个中断的 active 位

GICD_ICACTIVERn
GICD_ICACTIVERn

GICD_ICACTIVERn Clear-active bits
GICD_ICACTIVERn Clear-active bits

GICD_ICACTIVERn
GICD_ICACTIVERn

GICD_IPRIORITYRn

Interrupt Priority Registers

用于设置每个中断的优先级

  • 每个中断占 8 bit

  • 一个 32 位寄存器管理 4 个中断

GICD_IPRIORITYRn
GICD_IPRIORITYRn

GICD_IPRIORITYRn bit assignments
GICD_IPRIORITYRn bit assignments

地址计算:

GICD_IPRIORITYRn地址计算
GICD_IPRIORITYRn地址计算

GICC_CTLR

CPU Interface Control Register

GICC_CTLR
GICC_CTLR

GICC_CTLR bit assignment
GICC_CTLR bit assignment

GICC_PMR

Interrupt Priority Mask Register

CPU 接口的优先级屏蔽寄存器,它决定了 CPU 能够接收哪些优先级的中断

  • 只有 优先级数值 ≤ PMR 的中断 才能被 CPU 接收。
  • 注意:GIC 的优先级是 数值越小,优先级越高

GICC_PMR
GICC_PMR

GICC_PMR bit assignment
GICC_PMR bit assignment

使用举例:假设你的 GIC 实现有 8 bit 优先级:

  1. 如果GICC_PMR = 0xff
    • CPU 接收所有优先级的中断(最常见的初始化设置)。
  2. 如果GICC_PMR = 0xa0
    • 只接收 优先级值 ≤ 0xa0 的中断,优先级更“低”的中断会被屏蔽。
  3. 如果GICC_PMR = 0x0
    • 只接收最高优先级(0)的中断,其他都屏蔽

GICC_IAR

Interrupt Acknowledge Register

当 CPU 收到 IRQ 信号时,软件需要从这个寄存器读取当前 pending 中断的 ID,并确认它是哪一个 IRQ。

GICC_IAR
GICC_IAR

Bits名称含义
[9:0]INTID中断 ID(0~1019 表示有效中断号)
[12:10]CPUID标识触发该中断的 CPU(多核系统有用)
[31:13]Reserved保留,读出为 0

GICC_EOIR

End of Interrupt Register

软件在完成中断处理后,必须写入这个寄存器,告诉 GIC:“这个 IRQ 已经处理完了,可以清除 active 状态,并允许后续相同中断再次触发”。

GICC_EOIR
GICC_EOIR

Bits名称含义
[9:0]INTID中断 ID(必须与 IAR 读出的中断号一致)
[12:10]CPUID发出该中断的 CPU ID(多核系统使用)
[31:13]Reserved保留,写 0

树莓派4B的GIC400

GIC-400
GIC-400

  • ARM Core的中断:
    • Core n HP tiemr IRQ
    • Core n V timer IRQ
    • Legacy FIQn
    • Core n PS timer IRQ
    • Core n PNS timer IRQ (PPI ID 30)
    • Legacy IRQn
  • ARM local的中断
    • ARM Mailbox IRQs
    • Core 0 PMU IRQ
    • Core 1 PMU IRQ
    • Core 2 PMU IRQ
    • Core 3 PMU IRQ
    • AXIERR IRQ
    • Local timer IRQ
  • 16个ARMC外设中断
  • 64个VC外设中断
  • 51个PCI相关的外设中断

访问GIC-400寄存器

  • 树莓派4b上的GIC-400的基地址

GIC-400地址
GIC-400地址

GIC-400 register map
GIC-400 register map

GIC-400 memory map
GIC-400 memory map

GIC-400 memory map
GIC-400 memory map

访问:树莓派GIC400的**基地址 **+ GIC-400 memory map偏移 + 寄存器偏移

GIC400的初始化流程

  1. 设置distributor和CPU interface寄存器组的基地址
  2. 读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源
  3. 初始化distributor
    1. Disable distributor,设置GICD_CTLR(这一步可以不做,因为复位时本来就是关闭的)
    2. 设置SPI中断的路由
      1. 前32个中断怎么路由是GIC芯片固定的,因此先读GICD_ITARGETSRn前面的值,获取能路由的所有CPU
      2. SPI(Shared Peripheral Interrupt)中断 的路由设置为将中断分发到所有能路由的 CPU,因为SPI中断是Shared的中断,设置SPI的GICD_ITARGETSRn
    3. 设置SPI中断的触发类型,例如Level触发,设置GICD_ICFGRn
    4. Disactive和disable所有的SPI中断源
    5. 打开SGI中断(0-15),SMP会用到
    6. Enable distributor,设置GICD_CTLR
  4. 初始化CPU interface
    1. 为前32个中断源设置默认中断优先级,设置GICD_IPRIORITYRn
    2. 设置GICC_PRM,设置中断优先级mask level
    3. Enable CPU interface,设置GICC_PRM
注册中断
  1. 初始化外设
  2. 查找该外设的中断在GIC-400的中断号,例如PNS timer的中断号为30
  3. 设置GICD_ISENABLERn寄存器来enable这个中断号
  4. 打开设备相关的中断,例如树莓派上的generic timer,需要打开ARM_LOCAL寄存器中的TIMER_CNTRL0寄存器中相关的enable位
  5. 打开CPU的PSTATE中的I位
中断响应
  1. 中断发生
  2. 异常向量表
  3. 跳转到GIC中断函数里,gic_handle_irq()
  4. 读取GICC_IAR寄存器,获取中断号
  5. 根据中断号来进行相应的中断处理,例如读取的中断号为30,说明的是PNS的generic timer,然后跳转到generic timer的处理函数里。

GIC中断实验1:实现generic timer

GIC中断实验1
GIC中断实验1

GIC-400初始化

  1. 设置distributorCPU interface寄存器组的基地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef __GIC_V2_H
#define __GIC_V2_H

#include "asm/base.h"

#define GIC_V2_DISTRIBUTOR_OFFSET 0x1000
#define GIC_V2_CPU_INTERFACE_OFFSET 0x2000
#define GIC_V2_DISTRIBUTOR_BASE (GIC_400_BASE + GIC_V2_DISTRIBUTOR_OFFSET)
#define GIC_V2_CPU_INTERFACE_BASE (GIC_400_BASE + GIC_V2_CPU_INTERFACE_OFFSET)

#define GICD_CTLR (GIC_V2_DISTRIBUTOR_BASE + 0x000)

#define GICD_TYPER (GIC_V2_DISTRIBUTOR_BASE + 0x004)
#define GICD_TYPER_ITLINESNUMBER 0x1f
// 直接用 n / 4 或 n / 16 会得到 寄存器索引
// 然后在地址计算上乘以 4(每个寄存器 4 字节)
#define GICD_ITARGETSRn(n) (GIC_V2_DISTRIBUTOR_BASE + 0x800 + (n / 4) * 4)
#define GICD_ICFGRn(n) (GIC_V2_DISTRIBUTOR_BASE + 0xc00 + (n / 16) * 4)

#define GICD_ICFG_LEVEL_SENSITIVE 0x0
#define GICD_ICFG_LEVEL_EDGE_TRIGGERED 0x2

#define GICD_ISENABLERn(n) (GIC_V2_DISTRIBUTOR_BASE + 0x100 + (n / 32) * 4)
#define GICD_ICENABLERn(n) (GIC_V2_DISTRIBUTOR_BASE + 0x180 + (n / 32) * 4)
#define GICD_ICACTIVERn(n) (GIC_V2_DISTRIBUTOR_BASE + 0x380 + (n / 32) * 4)

#define GICD_IPRIORITYRn(n) (GIC_V2_DISTRIBUTOR_BASE + 0x400 + (n / 4) * 4)
#define DAFAULT_IPRIORITY 0xa0a0a0a0

#define GICC_PMR (GIC_V2_CPU_INTERFACE_BASE + 0x4)
#define GICC_CTLR (GIC_V2_CPU_INTERFACE_BASE + 0x0)

#define GICC_IAR (GIC_V2_CPU_INTERFACE_BASE + 0xc)
#define GICC_IAR_CPU_ID_MASK 0x1c00
#define GICC_IAR_INT_ID_MASK 0x3ff
#define GICC_EOIR (GIC_V2_CPU_INTERFACE_BASE + 0x10)

int gic_init(void);
void gic_enable_irq(int irq);

#endif

  1. 读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源

读取GICD_TYPER寄存器
读取GICD_TYPER寄存器

  1. 初始化distributor

    初始化distributor
    初始化distributor

    gic_distributor_init
    gic_distributor_init

  2. 初始化CPU interface

    gic_cpu_init
    gic_cpu_init

  3. 打开PNS_TIMER_IRQ的路由

打开PNS_TIMER_IRQ路由
打开PNS_TIMER_IRQ路由

  1. 测试

gic_irq_handle
gic_irq_handle

测试
测试

GIC中断实验2:实现树莓派上的System Timer

实验2
实验2

GICv3中断控制器

GICv3比GICv2改进了哪些内容

  • GICv3兼容GICv2
  • 支持更多CPU数量,>8
  • 支持message-base中断(Message Signaled InterruptMSI
  • 支持ITSInterrupt Translation Service)服务
  • 支持更多的硬件中断号,>1020
  • 为了更好兼容ARMv8异常模型,支持中断组(Interrupt grouping)
  • 为了优化访问延时,提供系统寄存器的方式来访问CPU Interface

GICv3支持的中断类型

  • Private Peripheral Interrupt(PPI)
    • PPI是指本地CPU特有的中断,比如CPU内部的定时器等。不同的CPU可以使用相同的PPI中断号
    • PPI可以在group0和group1
    • 可以边沿触发(edge-triggered)或者水平触发(level-sensitve)
  • Shared Peripheral Interrupt(SPI)
    • SPI通常用于外设中断,它可以路由到任意一个CPU
    • SPI可以在group0和group1
    • 可以边沿触发或者水平触发
  • Software Generated Interrupt(SGI)
    • SGI通常软件触发的中断,用于核间通信,例如IPI(Inter-Processor Interrupts)
    • SGI只能边沿触发
  • 新增:Locality-specific Peripheral Interrupt(LPI)
    • 在非安全中断组1
    • 边沿触发
    • 使用ITS服务
    • 没有active状态
    • message-based中断

LPI
LPI

有线中断与基于消息的中断

有线中断
有线中断

基于消息的中断
基于消息的中断

  • SPI和LPI都支持message-base中断
    • SPI的message-base中断不需要经过ITS,往GICD_SETSPI_NSR寄存器写入中断号触发中断
    • LPI的message-base需要ITS

中断号分配

中断号分配
中断号分配

中断状态机

  • Inactive 不活跃状态
  • Pending 中断触发了,但是还没有被CPU响应(acknowledge)
  • Active 中断被CPU响应和处理
  • Active & Pending 当前有一个中断正在被CPU响应和处理,这时有一个相同的中断触发了,这个新的中断被设置为active & pending
  • LPI没有active和active & pending状态

问题:如果GIC在响应中断的过程中,又有一个相同的中断触发了,那怎么办?

这里要分两种情况:

  • 如果GIC正在响应第一个中断时,即第一个中断的状态为active,此时第二个相同的中断触发,那么第二个中断的状态会变成 active & pending,这样避免丢失了中断
  • 如果第一个中断还处于pending状态,此时又来了一个相同的中断,那么它们会merge成一个

中断状态机
中断状态机

中断亲和性路由层级(Affinity routing)

  • GICv3支持4级路由
  • Level 0面对的是redistributor

Distributor分发
Distributor分发

core的唯一标识
core的唯一标识

GIC-500的中断亲和性路由

GIC-500的中断亲和性路由
GIC-500的中断亲和性路由

  • GIC-500支持两级的亲和性路由
    • Level 0 是 core
    • Level 1 是 cluster
  • GIC-500最大支持128个cores以及32个cluster

0.0.0.1表示第0个cluster中的第1个core

0.0.1.1表示第1个cluster中的第1个core

  • 中断组与安全模式
    • ARMv8支持安全模式和非安全模式
    • GICv3支持EL0~EL3,所以每个中断源都需要设置对应的中断组和安全模式
      • Group0用于EL3
      • 安全模式的Group1:用于Trust OS on EL2
      • 非安全模式的Group1:用于VMM或者OS

中断组与安全模式
中断组与安全模式

Group0使用FIQ,Group1根据情况使用IRQ or FIQ

Group0,Group1
Group0,Group1

Exception Level
Exception Level

例子

Non-secure/secure Group
Non-secure/secure Group

注意:中断是否会路由到EL3要看SCR_EL3寄存器FIQ和IRQ字段

  • 在非安全模式下:
    • 非安全group1的中断直接在RichOS响应
    • 安全group1和Group0的FIQ中断路由到EL3
  • 在安全模式下
    • 安全group1的IRQ中断直接在Trusted OS响应
    • 非安全group1和group0的FIQ中断路由到EL3

特殊中断号

特殊中断号
特殊中断号

1021号中断的使用1

例子: 陷入到EL3
例子: 陷入到EL3

  1. CPU运行在安全模式下的trusted os,此时来了一个非安全模式下的OS的中断,那么需要陷入到EL3的FIQ来处理
  2. CPU陷入到EL3的安全监视器,安全监视器会读取IAR寄存器,并且读到1021,说明这个中断是希望在非安全模式下处理的。切换到非安全模式的Rich OS
  3. CPU切换到非安全模式的Rich OS来处理这个中断

1021号中断的使用2

例子: 陷入到EL2的情况
例子: 陷入到EL2的情况

  1. CPU运行在安全模式下的trusted os,此时来了一个非安全模式下OS的中断。那么需要陷入到EL3的FIQ来处理。但是由于SCR_EL3.FIQ=0,它只能在EL2中处理。
  2. EL2的Trusted OS执行SMC系统调用陷入到EL3
  3. 安全监视器会读取IAR寄存器,并且读到1021,说明这个中断是希望在非安全模式下处理的。切换到非安全模式的Rich OS
  4. CPU切换到非安全模式的Rich OS来处理这个中断

GICC与ICC

**ICC(Interrupt Controller CPU interface)**特指 GIC 中 与 CPU 直接交互的接口部分,通过 系统寄存器(System Registers) 访问

为什么 GICv2 的 CPU 接口寄存器叫GICC_*,而 GICv3 的却叫ICC_*

答案的核心在于:GICv2 和 GICv3 在 CPU 接口(CPU Interface)的实现方式上发生了根本性变化 —— 从 内存映射 I/O(MMIO) 转变为 系统寄存器(System Registers)。ARM 通过命名差异明确区分这两种架构。

中断优先级

  • GICv3支持8位优先级,最多256级别
    • 支持2个安全模式时,最少支持32个中断优先级,最多支持256个
    • 支持1个安全模式时,最少支持16个中断优先级
  • 中断优先级
    • 数值越小,中断优先级越高,0表示最高优先级,255表示最低优先级,idle priority
    • GICR_IPRIORITYR<n>设置PPI和SGI中断优先级
    • GICD_IPRIORITYR<n>设置SPI中断优先级
    • LPI配置表保存了LPI的中断优先级

中断优先级分组寄存器(ICC_BPR0_EL1/ICC_BPR1_EL1)

  • GIC 中每个中断都有一个 8 位优先级值(priority field),数值越小,优先级越高(例如 0x00 是最高优先级,0xFF 是最低)。
  • CPU 接口(CPU Interface)在决定是否 抢占当前正在处理的中断 时,会比较新中断和当前中断的优先级。

但 GIC 并不是简单地比较整个 8 位值 —— 它通过 Binary Point Register(BPR) 将优先级划分为两个部分:

字段含义
Group Priority(组优先级)高位部分,用于决定能否抢占
Subpriority(子优先级)低位部分,仅在组优先级相同时用于排序
  • ICC_BPR0_EL1:用于 Group 0 中断
  • ICC_BPR1_EL1:用于 Group 1 中断

注意:group1和group0的分组情况略有不同

中断优先级分组寄存器
中断优先级分组寄存器

中断优先级阈值与运行中断优先级

  • 中断阈值
    • 寄存器:ICC_PMR_EL1(Priority Mask Register)
    • PMR决定目标CPU的优先级阈值。GIC只有当pending中断的优先级高于这个中断优先级阈值,才会发送中断到CPU
    • PMR为0,表示屏蔽了所有中断发送给CPU
  • Running priority
    • 寄存器:ICC_RPR_EL1(Running Priority Register)
    • 只读寄存器,返回当前 CPU 上正在处理的最高优先级中断的 Group Priority

中断优先级的抢占

  • GICv3支持中断抢占,当一个中断优先级同时满足下面条件
    • 优先级高于CPU接口的优先级阈值PMR
    • Group priority高于正在处理的running priority

GICv3内部架构

GICv3内部架构
GICv3内部架构

  • Distributor:优先级排队,派发SPI和SGI到redistributor
  • Redistributor:连接CPU interface。每个CPU一个redistributor
  • CPU interface:发送中断给CPU,响应中断等
  • ITS: 中断转换服务,把LPIs中断请求转换到中断号以及发送到redistributor

ITS服务(Interrupt translation service)

  • ITS作用:把设备(device_id)的Event_ID转成:
    • 硬件中断号(INTID)
    • 目标redistributor
  • ITS转换过程
    • 使用Device_ID来查询device table
    • 使用Event_ID来查询Interrupt translation table
      • 物理中断号INTID
      • interrupt collection number
    • 由ICID来查询Collection table得到目标redistributor
  • ITS五张表
    • 中断配置表configure table
    • 中断pending表
    • device table
    • Interrupt translation table
    • Collection table

Interrupt translation service
Interrupt translation service

Interrupt translation
Interrupt translation

配置表和pending表

  • 中断配置表用来存储每个LPI中断的优先级和enable位

    • 每个表项占8bit
    • 配置表基地址:GICR_PROPBASER.Physical_Address
    • 表项个数:2^(GICR_PROPBASER.IDbits)

    中断配置表
    中断配置表

中断配置表表项
中断配置表表项

  • 中断pending 用来表示每个LPI中断的pending状态
    • 每个表项占1个bit
    • 每个redistributor有一个中断pending表

Pending表
Pending表

Device Table的创建

  • Device Table由OS软件创建

    • 分配内存,创建Table
    • 把表的基地址设置到GITS_BASER.Physical_Address
  • Device Table的重要参数在GITS_BASER\<n\>寄存器中

    • 表的类型:GITS_BASER.Type
    • 表项的大小:GITS_BASER.Entry_Size(8个字节)
    • Table中每个page的大小:GITS_BASER.Page_Size(eg, 64KB)
    • 是否需要二级页表:GITS_BASER_Indirect
    • 设置表基地址:GITS_BASER.Physical_Address
    • 需要多少个表项:2^(device_id位宽),device_id位宽在GITS_TYPER.devbits
  • Device Table支持1级或者2级表

  • Device Table表项的内容由:

    • 软件通过command queue来发送命令给硬件,硬件来填充表项
    • 通过MAPD命令,把deviceID映射到ITT表中

Device table一级表
Device table一级表

Device Table二级表
Device Table二级表

  • Device Table的表项叫做DTE,用来指向ITT表的

DDT表项DTE
DDT表项DTE

  • ITT的表项叫做ITE,用来描述EventID和最终物理ID号的关系

ITT表项ITE
ITT表项ITE

Interrupt translation table的创建

  • ITT表项用来映射 EventID -> 物理中断号INTID以及ICID
  • ITT重要参数
    • ITT表项大小:GITS_TYPER.ITT_entry_size(eg:16字节)
    • ITT表项个数:申请中断时请求的vector个数
    • ITT表的基地址:由OS来分配
  • ITT表项的内容由:
    • 软件通过command queue来发送命令给硬件,硬件来完成
    • 通过MAPD命令,把deviceID映射到ITT表中

Collection Table的创建

  • Collection Table表项用来映射:ICID -> redistributor

  • 这个表不需要在内存中分配内存

  • Collection Table表项的内容由:

    • 软件通过command queue来发送命令给硬件,硬件来完成
    • 软件告知:redistributor的物理地址或者GICR_TYPER.Processor_Number
    • 通过MAPC命令来映射ICID -> redistributor
    • 在GIC初始化的时候就遍历所有的redistributor,并且调用MAPC命令初始化
    1
    2
    3
    4
    5
    6
    7
    gic_smp_init()->
    遍历所有present CPU()->
    gic_starting_cpu()->
    its_cpu_init()->
    its_cpu_init_collections()->
    its_cpu_init_collection()->
    its_send_mapc()发送MAPC命令初始化collection table
  • Collection table的表项CTE

collection table表项CTE
collection table表项CTE

ITS Command Queue

  • 三个与command queue相关的寄存器
    • GITS_CBASER:指定command queue的大小和基地,基地址必须64KB对齐,大小必须4KB整数倍
    • GITS_CREADR:ITS下一条要处理的command
    • GITS_CWRITER:下一条要写入的command
  • 常用的Command
    • MAPD:映射device ID到ITT table中
      • MAPD <DeviceID>, <ITT_addr>, <size>
    • MAPI:映射eventid和deviceid到ITT中
      • MAPI <DeviceID>, <EventID>, <Collection ID>
    • MAPTI:映射eventid,deviceid以及硬件中断号到ITT中
      • MAPTI <DeviceID>, <EventID>, <INTID>, <Collection ID>
    • MAPC:映射collection id到目标redistributor
      • MAPC <Collection ID>, <Target Redistributor>

ITS Command Queue
ITS Command Queue

例子

假设某个设备的Device ID为5,想把Event ID=0映射到物理中断号8192中,ITT表的基地址为0x850000。对应的Collection ID为3,对应的目标redistributor的物理地址为0x78400000

例子
例子

linux内核中喜欢用vector表示eventid

ITS在Linux中的实现

qemu virtio device its
qemu virtio device its

irq domain中断控制域
  • 系统存在多级中断控制器的可能性
    • 传统中断控制器-GIC
    • 可以抽象成中断控制器:GIC ITS,GPIO等
    • 虚拟中断控制器,platform irqdomain
  • IRQ Domain看作是IRQ Controller的软件抽象

irq domain
irq domain

ITS驱动框架

ITS驱动代码
ITS驱动代码

ITS驱动框架
ITS驱动框架

OPS
OPS

创建MSI中断的API

MSI中断 platform device
MSI中断 platform device

MSI中断 PCI device
MSI中断 PCI device

Platform device中使用MSI中断的例子-SMMUv3

SMMUv3
SMMUv3

platform请求分配MSI中断流程图

platform请求分配MSI中断流程图
platform请求分配MSI中断流程图

PCI设备分配MSI中断流程图

PCI设备分配MSI中断
PCI设备分配MSI中断

pci_alloc_irq_vectors_affinity
pci_alloc_irq_vectors_affinity

IO设备如何触发中断?

对于GICv3中断控制来说,IO设备需要往GITS_TRANSLATER寄存器写入event ID,即可触发MSI中断

GITS_TRANSLATER
GITS_TRANSLATER

Linux内核封装了一个struct msi_msg数据结构,包括了这个寄存器的地址,以及将要写入的数据

1
2
3
4
5
struct msi_msg{
u32 address_lo;
u32 address_hi;
u32 data;
}

在irq_chip的ops有一个its_irq_compose_msi_msg回调函数,用来填充这个msi_msg,就是把GITS_TRANSLATER寄存器的物理地址写入到msi_msg->address字段里

设备驱动程序在使用platform_msi_domain_alloc_irqs()注册MSI的时候,就会从msi_msg数据结构中获取到GITS_TRANSLATER寄存器的物理地址和eventID,然后写入到IO设备自己的寄存器(例如SMMU中的SMMU_EVENTQ_IRQ_CFGO和CFG1)里。

设备要触发MSI,就会自动往自己寄存器里写入eventid,来触发中断。

以SMMU为例:

SMMU
SMMU

ITS Debug Tips
  • 最新的qemu支持ITS。可以通过QEMU+linux kernel来单步调试ITS和MSI
  • 在kernel command line中添加"irq_gic_v3_its.dyndbg=+pflmt irqdomain.dyndbg=+pflmt"来打开相关的动态打印:

kernel启动日志
kernel启动日志

  • 可以在its_domain_ops回调中添加"dump_stack()"来打印调用函数关系calltrace