hnu操作系统-lab5

实验目的

ARM的中断系统

中断是一种硬件机制。借助于中断,CPU可以不必再采用轮询这种低效的方式访问外部设备。将所有的外部设备与CPU直接相连是不现实的,外部设备的中断请求一般经由中断控制器,由中断控制器仲裁后再转发给CPU。如下图所示Arm的中断系统。

配置gicv2中断控制器

技术参考手册

  • GICv2 最多支持8个核的中断管理。
  • GIC包括两大主要部分(由图中蓝色虚竖线分隔,Distributor和CPU Interface由蓝色虚矩形框标示),分别是:
    • ==Distributor==,其通过GICD_开头的寄存器进行控制(蓝色实矩形框标示)
    • ==CPU Interface==,其通过GICC_开头的寄存器进行控制(蓝色实矩形框标示)
  • 中断类型分为以下几类(由图中红色虚线椭圆标示):
    • ==SPI==:(shared peripheral interrupt),共享外设中断。该中断来源于外设,通过Distributor分发给特定的core,其中断编号为32-1019。从图中可以看到所有核共享SPI。
    • ==PPI==:(private peripheral interrupt),私有外设中断。该中断来源于外设,但只对指定的core有效,中断信号只会发送给指定的core,其中断编号为==16-31==。从图中可以看到每个core都有自己的PPI。
    • ==SGI==:(software-generated interrupt),软中断。软件产生的中断,用于给其他的core发送中断信号,其中断编号为0-15。
    • virtual interrupt,虚拟中断,用于支持虚拟机。图中也可以看到,因为我们暂时不关心,所以没有标注。
    • 此外可以看到(FIQ, IRQ)可通过b进行旁路,我们也不关心。如感兴趣可以查看技术手册了解细节。

外设中断可由两种方式触发:

  • edge-triggered: 边沿触发,当检测到中断信号上升沿时中断有效。
  • level-sensitive:电平触发,当中断源为指定电平时中断有效。

中断优先级:
因为soc中,中断有很多,为了方便对中断的管理,对每个中断,附加了中断优先级。在中断仲裁时,高优先级的中断,会优于低优先级的中断,发送给cpu处理。当cpu在响应低优先级中断时,如果此时来了高优先级中断,那么高优先级中断会抢占低优先级中断,而被处理器响应。
由ARM Generic Interrupt Controller Architecture Specification - version 2.0 (section 3.3)可知,GICv2最多支持256个中断优先级。GICv2中规定,所支持的中断优先级别数与GIC的具体实现有关,如果支持的中断优先级数比256少(最少为16),则8位优先级的低位为0,且遵循RAZ/WI(Read-As-Zero, Writes Ignored)原则。

GICv2初始化

  • virt.dts中intc部分

  • timer部分

  • intc中的 reg 指明GICD寄存器映射到内存的位置为0x8000000,长度为0x10000, GICC寄存器映射到内存的位置为0x8010000,长度为0x10000

  • intc中的 #interrupt-cells 指明 interrupts 包括3个cells。第一个文档 指明:

    • 第一个cell为中断类型,0表示SPI,1表示PPI;
    • 第二个cell为中断号,SPI范围为[0-987],PPI为[0-15];
    • 第三个cell为flags,其中[3:0]位表示触发类型,4表示高电平触发,[15:8]为PPI的cpu中断掩码,每1位对应一个cpu,为1表示该中断会连接到对应的cpu。
  • 以timer设备为例,其中包括4个中断。以第二个中断的参数 0x01 0x0e 0x104 为例,其指明该中断为PPI类型的中断,中断号14, 路由到第一个cpu,且高电平触发。但注意到==PPI的起始中断号为16==,所以实际上该中断在GICv2中的中断号应为16 + 14 = 30。

  • 新建 src/bsp/hwi_init.c 文件,初始化 GIC

  • 以下为对初始化文件的一些解释



    以下三个,每32个中断共享一个32位寄存器,每个寄存器四字节

    以下为中断id映射规则,警用中断即对该位置为1

  • distributor中断使能设置

  • distributor中断使能清除设置

  • distributor中断挂起状态清除设置

  • GICD_IPRIORITYR 为每个中断(SPI/PPI/SGI)配置优先级(Priority),数值越小优先级越高。

    1. 计算优先级字段的偏移量:(中断号%4) 8*

      uint32_t shift = (interrupt % GICD_IPRIORITY_SIZE) * GICD_IPRIORITY_BITS;
       - 每个优先级字段占8位(GICD_IPRIORITY_BITS=8)
      - (interrupt % 4) * 8计算出该中断优先级字段在32位寄存器中的起始位位置:
      - 第0个中断:0 * 8 = 0(占用bit70)
      - 第1个中断:1 * 8 = 8(占用bit15
      8)
      - 第2个中断:2 * 8 = 16(占用bit2316)
      - 第3个中断:3 * 8 = 24(占用bit31
      24)

      例如,对于中断号65:

      - n = 65/4 = 16 → 使用GICD_IPRIORITYR16寄存器
      - offset = 65%4 = 1 → 优先级字段在寄存器中的偏移是8位(即bit15~8)

    2. ​清除原有优先级值​
      value &= ~(0xff << shift);

      • 0xff是8位掩码(每个中断优先级占8位)
      • shift通过(interrupt % 4) * 8计算得出,定位到目标优先级字段的起始位
      • ~取反后与寄存器当前值value做按位与操作,清空目标优先级字段(保留其他字段不变)
    3. ​设置新优先级值​
      value |= priority << shift;

      • 将新优先级值priority左移到目标字段位置
      • 通过按位或操作写入寄存器值value
      • 注:优先级数值越小优先级越高(0xFF为最低优先级)
    4. ​写回寄存器​
      GIC_REG_WRITE(addr, value);

      • 将修改后的完整32位值写回GICD_IPRIORITYRn寄存器组
      • 地址addr通过基地址 + (中断号/4)*4计算得出
  • OsGicIntSetConfig 为每个中断(SPI/PPI/SGI)配置触发类型(Trigger Type),通过 ​​Int_config​​ 字段控制。

    1. ​读取当前寄存器值​
      uint32_t value = GIC_REG_READ(addr);

      • 通过内存映射方式读取目标寄存器GICD_ICFGRn的当前值
      • 该寄存器组用于配置中断触发方式(电平/边沿触发),每个中断占2位(GICD_ICFGR_BITS=2
    2. ​清除原有配置值​
      value &= ~(0x03 << shift);

      • 0x03是2位掩码(二进制11
      • shift = (interrupt % 16) * 2 计算目标中断的配置位偏移
      • 通过按位取反和与操作清空目标字段(保留其他中断的配置不变)
    3. ​设置新配置值​
      value |= config << shift;

      • config的有效值为:
        • 0b00:电平触发(Level-sensitive)
        • 0b01:边沿触发(Edge-triggered)
      • 将新配置值左移到目标位置后,通过按位或操作写入
    4. ​写回寄存器​
      GIC_REG_WRITE(addr, value);

      • 将修改后的32位值写回寄存器
      • 地址addr通过GICD_ICFGR + (中断号/16)*4计算得出(每寄存器管理16个中断)

使能时钟中断

新建 src/include/prt_config.h

1
2
/* Tick中断时间间隔,tick处理时间不能超过1/OS_TICK_PER_SECOND(s) */
#define OS_TICK_PER_SECOND 1000

新建 src/include/os_cpu_armv8.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef OS_CPU_ARMV8_H
#define OS_CPU_ARMV8_H

#include "prt_typedef.h"

// CurrentEl等级
#define CURRENT_EL_2       0x8 //EL2(Hypervisor模式)
#define CURRENT_EL_1       0x4 //EL1(内核模式)
#define CURRENT_EL_0       0x0 //EL0(用户模式)

//DALF中断屏蔽位:例如DAIF_IRQ_BIT置1会屏蔽普通中断(IRQ),常用于临界区保护
#define DAIF_DBG_BIT      (1U << 3) //调试异常屏蔽
#define DAIF_ABT_BIT      (1U << 2) //同步异常屏蔽
#define DAIF_IRQ_BIT      (1U << 1) //IRQ中断屏蔽
#define DAIF_FIQ_BIT      (1U << 0) //FIQ中断屏蔽

#define INT_MASK          (1U << 7) //全局中断屏蔽位

#define PRT_DSB() OS_EMBED_ASM("DSB sy" : : : "memory") //数据同步屏障
#define PRT_DMB() OS_EMBED_ASM("DMB sy" : : : "memory") //数据内存屏障
#define PRT_ISB() OS_EMBED_ASM("ISB" : : : "memory") //指令同步屏障

#endif /* OS_CPU_ARMV8_H */

[!NOTE] Title

  1. 异常等级定义:标识ARMv8的异常等级(Exception Level)
  2. DALF中断屏蔽位:控制DAIF寄存器(处理器状态寄存器PSTATE的一部分)的屏蔽位
  3. 内存屏障指令:- DSB​​:确保屏障前的内存访问完成后再执行后续指令(如写设备寄存器后等待生效) - ​​DMB​​:保证内存访问顺序,但不强制完成(如多核数据同步)- ​​ISB​​:清空流水线,确保后续指令从内存重新加载(如修改代码后执行)

新建 src/bsp/timer.c 文件,对定时器和对应的中断进行配置

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
#include "prt_typedef.h"
#include "prt_config.h"
#include "os_cpu_armv8.h"

U64 g_timerFrequency;
extern void OsGicIntSetConfig(uint32_t interrupt, uint32_t config);
extern void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority);
extern void OsGicEnableInt(U32 intId);
extern void OsGicClearIntPending(uint32_t interrupt);

void CoreTimerInit(void)
{
    // 配置中断控制器
    OsGicIntSetConfig(30, 0); // 配置为电平触发
    OsGicIntSetPriority(30, 0); // 优先级为0
    OsGicClearIntPending(30); // 清除中断pending
    OsGicEnableInt(30); // 启用中断

    // 配置定时器
    OS_EMBED_ASM("MRS %0, CNTFRQ_EL0" : "=r"(g_timerFrequency) : : "memory", "cc"); //读取时钟频率到g_timerFrequency

    U32 cfgMask = 0x0;
    U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND;//计算每个定时周期的计数值

    OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory");//禁用定时器
    PRT_ISB(); //指令同步屏障,确保操作顺序,保证之前的写操作对后续的指令可见
    OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期,设置初始计数值,CNTP_TVAL_EL0是递减计数器,减到0触发中断

    cfgMask = 0x1;
    OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory"); //启用定时器 enable=1, imask=0, istatus= 0,
    OS_EMBED_ASM("MSR DAIFCLR, #2"); //清除CPU的IRQ屏蔽位,允许CPU进行中断

}

清除中断挂起状态的目的是为了确保在配置定时器之前没有任何挂起的中断,如果在配置定时器之前不清除中断状态,可能会导致在配置中出现中断,并且这些中断可能会干扰到定时器的配置过程。

[!NOTE] Title
30:物理定时器中断,当定时器(CNTP_TVAL_EL0)递减到0时,硬件自动触发中断id为30的ppi,gic将中断转发给cpu,cpu跳转到中断处理

时钟中断处理

将 prt_vector.S 中的 EXC_HANDLE 5 OsExcDispatch 改为 EXC_HANDLE 5 OsHwiDispatcher,表明我们将对 IRQ 类型的异常(即中断)使用 OsHwiDispatcher 处理。

[!NOTE] Title
需修改为 EXC_HANDLE 5 OsHwiDispatcher ,否则还是 OsExcDispatch 函数处理,仅会输出 “Catch a exception.” 信息

在 prt_vector.S 中加入 OsHwiDispatcher 处理代码,其类似于之前的 OsExcDispatch ,因此不再说明。

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
    .globl OsHwiDispatcher
.type OsHwiDispatcher, @function
.align 4
OsHwiDispatcher:
mrs x5, esr_el1
mrs x4, far_el1
mrs x3, spsr_el1
mrs x2, elr_el1
stp x4, x5, [sp,#-16]!
stp x2, x3, [sp,#-16]!

mov x0, x1 // 异常类型0~15,参见异常向量表
mov x1, sp // 异常时寄存器信息,通过栈及其sp指针提供
bl OsHwiDispatch

ldp x2, x3, [sp],#16
add sp, sp, #16 // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr
msr spsr_el1, x3
msr elr_el1, x2
dsb sy
isb

RESTORE_EXC_REGS // 恢复上下文

eret //从异常返回

在 prt_exc.c 中引用头文件 os_attr_armv8_external.h , os_cpu_armv8.h , OsHwiDispatch 处理 IRQ 类型的中断。

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
//中断处理框架实现
extern void OsTickDispatcher(void);
OS_SEC_ALW_INLINE INLINE void OsHwiHandleActive(U32 irqNum)
{
    switch(irqNum){
        case 30: //arm物理定时器中断(ppi)
            OsTickDispatcher();//调用系统时钟中断处理
            // PRT_Printf(".");
            break;
        default://其他中断暂不处理
            break;
    }
}

extern  U32 OsGicIntAcknowledge(void);
extern void OsGicIntClear(U32 value);
// src/arch/cpu/armv8/common/hwi/prt_hwi.c  OsHwiDispatch(),OsHwiDispatchHandle()
/*
* 描述: 中断处理入口, 调用处外部已关中断,防止嵌套中断破坏现场
*/
OS_SEC_L0_TEXT void OsHwiDispatch( U32 excType, struct ExcRegInfo *excRegs) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
    // 中断确认,相当于OsHwiNumGet()
    U32 value = OsGicIntAcknowledge();//读取GICC_IAR寄存器
    U32 irq_num = value & 0x1ff;//提取低9位中断号
    U32 core_num = value & 0xe00;//提取cpu核标识(高 3位)

    OsHwiHandleActive(irq_num); //根据中断号调用对应处理函数

    // 清除中断,相当于 OsHwiClear(hwiNum),告知gic当前cpu已处理完资源,让gic释放相关资源
    OsGicIntClear(irq_num|core_num); // 写入GICC_EOIR寄存器
}

OsGicIntAcknowledge()​:读取GIC的GICC_IAR寄存器,获取当前中断号(含CPU核信息)
OsGicIntClear()​:写入GIC的GICC_EOIR寄存器,通知中断处理完成
OS_SEC_L0_TEXT 是一个宏,用于指定函数的存储类别和属性,以确保它被正确地放置在存储器中

[!NOTE] Title
PPI是​​中断源类型​​(核私有),而IRQ是​​CPU接收中断的物理信号​​。PPI通过GIC分发后,最终以IRQ(或FIQ)信号的形式触发CPU核的中断处理流程。这种设计实现了多核系统中中断的隔离与高效管理。

新建 src/kernel/tick/prt_tick.c 文件,提供 OsTickDispatcher 时钟中断处理函数。

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
#include "os_attr_armv8_external.h"
#include "prt_typedef.h"
#include "prt_config.h"
#include "os_cpu_armv8_external.h"

extern U64 g_timerFrequency;// 系统计数器频率(单位Hz,通过CNTFRQ_EL0读取)

/* Tick计数 */
OS_SEC_BSS U64 g_uniTicks; //src/core/kernel/sys/prt_sys.c

/*
* 描述:Tick中断的处理函数。扫描任务超时链表、扫描超时软件定时器、扫描TSKMON等。
*/
OS_SEC_TEXT void OsTickDispatcher(void)
{
    uintptr_t intSave;

//开启和关闭中断,避免中断嵌套,实现原子操作,保护临界区代码
    intSave = OsIntLock();//关中断,保存中断状态
    g_uniTicks++;//递增tick计数
    OsIntRestore(intSave);//恢复中断状态


    U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND;
    OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期
}


/*
* 描述:获取当前的tick计数
*/
OS_SEC_L2_TEXT U64 PRT_TickGetCount(void) //src/core/kernel/sys/prt_sys_time.c
{
    return g_uniTicks;
}

g_timerFrequency​:存储ARM系统计数器的基准频率,通过CNTFRQ_EL0寄存器读取。g_uniTicks​:记录系统启动后的总Tick数,每次定时器中断触发时递增。
Tick中断处理函数 (OsTickDispatcher)​:

  • 计算下一次中断的周期值(cycle = 频率 / 每秒Tick数),写入CNTP_TVAL_EL0寄存器,硬件会自动重载并开始递减计数。
  • CNTP_TVAL_EL0​:ARMv8物理定时器的递减计数器,减到0时触发中断(IRQ 30)。

在 OsTickDispatcher 中调用了 OsIntLock 和 OsIntRestore 函数,这两个函数用于关中断和开中断。简单起见,将其放入 prt_exc.c 中。

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
/*
* 描述: 开启全局可屏蔽中断。
*/
OS_SEC_L0_TEXT uintptr_t PRT_HwiUnLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
    uintptr_t state = 0;

    OS_EMBED_ASM(
        "mrs %0, DAIF      \n"// 读取当前DAIF状态到state
        "msr DAIFClr, %1   \n" // 清除DAIF寄存器的指定位,IRQ屏蔽位(开启中断)
        : "=r"(state) //输出操作数state
        : "i"(DAIF_IRQ_BIT)// 输入操作数,立即数DAIF_IRQ_BIT = (1 << 1)
        : "memory", "cc")//告诉编译器此指令会影响内存和条件码
    return state & INT_MASK;// 返回原始中断状态(仅保留INT_MASK位)
}

/*
* 描述: 关闭全局可屏蔽中断。
*/
OS_SEC_L0_TEXT uintptr_t PRT_HwiLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
    uintptr_t state = 0;
    OS_EMBED_ASM(
        "mrs %0, DAIF      \n"// 读取当前DAIF状态
        "msr DAIFSet, %1   \n"// 设置IRQ屏蔽位(关闭中断)
        : "=r"(state)
        : "i"(DAIF_IRQ_BIT)
        : "memory", "cc");
}// 返回原始中断状态
    return state & INT_MASK;

/*
* 描述: 恢复原中断状态寄存器。
*/
OS_SEC_L0_TEXT void PRT_HwiRestore(uintptr_t intSave) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
    if ((intSave & INT_MASK) == 0) {// 恢复开启中断
        OS_EMBED_ASM(
            "msr DAIFClr, %0\n" //如果中断未屏蔽,清除irq屏蔽位
            :
            : "i"(DAIF_IRQ_BIT)
            : "memory", "cc");
    } else {// 恢复关闭中断
        OS_EMBED_ASM(
            "msr DAIFSet, %0\n" //如果原来中断已屏蔽,设置irq屏蔽位
            :
            : "i"(DAIF_IRQ_BIT)
            : "memory", "cc");
    }
    return;
}

开启全局可屏蔽中断 (PRT_HwiUnLock)​:清除DAIF寄存器的IRQ屏蔽位(DAIF.I),允许CPU响应IRQ中断。返回原始中断状态(用于后续恢复)。
关闭全局可屏蔽中断 (PRT_HwiLock)​:设置DAIF寄存器的IRQ屏蔽位(DAIF.I),禁止CPU响应IRQ中断。
恢复中断状态 (PRT_HwiRestore)​:根据intSave参数恢复原始中断状态(通常与PRT_HwiLock/UnLock配对使用)

头文件 src/bsp/os_cpu_armv8_external.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef OS_CPU_ARMV8_EXTERNAL_H
#define OS_CPU_ARMV8_EXTERNAL_H

extern uintptr_t PRT_HwiUnLock(void);
extern uintptr_t PRT_HwiLock(void);
extern void PRT_HwiRestore(uintptr_t intSave);

#define OsIntUnLock() PRT_HwiUnLock() //开启全局中断
#define OsIntLock()   PRT_HwiLock() //关闭全局中断
#define OsIntRestore(intSave) PRT_HwiRestore(intSave) //恢复原中断状态

#endif

新增文件到构建系统

src/kernel, src/kernel/tick 目录下均需加入 CMakeLists.txt, src/ 和 src/bsp/ 下的 CMakeLists.txt 需修改。其中,
src/kernel/tick/CMakeLists.txt 类似 src/bsp/CMakeLists.txt

src/kernel/CMakeLists.txt 内容为: add_subdirectory(tick)
src/CMakeLists.txt 需修改增加include目录、包含子目录和编译目标:

读取系统tick值

新建 prt_tick.h,声明 Tick 相关的接口函数.

1
2
3
4
5
6
7
8
#ifndef PRT_TICK_H
#define PRT_TICK_H

#include "prt_typedef.h"

extern U64 PRT_TickGetCount(void);

#endif /* PRT_TICK_H */

修改main.c

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
#include "prt_typedef.h"
#include "prt_tick.h"

extern U32 PRT_Printf(const char *format, ...);
extern void PRT_UartInit(void);
extern void CoreTimerInit(void);
extern U32 OsHwiInit(void);

U64 delay_time = 10000;

S32 main(void)
{
    // 初始化GIC
    OsHwiInit();
    // 启用Timer
    CoreTimerInit();

    PRT_UartInit();

    PRT_Printf("            _       _ _____      _             _             _   _ _   _ _   _           \n");
    PRT_Printf("  _ __ ___ (_)_ __ (_) ____|   _| | ___ _ __  | |__  _   _  | | | | \\ | | | | | ___ _ __ \n");
    PRT_Printf(" | '_ ` _ \\| | '_ \\| |  _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| |  \\| | | | |/ _ \\ '__|\n");
    PRT_Printf(" | | | | | | | | | | | |__| |_| | |  __/ |    | |_) | |_| | |  _  | |\\  | |_| |  __/ |   \n");
    PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_|    |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_|   \n");
    PRT_Printf("                                                     |___/                               \n");

    PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n");
   
    for(int i = 0; i < 10; i++)
    {

        U32 tick = PRT_TickGetCount();//每次中断递增一个计数器
        PRT_Printf("[%d] current tick: %u\n", i, tick);
        //delay
        int delay_time = 10000000;  // 根据自己机器计算能力不同调整该值
        while(delay_time>0){
            PRT_TickGetCount();  //消耗时间,防止延时代码被编译器优化
            delay_time--;
        }
    }
  while(1);
    return 0;
}

运行

作业

实现 hwi_init.c 中缺失的 OsGicEnableInt 和 OsGicDisableInt 函数。

[!NOTE]

  • GIC的​​Distributor​​模块使用GICD_ISENABLERn寄存器组控制中断的使能状态,每个寄存器(32位)管理​​32个连续的中断号​​:

    • ​每个bit对应一个中断号​​:
      例如,GICD_ISENABLER0的bit0控制中断号0,bit1控制中断号1,依此类推。
    • ​寄存器数量动态扩展​​:
      若GIC支持1020个中断(如GICv2),则需要1020/32=32GICD_ISENABLERn寄存器。

查看技术文档可知,n=(中断号%32)* 4,使用第n个寄存器,设置对应位为1 << (中断号%32),生成仅中断号为1的掩码。

  1. 寄存器索引:37 / 32 = 1(使用GICD_ISENABLER1)。
  2. 位偏移:37 % 32 = 5
  3. 掩码:1 << 5 = 0x20(二进制00100000)。
  4. 写入GICD_ISENABLER1后,第5位(对应中断号37)被置1,其他位不变。