Windows ARM64 Internals: Pardon The Interruption! Interrupts on Windows for ARM
引言
最近,我发布了一篇博客,介绍了与Windows on ARM (WoA) 系统相关的一些基础构建块。我一直“知道”中断是高度架构相关的,而“中断模式”的实现也会因这个概念而异。鉴于此,我认为研究围绕WoA系统的中断功能会很有趣。
在这篇博客文章中,可能会有很多省略之处——包括这样的事实:(通用中断控制器) GICv4系统允许直接注入虚拟中断(例如,我的WoA系统仅在GIC版本3上),以及围绕虚拟化和中断的许多其他细微差别(尽管我们会涉及虚拟化和安全内核的“安全中断”)。
最后,这篇博客文章并不是对ARM文档中关于低级中断细节的简单复述——尽管其中一些知识是必需的,并且在本博客适用之处也进行了概述。相反,这篇博客的主题与我之前关于ARM64 Windows内部机制的博客类似——向像我一样具有x64背景的Windows研究人员展示ARM64的基础知识,并概述Windows系统上x64和ARM64中断分发的差异。
通用中断控制器(GIC)概述
传统的基于Intel的x86架构与ARM之间的一个主要区别是ARM采用了通用中断控制器(GIC)。高级可编程中断控制器(APIC)可能是大多数来自Windows背景的人所熟悉的控制器,因为这是Intel的中断控制器家族——大多数Windows机器运行在Intel上。
ARM上的GIC已经经历了多次迭代。进行分析所用的Surface Pro机器采用了GICv3。然而,ARM现在已经有了GICv5的文档,这是几个月前ARM宣布的。博客的这一部分只是为了介绍基础知识,好奇的读者应该访问ARM文档以获取更多信息。
在ARM系统上实现GIC的主要目的是提供一种标准化的中断处理方式。下图来自ARM,提供了GIC中断传递的高级概述。
(此处应有一张GIC中断传递的流程图,由ARM提供,但原文中未包含实际图片链接,仅为占位说明)
博客的这一部分不会作为围绕GIC功能的术语“词汇表”。ARM提供了关于低级细节的文档。然而,对于我们来说,值得特别提及以下几点,特别是关于GICv3中存在的特性(尽管不一定仅限于GICv3):
- 中断类型:有两种类型的中断:IRQ和FIQ。
- IRQ是正常优先级的标准中断请求。
- FIQ是比IRQ优先级更高的快速中断请求。
- 四种主要中断“源”:
- 外部(共享外设中断,SPI):这里的“外部”意味着中断可以传递给任何处理器。
- 内部(私有/每处理器外设中断,PPI):这是特定处理器私有的中断。PPI的一个例子是在特定处理器上生成的性能中断。PMU是一个每CPU的构造,可以配置目标CPU以生成性能相关信息,当某些条件为真时生成中断——导致一个PPI。
- 基于软件的(软件生成中断,SGI):这是“ARM”版本的IPI(处理器间中断),即一个核心向另一个核心发送中断。
- 特定于区域的(特定于区域的外设中断,LPI):这些始终是基于消息的中断,可以从中断转换服务(ITS)生成。进行分析的Surface Pro机器确实实现了ITS。但是,由于操作系统是虚拟化的,Hyper-V并未将其暴露给根分区。然而,这并不意味着基于消息的中断不能被生成和处理。
- 正如在之前的博客文章中提到的,Windows在启用VBS时并不真正使用TrustZone(VTL提供了非安全/安全世界功能)——中断也分为“安全”和“非安全”两类(与TrustZone安全状态相关)。
- GIC允许为管理程序提供虚拟中断功能(vGIC)(根据GIC版本存在细微差别,稍后会详述)。
除了处理来自“中断信号”(有时称为硬件“触发”或“通知”中断控制器)的中断外,GIC还支持基于消息的中断。这些中断的传递机制略有不同(稍后会详述)。由于每个中断源都由多个中断ID(INTID)组成(例如,中断ID 0-15是SGI,16-31是PPI等),这意味着并非每个ID都需要物理连线。
注意上面我们提到了中断源——由特定的INTID表示——它映射到一条“中断线”(具有特定的“组”,例如SPI、PPI等)。中断“来自”中断源。最后,在我们深入Windows实现之前,让我们总结一下GIC架构中四个重要的结构,它们统称为中断路由基础设施(IRI)。IRI和中断路由方案如下图所示(取自Arm通用中断控制器架构规范)。
(此处应有一张IRI和中断路由方案的图表,但原文中未包含实际图片链接,仅为占位说明)
GIC分发器
GIC分发器是中断模式的“大脑”——所有物理中断源都连接到这个组件。它物理存在于特定的SoC(片上系统,这是ARM将CPU/GPU/内存控制器/外设等集成到“单一芯片”中的方式)上,并且始终可以通过物理内存访问,而不是系统寄存器(但Windows内核也将其映射到虚拟内存中)。系统中只有一个分发器结构。
分发器主要负责将物理中断优先排序并分发给重新分配器(和CPU接口)。这对于SPI尤其重要,因为它们对于特定CPU是“外部”的,因为分发器必须将中断路由到特定的CPU。
分发器参与软件生成的中断(如IPI,即使中断源自特定处理器)并促进路由。然而,对于特定于CPU的中断(如PPI),分发器不需要参与。
GIC重新分配器
重新分配器是每CPU结构——每个CPU只有一个重新分配器。重新分配器接收从中断源路由到分发器的SPI。重新分配器有更多“活动部件”或细微差别。
此外,当软件发起的中断(例如从软件请求的处理器间中断)发生时(SGI),它们由“发出”CPU接口和重新分配器生成。从这些组件,它们然后被路由到分发器,然后目标CPU的重新分配器和CPU接口接收中断。
PPI是特定于特定CPU的中断。因此,根本不需要分发器。中断源中断直接路由到CPU的重新分配器。此外,LPI被路由到目标重新分配器。
GIC CPU接口
然后,各种CPU接口成为核心实际接收中断的机制。存在物理CPU接口和虚拟CPU接口(但目前当我们提到“CPU接口”时,我们指的是物理接口)。CPU接口可以通过系统寄存器(或内存映射接口)访问,但Windows使用系统寄存器。这意味着这些寄存器可用于屏蔽中断并控制CPU上中断的状态。
GIC中断转换服务(ITS)
ITS在GICv3中是可选的(我们的机器使用的是GICv3)。ITS有一个主要用途——基于消息的中断(MSI)。当ITS存在时,它负责将LPI(代表基于消息的中断)路由到目标CPU的重新分配器。它们还负责实际将MSI请求(基于消息的中断)转换为LPI。
进行分析的Surface Pro机器没有实现ITS,但它仍然支持LPI(支持LPI但不要求ITS)。GIC4需要ITS,因为GIC4需要支持虚拟LPI,以支持将虚拟中断直接注入到VM而不涉及管理程序。
最后,下图总结了基本的中断例程方案——再次取自ARM文档。
(此处应有一张基本中断例程方案的图表,但原文中未包含实际图片链接,仅为占位说明)
Windows on ARM中断初始化与发现
尽管在此之前有一些对中断功能的引用,但我们将从nt!HalpInitializeInterrupts开始。这个函数负责我们关心的大部分中断发现和初始化工作。nt!HalpInitializeInterrupts接收一个参数——来自引导加载程序的加载器参数块,由nt!_LOADER_PARAMETER_BLOCK结构表示。
这个函数首先做的事情之一是执行“GIC”发现。内核将首先尝试发现GICv3,并“默认”检查GICv1是否可用。
(此处应有一张反汇编代码截图,但原文中未包含实际图片链接,仅为占位说明。截图显示内核尝试发现GICv3,失败则尝试GICv1,并使用nt!HalSetInterruptProblem报告发现失败)
作为一个争论点,nt!HalSetInterruptProblem接受一个来自INTERRUPT_PROBLEM枚举的参数值。对于ARM设备,以下是有效值,可以帮助调试/确定正在发生的情况。例如,在上图中,错误表示发现正在进行(InterruptProblemFailedDiscovery):
|
|
nt!HalpGic3Discover首先枚举名为“APIC”的高级配置与电源接口(ACPI)表。为了使硬件抽象层(HAL)的工作量减少,Windows实际上要求运行Windows的ARM64系统必须支持ACPI。
ACPI是一种规范,用于允许硬件向软件描述可用的接口。ACPI对我们特别相关,因为它描述了系统上的中断功能。毕竟,中断不仅仅是一个软件构造——例如,实际的计算机芯片具有用于许多中断操作的物理连线。因此,ACPI接口暴露了一系列表格,允许操作系统了解机器的实际硬件配置——包括中断配置。
ACPI“APIC”表实际上指的是多APIC描述表(MADT)。虽然使用了APIC这个名称,因为基于Intel的系统长期以来占主导地位,但最新版本的ACPI(5.0及更高版本)已经添加了GIC的描述符——这是基于ARM的系统使用的(不是APIC)。我们所说的MADT负责描述系统的中断功能——特别是描述GIC和GIC分发器(我们之前提到过)。
在这种情况下,nt!ExtEnvGetAcpiTable返回一个指向nt!_MAPIC结构的指针——该结构代表MADT,并具有以下布局。您可以将此布局与UEFI的最新ACPI规范交叉参考:
|
|
此结构的APICTables成员对应于官方ACPI规范中概述的“中断控制器结构[n]”——指的是系统上可用的中断控制器结构列表。
在这种情况下,nt!_MAPIC结构充当一种头部,用于描述随后的各种中断结构——所有这些结构共同构成了系统上的中断功能。WinDbg提供了一个很好的扩展,允许我们轻松解析存在的功能:
|
|
我们可以看到这里存在许多结构:GIC分发器(只能有一个),“处理器本地GIC”,指的是我们之前提到的每CPU“接口”。我的机器有6个CPU和总共12个核心(我们可以看到这里有6个条目。这些由ACPI规范中提到的“GIC CPU接口(GICC)结构”表示),每CPU的GIC重新分配器(GIC重新分配器(GICR)结构),以及一个MSI(GIC MSI帧结构)接口。
然后,nt!HalpGic3Discover负责解析所有存在的中断结构,并让内核了解支持的GIC功能类型(是否支持LPI,是否需要中断转换服务,有多少启用的GIC CPU接口等)。nt!HalpGic3Discover接收一个参数——来自EXT_ENV枚举的一个值,表示有关当前操作环境的更多信息——并向下传递给初始化堆栈。例如,在我们的情况下,操作环境是ExtEnvHvRoot——因为我所在的机器启用了VBS,因此Windows操作系统驻留在根分区中。这意味着GIC需要与根分区交互。正如我们稍后将看到的,特别是在虚拟中断的情况下,了解执行环境很重要。
|
|
然而,作为一个争论点,动态分析是在虚拟机中进行的,因此(显然)操作环境是客户机:
|
|
成功后,GIC分发器通过nt!Gic3ValidateIoUnit进行验证。如前所述,单个GIC分发器是所有中断源连接的地方。这是一个非常重要的结构。在Windows上,nt!_GIC_DISTRIBUTOR结构代表GIC分发器。
Windows定义的GIC分发器结构用于描述GIC分发器。然而,GIC分发器实际上被映射到物理内存中,并在Windows上通过nt!_GIC_DISTRIBUTOR结构的ControllerPhysicalAddress成员访问。该地址具有ARM在此处描述的实际GIC分发器的布局。此结构不在Windows符号中(我手动将其添加到IDA中),填充了内核的其余“启发性”数据——包括支持的安保状态数量、是否支持LPI(支持——但未使用)、扩展SPI支持以及最后一次GIC版本检查。
在Windows内核验证GIC之后,实际的中断控制器向Windows注册。这是通过nt!HalpGic3RegisterIoUnit函数完成的。此函数负责填充用于构建系统上已注册中断控制器列表的信息。在执行此分析的机器上,只有一个已注册的中断控制器。这是通过填充nt!_REGISTERED_INTERRUPT_CONTROLLER结构并将其添加到由nt!HalpRegisteredInterruptControllers符号管理的已注册中断控制器的双向链表中,同时递增nt!HalpInterruptControllerCount的数量来实现的。使用WinDbg,我们实际上可以解析整个链表(其中只包含一个链接)以查看已注册中断控制器的内容。
(此处应有一张WinDbg解析已注册中断控制器的截图,但原文中未包含实际图片链接,仅为占位说明)
在这里我们可以看到,这应该不足为奇,KnownType设置为InterruptControllerGicV3——这似乎表明我们正在查看正确的结构。这就是Windows如何从中断功能发现到实际从硬件可用的内容向操作系统注册中断控制器的过程。已注册的中断控制器还包含一个函数列表(由nt!_INTERRUPT_FUNCTION_TABLE结构表示),这些函数允许进一步配置中断控制器和/或从HAL进行交互。这些不是“中断处理程序”函数。
注册控制器后,它尚未完全初始化。首先,GIC版本被保留(nt!HalpInterruptGicVersion)。接下来,在每个中断控制器(在我们的情况下只有一个)实际完全初始化之前,许多关键的低级中断处理程序,如CPU的SGI(例如,处理器间中断,通过KiIpiServiceRoutine)、重启服务(nt!HalpInterruptRebootService)等,通过nt!HalpCreateInterrupt注册。nt!HalpCreateInterrupt负责分配一个中断对象(nt!_KINTERRUPT)——它代表特定类型的中断,并允许操作系统/软件注册特定的中断服务例程(KINTERRUPT->ServiceRoutine)。nt!KeInitializeInterruptEx负责填充nt!_KINTERRUPT对象的大部分内容,包括从nt!HalpCreateInterrupt传递的参数——例如ServiceRoutine、Vector(稍后会详细介绍,但最大值为0xFFF)和Irql(中断发生时CPU将处于的IRQL)。
创建各种中断对象后(我们尚未调用nt!HalpInterruptInitializeController),它们通过nt!KiConnectInterruptInternal连接到IDT。
nt!KiConnectInterruptInternal(在Windows on ARM上)做的第一件事是执行一些基本验证。要连接的目标中断的向量号不能大于4095(稍后会详细介绍),与目标中断关联的IRQL不能高于HIGH_LEVEL,确保KINTERRUPT对象的Number成员有效(这不是中断号,而是中断已初始化所针对的目标CPU号),并确保与中断对象关联的SynchronizationIrql(获取存储在中断对象本身中的锁时的IRQL)有效。
基本验证后,内核将通过KPCR->Idt(通过nt!KiGetIdtEntry)索引每CPU的IDT,以定位要连接到IDT的中断对象将驻留的目标位置(注意我们不使用KPCR->IdtBase,这是x64上的相关字段。此字段在ARM64上不存在)。这将存储前16个已注册的中断。超过前16个的任何中断将存储在扩展IDT中(KPCR->IdtExt)。
例如,SGI/IPI中断通过调用nt!HalpCreateInterrupt注册,参数如下:
|
|
0xE01代表KINTERRUPT.Vector中断对象值。这可以在下面看到。
(此处应有一张显示KINTERRUPT.Vector值的WinDbg截图,但原文中未包含实际图片链接,仅为占位说明)
这意味着在我们的情况下,nt!KiGetIdtEntry将索引第一个“常规”IDT(KPCR->Idt),因为低四位小于最大值16。然而,在x64和ARM64之间访问特定IDT条目存在一些差异(例如,CPU不知道通过IDTR的IDT布局,因为正在使用通用中断控制器)。我们将在中断分发和处理部分详细讨论。此外,虽然此处的变量名为VectorIndex,但这并不完全正确。此值包含的不仅仅是向量索引。这可以通过在软件中访问此值的方式看出:
- 提取
KINTERRUPT.Vector的高字节(0xE0) - 将低四位加到剩余值中。
在我们的示例中,0xE01变为0xE1。这是目标中断在IDT中的索引。这就是实际中断对象写入IDT的位置。
作为旁注,向量的值似乎是目标中断的IRQL和IDT中实际索引的有效掩码。所以0xE01的IRQL为0xE,等等。——只有一个例外,我目前不确定原因——那就是与重启相关的中断。出于未知原因,这个中断对象(nt!HalpInterruptRebootService)的向量为0xd07,实际IRQL为0x7。
(此处应有一张显示重启中断对象的向量和IRQL的WinDbg截图,但原文中未包含实际图片链接,仅为占位说明)
在这种情况下,似乎可以有16个IRQL(与Windows on ARM上一样),每个IRQL可以有16个关联的中断——总共256个中断。这是合理的,因为技术上处理器控制区域(nt!_KPCR)中的IDT数组技术上是一个硬编码的256元素数组!
作为旁注,在我当前的ARM机器上,具有已注册中断的“最低”IRQL是0x2,即DISPATCH_LEVEL。此中断的服务例程是nt!HalpInterruptSwServiceRoutine——这似乎表明这是软件中断服务例程(它是真实函数nt!KiSwInterruptDispatch的包装器,该函数因与PatchGuard关联而闻名,并且也存在于x64 IDT中。它似乎不是一个实际的中断处理程序,更像是一个“通过模糊实现安全”的特性)。
一旦初始中断对象已连接到中断控制器的软件表示(nt!HalpRegisteredInterruptControllers),就会调用nt!HalpInterruptInitializeController——它执行许多低级中断初始化逻辑。这有效地开始于将范围内的已注册中断控制器转发给nt!HalpInterruptInitializeLocalUnit。
nt!HalpInterruptInitializeLocalUnit首先检查DAIF系统寄存器是否设置了DAIF.I——这表明是否屏蔽了IRQ异常。这是检查当前异常级别是否将接收中断的另一种方式。在我当前的机器上,在系统初始化的这个阶段,FIQ和IRQ都被屏蔽。如果出于某种原因,IRQ和FIQ没有被屏蔽(有效地“临时禁用”),此函数将把DAIFSet设置为掩码0b11——这允许写入DAIF系统寄存器。
中断临时禁用后,nt!HalpInterruptInitializeLocalUnit将调用nt!HalpGic3InitializeLocalUnit。nt!HalpGic3InitializeLocalUnit是已注册中断控制器的注册函数之一(REGISTERED_INTERRUPT_CONTROLLER.FunctionTable[InitializeLocalUnit])。nt!HalpGic3InitializeLocalUnit接受一个已注册控制器的内部数据参数(REGISTERED_INTERRUPT_CONTROLLER.InternalData)。内部数据在nt!HalpGic3RegisterIoUnit中填充,构建后,内部数据存储在全局变量nt!HalpGic3中。此内部数据可作为GIC3_DATA结构访问——该结构在符号中。内部数据使用ANYSIZE_ARRAY模式在其自身之后也存储N个GIC3_LOCALUNIT_INFO结构——其中N指的是当前机器上的CPU数量。
(此处应有一张GIC3_DATA结构布局的WinDbg截图,但原文中未包含实际图片链接,仅为占位说明)
GIC3_DATA结构中值得指出的一些项目,它们提供了额外的抽象层。大多数其他数据是不言自明的:
IoUnitBase= GIC分发器的物理地址IoUnit= GIC分发器的映射虚拟地址GsiBase= 来自ACPI规范——这是全局系统中断(GSI)基值。有效地是可用有线中断编号的基数。与ARM的INTIDs 1:1映射Identifier= GIC分发器的硬件ID
nt!Halp3Gic3InitializeLocalUnit首先定位目标CPU的本地单元信息——由前面提到的_GIC3_LOCALUNIT_INFO结构表示。如果本地CPU接口尚未初始化,则对其进行配置。本地单元是本地CPU中断模式的表示——包括重新分配器和CPU接口信息。从这些结构中解析ACPI的中断表以获取重新分配器和CPU接口结构。从这些结构中,重新分配器的物理地址被映射到虚拟内存,提取各种触发模式(性能和维护中断被指定为电平敏感或边沿敏感。边沿敏感意味着只有在物理中断线上实际发生变化时才“接收”中断(例如,0 -> 1,如电压从高到低或从低到高)。电平敏感意味着当中断线被断言时(如果我们过于简化,则该线“设置为1”),无论这是否是从先前状态的变化)都会接收/报告中断)。此外,MPIDR_EL1系统寄存器(多处理器亲和寄存器)被保留——该寄存器包含关于目标处理器的标识信息(有效地是唯一处理器标识符,具有更细粒度的信息,如处理器集群中的集群ID——用于共享资源等的处理器分组)。在这种情况下,所有“非标识符”位(寄存器中表示元数据的位,如指示单处理器系统)被清除,亲和力位用于识别CPU(亲和力级别0、1、2、3)。
(此处应有一张MPIDR_EL1寄存器值及亲和力位提取的截图或说明,但原文中未包含实际图片链接,仅为占位说明)
最后,重新分配器被映射到虚拟内存中(映射大小由HalpGic3RedistMapSize表示,该值在nt!HalpGic3RegisterIoUnit中计算)。这将本地单元标记为已初始化(GIC3_LOCALUNIT_INFO->Initialized = 1)。
接下来,读取适当的中断控制器系统寄存器启用寄存器,即ICC_SRE_ELX。这里值得指出一些细微差别。ICC_XXX实际上在我们的情况下取代了GICC_XXX。GICC_XXX指的是传统寄存器。根据文档,在GICv3中,物理CPU接口寄存器以ICC为前缀,虚拟CPU接口寄存器以ICV而不是GICV为前缀。这就是为什么在Windows中,例如,您只会看到写入ICC_XXX系统寄存器。
如果尚未设置,内核将始终设置位1。这是ICC_SRE_ELX.SRE位——它表示是应使用内存映射接口还是系统寄存器接口来与目标CPU的GIC CPU接口交互。通过将该值设置为1,表示将使用系统寄存器接口(正如GIC文档也指出,当亲和路由用于所有启用的安保状态时,必须使用系统寄存器。值得指出的是,像GIC分发器这样的项目始终是内存映射的)。
然后内核暂时禁用组1中断(只有2个组,组0用于在EL3处理的中断,因此组1用于当前异常和安保级别的所有其他中断。记住Windows不使用传统的安保级别,因为它已经使用VTL来分隔“安全和非安全世界”),将中断优先级过滤器设置为0(意味着CPU将接受优先级高于0的中断。0是最高值,因此这实际上意味着只有优先级高于0的中断才能通过。鉴于0是最高优先级,因为数字越低优先级越高,这也有助于在本地单元配置之前禁用中断),并且还将EL1的中断控制器二进制点寄存器设置为值3——这是所需的最小值。
此时,可能值得简要提一下中断分组。中断分组允许GIC根据一组特性对中断进行分组——特别是与ARM安保和异常模型对齐。中断分组按安保状态(非安全和安全世界)和异常级别对中断进行分组。也值得指出的是,Windows只使用组1中断,并且专门在非安全状态下使用。这可以通过从GIC分发器读取GICD_CTLR.EnableGrpXXX值来确认——该值描述了启用哪些中断组。这也可以通过在ntoskrnl.exe和hvaa64.exe(Hyper-V)中搜索缺少对系统寄存器ICC_IAR0_EL1、ICC_EOIR0_EL等的写入来进一步确认,其中0指的是组0——这些是与在EL3处理的中断相关的中断,EL3是“非安全和安全世界之间的桥梁”。
|
|
继续,nt!HalpGic3InitializeLocalUnit然后继续填充GIC3_LOCALUNIT_INFO结构中的一些额外的GIC重新分配器信息。首先,来自LPI配置表的信息(由范围内的CPU的GIC重新分配器跟踪)被添加到我们迄今为止一直在检查的“内部数据”中(通过nt!HalpGic3跟踪)。这是通过访问来自GIC重新分配器的GICR_PROPBASER寄存器实现的——该寄存器指定LPI配置表。
GIC3_DATA结构的LpiConfig成员(类型为LPI_CONFIG_TABLE_ENTRY)维护目标CPU的LPI表(以及所有其他LPI配置表)的虚拟地址。注意,重新分配器的格式由ARM记录,不是Windows符号的一部分。
接下来,LPI挂起表被映射到虚拟内存中,这次通过本地单元的结构(GIC3_LOCALUNIT_INFO)作为PendingTable成员进行跟踪。这是通过访问来自GIC重新分配器的内存映射接口的GICR_PENDBASER寄存器实现的。此外,代表虚拟内存中GIC状态的全局GIC数据结构(nt!HalpGic3)更新每CPU的崩溃转储信息。挂起的LPI表也被添加到崩溃转储信息中。
需要指出的一件事——应该注意,从GIC重新分配器寄存器(包含GICR_CTLR等)开始,偏移0x10000(64KB)之后是负责配置SGI和PPI的GIC重新分配器寄存器。它们也由ARM记录。GIC文档中也指出了这一点:
每个重新分配器在物理地址映射中定义两个64KB帧:
RD_base用于控制重新分配器的整体行为,用于控制LPI,以及在不包含至少一个ITS的系统中生成LPI。SGI_base用于控制和生成PPI和SGI。
这意味着从GIC3_LOCALUNIT_INFO->Redistributor + 0x1000开始包含SGI/PPI重新分配器寄存器的起始位置。从SGI/PPI寄存器中,配置GICR_ICENABLER0寄存器,即中断清除使能寄存器0。通过将启用位(通过写入值1指示)设置到目标寄存器来配置此寄存器,以启用将所有中断转发到GIC重新分配器——同时对为ARM框架架构A(FF-A)保留的任何SGI(GICR_ICENABLER0封装了SGI和PPI)保持敏感。具体来说,进行FFA_FEATURE调用以获取计划接收器中断(SRI)的中断ID(INTID),并确保此中断ID始终被禁用。然而,这仅适用于某些操作环境(如没有Hyper-V存在的情况),因此,我的机器显示nt!HalpFfaEarlyErrorRecords(一个与初始化FF-A相关的错误数组)报告STATUS_NOT_SUPPORTED错误,该错误从FFA_ERROR代码NOT_SUPPORTED转换而来(因此,无需担心与FF-A的SRI相关的SGI的“特殊”处理)。这意味着没有为FF-A的SRI保留SGI。这只是我觉得需要指出的一点。这也可以通过检查nt!HalFfaSupported和nt!HalFfaInitialized的存在来进一步验证——这两个变量表示FFA支持和状态。
最后,nt!HalpGic3InitializeLocalUnit做的最后一件事之一是配置ICC_CTLR_EL1系统寄存器,即中断控制寄存器。如果操作环境是ExtEnvHypervisor,则设置ICC_CTLR_EL1.EOIMode(中断结束模式)。否则(在我们的情况下如此,因为我们的操作环境是ExtEnvHvRoot),EOIMode设置为0。中断结束(EOI)指的是为指示已处理目标中断的软件例程已完成而采取的特定操作。寄存器中的值0表示,例如,写入ICC_EOIR1_EL1(用于组1中断)既负责“优先级下降”,也负责中断的停用。而值1表示需要写入单独的寄存器以进行停用。[ARM]关于配置GIC的文档指出,此模式(EOIMode == 1)用于虚拟化目的。
nt!HalpGic3InitializeLocalUnitData通过ICC_IGRPEN1_EL1重新启用中断(现在本地CPU单元(重新分配器和CPU接口)已配置)而结束(稍后nt!HalpInterruptMarkProcessorStarted将处理器标记为已“启动”以处理中断)。
在nt!HalpGic3InitializeLocalUnit数据退出后,一个每CPU(技术上每核心,我的系统有12个核心)结构INTERRUPT_TARGET被填充并由符号nt!HalpInterruptTargets管理。这是通过nt!HalpGic3ConvertId实现的。这些结构概述了CPU模式的附加