本文档翻译自文档Arm Generic Interrupt Controller v3 and v4 - Virtualization
Armv8-A选择性的支持虚拟化。为了完成该功能,GICv3也支持虚拟化。GICv3中对虚拟化的支持包括如下功能:
NOTE:GIC架构不提供Distributor,Redistributor或ITS的虚拟化。这些接口的虚拟化必须由软件处理。这超出了本文档的范围,因此不在这里进行描述。
Hypervisor创建,控制并管理虚拟机VM。一个虚拟机在功能上等于一个物理系统并包含一个或多个虚拟处理器。每个处理器包含一个或多个虚拟PE。
GICv3.x和GICv4.1支持的虚拟化工作在PE级别。比如,当创建一个虚拟中断,它发往的是一个特定的vPE,而不是虚拟机VM。通常GIC并不知道虚拟机相关的vPE是如何不同的。这在后续介绍的控制非常重要。
本文档使用术语hypervisor表示的是负责管理vPE运行在EL2的任意软件。在本文档中,我们可以忽略虚拟软件之间的不同,因为我们主要集中在GIC的特性上。但是,并非所有的虚拟化方案会使用GIC中所有有效特性。
给定的vPE可以被描述为scheduled或not-scheduled。一个scheduled vPE表示hypervisor调度vPE到一个物理pPE并且运行。一个系统可能包含超过pPE数目的vPE。没有被hypervisor调度的vPE不运行因此当前不能接受中断。
本章节给出GICv3虚拟化支持的overview。GICv3虚拟化与GICv2的支持类似,主要的虚拟化在CPU interface。它允许虚拟中断被发送到在pPE上的当前被调度的vPE上。
CPU Interface分成三组:
下图呈现了CPU interface寄存器的三个组:
这些寄存器的名字格式为ICC_*_ELx。
运行在EL2的hypervisor使用ICC_*_ELx寄存器处理物理中断。
这些寄存器的名字格式为ICH_*_ELx。
Hypervisor访问额外的寄存器来控制架构提供的虚拟化特性。这些特性如下所示:
这些寄存器控制物理PE的虚拟特性。不可能访问另一个PE的状态,即运行在PE X不能访问PE Y的状态。
这些寄存器的名字格式为ICV_*_ELx。
运行在虚拟化环境下的软件使用ICV_*_ELx寄存器处理虚拟中断。这些寄存器与ICC_*_ELx寄存器有相同的格式和功能。
ICV和ICC寄存器具有相同的指令编码。在EL2和EL3上,ICC寄存器通常是可访问的。在EL1上,HCR_EL2的路由bit决定是ICC还是ICV寄存器被访问。
ICV寄存器被分成三组:
Group0
用于处理Group0中断的寄存器,比如ICC_IAR0_EL1和ICV_IAR0_EL1。当HCR_EL2.FMO=1时,在EL1上ICV寄存器被访问,而不是ICC寄存器被访问。
Group1
用于处理Group1中断的寄存器,比如ICC_IAR1_EL1和ICV_IAR1_EL1。当HCR_EL2.IMO=1时,在EL1上ICV寄存器被访问,而不是ICC寄存器被访问。
Common
用于处理Group0和Group1中断的寄存器,比如ICC_DIR_EL1和ICV_DIR_EL1。当或HCR_EL2.IMO=1或HCR_EL2.FMO=1时,在EL1上运行ICV寄存器被访问,而不是ICC寄存器被访问。
NOTE:ICV寄存器是否在安全EL1上被使用依赖于安全虚拟化是否被使能。
下图描述了一个例子,相同的指令基于HCR_EL2路由控制是访问ICC还是访问ICV寄存器。
hypervisor可以通过使用List寄存器ICC_LR
vINTID(虚拟INTID)
虚拟环境下的INTID
State
虚拟中断的状态(Pending,Active,Active and pending,或Inactive)。状态机自动更新,因为虚拟环境下的软件与GIC交互。比如,hypervisor可能创建一个新的虚拟中断,初始化时将状态设置为Pending。当vPE上的软件读取ICV_IARn_EL1时,状态被更新为Active。
Group
在非安全状态时,虚拟环境通常行为为GICD_CTLR.DS=1。在安全状态时,虚拟环境通常表现为GICD_CTLR.DS=0,将FIQ路由到EL1。因此这两种情况下虚拟中断可以为Group0或Group1。Group0中断被当作vFIQ。Group1中断被当作vIRQ。
pINTID(物理INTID)
一个虚拟中断可选择性的被物理中断的INTID标记。当vINTID的状态机被更新时,这也是pINTID的状态机。
List寄存器不会记录目标vPE。List寄存器指定了当前被调度的vPE,当被调度的vPE发生变化时,由软件来进行上下文切换List寄存器。
下图呈示了一个物理中断发往一个vPE的例子:
这个过程如下:
这个例子显示了一个物理中断作为虚拟中断被发送给vPE。比如,它可以由外设通过hypervisor发送给VM。并非所有虚拟中断都是由于物理中断产生。虚拟化软件在任何时候在List寄存器中产生虚拟中断。
如果虚拟CPU interface中某些条件为真,CPU interface可以被配置产生物理中断。
这些中断可以以INTID 25的PPI报告。该中断通常可以被配置为非安全Group1,并由EL2的hypervisor软件处理。
维护中断的产生由ICH_HCR_EL2控制,发出的中断在ICH_MISR_EL2中被报告。
如果在虚拟CPU interface中vPE清除Group enable位中的其中一个时将产生一个维护中断。当看到该中断时,hypervisor可以移除属于该disable group的pending虚拟中断。
当在vPE之间进行上下文切换时,hypervisor软件会保存一个vPE并加载另一个vPE的上下文。虚拟CPU interface的状态形成一个vPE的部分上下文。虚拟CPU interface状态包含如下信息:
可以通过ICH寄存器访问ICV寄存器。如下例子,下图呈示了ICH_VMCR_EL2的各域是如何映射到ICV寄存器状态。
当切换vPE时必须对有效虚拟优先级进行保存和恢复。通过ICH_APxRn_EL2寄存器访问当前vPE的有效优先级。
如管理虚拟中断所描述的,通过List寄存器管理虚拟中断。这些寄存器的状态对当前vPE是特殊的。因此,这些寄存器在切换上下文时被保存和恢复。
GICv4继承之前章节介绍的所有虚拟化的支持。它增加了虚拟中断的直接注入。该特性允许软件向ITS描述物理event是如何映射到虚拟中断。如果虚拟中断发往的vPE正在运行时,虚拟中断可以直接发往而不需要先进入hypervisor。这可以通过减少进入hypervisor的次数来减少虚拟中断相关的overhead。
GICv4.0支持直接注入虚拟LPI。GICv4.1扩展该特性到虚拟SGI。GICv4.0和GICv4.1之间存在多个不同点,这让它们不会彼此兼容。本文档覆盖GICv4.1编程模式。
GICv4.0和GICv4.1中的直接注入仅限于非安全状态。在安全状态不支持直接注入。
本节首先给出GICv4.0中是直接注入是如何工作的。下面章节提供更多细节。
GICv4.0允许软件定义多个虚拟PE,并将物理中断映射到这些虚拟PE。一个vPE由vPEID区分。vPEID为全局ID,由系统中所有Redistributor和ITS共享。
vPE的配置和状态保存在基于内存的表中。这与物理LPI的配置和状态是如何被管理类似。这里有三种类型的基于内存的表,Redistributor使用这些表来管理虚拟中断:
虚拟LPI pending表
这是一个Per-vPE的虚拟LPI pending表。它保存了目标为vPE的虚拟中断的pending状态。
虚拟LPI配置表
虚拟LPI配置表保存vLPI的配置(使能和优先级)。一个虚拟配置表可能由多个vPE共享。比如,一个虚拟机的所有vPE可能共享一个虚拟LPI配置表。
vPE配置表
vPE配置表保存所有vPE的设置。在这个表中每个vPE存在一个entry,保存一个指针指向vPE的虚拟pending和配置表。一个vPE配置表项也保存vPE的其他信息,比如vINTID命名空间的大小。一个vPE配置表由多个Redistributor共享。
下图显示了这些表的关系:
vPE配置表的位置和大小由软件通过GICR_VPROPBASER寄存器指定。表中表项需要被遍历为使用ITS命令的副作用。
GIC需要知道哪个vPE当前被调度在一个物理PE上。对于GICv4.0,当前被调度的vPE由GICR_VPENDBASER指定。
软件使用ITS命令来创建和管理vPE。VMAPP定义一个新的vPE,指定虚拟pending和配置表的配置和位置。这些信息被保存在vPE的配置表中。VMAPI和VMAPTI将物理中断映射到目标为某个vPE的虚拟中断。
下图显示了当一个中断目标为一个vPE时将发生何事:
ITS和Redistributor可以缓存不同表的信息。因此,实际上并不是所有中断要求从内存访问中取出表的内容。
Redistributor为vPE取出配置和从vPE配置表中取出vINTID。
Redistributors被分组,这是通过GICR_TYPER.CommonLPIAff和GICR_TYPER.Affinity定义的group。CommonLPIAff在亲和值中作为一个mask,在mask被应用后Redistributor的相同亲和性值为相同group的一部分。
比如,如果CommonLPIAff=2,有相同Aff3.Aff2值的Redistributor处于相同的group中。
考虑有四个Redistributor的系统,如下亲和性:
在mask被应用后,上述变成:
即我们有两个组group0 0.1.x.x和group1 0.0.x.x。
CommonLPIAff组被认为是物理上相互连接的Redistributor。比如,在多chip设计中对每个chip都有一个group。
CommonLPIAff值非常重要,因为它决定软件需要分配多少内存结构以及一个vPE被调度到哪个Redistributor。我们将在下面章节继续讨论。
vPE配置表存在所有vPE的细节。表的大小显示了软件可以创建的vPE的大小。
由GIC对vPE配置表进行遍历和维护,这是ITS命令的副作用。在内存分配给GIC后软件不再读或写该表。如果读或写会导致GIC行为不正确。
软件必须对每个CommonLPIAff组分配一个表的拷贝。即,如果有两个CommonLPIAff组,软件必须为表的两个拷贝分配足够的内存。这是一个性能优化,因为它允许Redistributor使用靠近它的内存。
在创建vPE映射之前软件必须分配要求的表的数目并遍历每个Redistributor的GICR_VPROPBASER。
NOTE: 属于不同CommonLPIAff组的Redistributor不能共享vPE配置表的拷贝。
哪个vPE当前被调度到PE上是由GICR_VPENDBASER。为也修改被调度的vPE,软件必须:
清GICR_VPENDBASER.Valid
清Valid位是通知Redistributor发生了上下文切换。Redistributor从虚拟CPU interface取出任何pending虚拟中断,并保证内存中虚拟LPI pending表是正确的。
轮询GICR_VPENDBASER.Dirty直到为0
当Redistributor完成它的内部状态更新时会报告Dirty位。这包括从vCPU接口中取出旧的vPE的任何pending虚拟中断。一个新的vPE直到该位为0时才能够被调度。Arm建议虚拟软件直到Dirty为0时才通过ICH寄存器进行上下文切换。
在进程中更新GICR_VPENDBASER,并设置Valid=1
将Valid位设置为1将通过Redistributor新的vPEID有效,这个vPE的虚拟中断可以发出。GICR_VPENDBASER也包含虚拟Distributor Group使能,这能够控制哪个虚拟中断组可以被发往CPU interface。
可选择的:轮询GICR_VPENDBASER.Dirty直到为0
当写1到Valid后,GIC将中断发往新被调度的vPE。Dirty位一直为1直到或GIC发现一个它可以传递的中断,或它完成walk pending表并没有发现pending中断。
在ITS中,vPE被映射到一个特定的Redistributor。这个映射后面可以修改,但在任何时间点上对于vPE仅有单个目标Redistributor。但是一个vPE可以被调度到任一Redistributor,假若有一组CommonLPIAff组作为目标Redistributor。在Redistributor上进行调度作为不同组可以造成GIC misbehave?
在单chip设计中可能所有的Redistributor可以为相同CommonLPIAff group。在这种情况下可以将vPE调度到任何Redistributor上。
Hypervisor通常将vPE分成三类:
Running
vPE当前由hypervisor进行调度到物理PE上。对于GIC,这意味着vPE可以接受直接注入虚拟中断。
Runnable or to-be-scheduled
vPE没有被调度到任一物理PE上。hypervisor知道vPE仍有工作需要做,因此会在将来某个时候调度它。虚拟中断当前不能由GIC传递给本vPE。
Idle
vPE当前不能被调度到任一物理PE上。hypervisor相信vPE没有工作需要做,因此不会在将来进行调度。
当vPE变得有效时一个vPE从idle变成Runnable。可以发生的一种情况是中断到达目标vPE。但这要求hypervisor意识到中断已经到达。完成这个的机制称为doorbell中断。
当一个虚拟中断到达时,如果目标vPE被调度,中断可以直接发往CPU interface:
当这个vPE没有被调度时,可选择性的产生一个doorbell中断:
doorbell为物理中断,通常被带到EL2并由Hypervisor处理。它发信号给hypervisor,对于非调度的vPE存在一个pending中断,这意味着在将来调度中non-scheduled vPE被移到Runnable队列中。
GICv4.0支持两种类型的doorbell:
每个vPE可以被分配一个default doorbell。当任意中断发往的vPE变成pending或没有被调度时,一个default doorbell产生。
对于default doorbell,架构做出几个保证:
(1)在residencies之间一个vPE的default doorbell被设置为pending不超过一次
一旦vPE从idle变成Runnable,对这个vPE软件不再需要更多doorbell。这个vPE通常将被调度。
(2)当vPE变成non-scheduled时如果要求,将产生一个default doorbell
如果vPE正在变成non-scheduled,hypervisor准备将vPE变成runnable,它就不再需要doorbell。如果再接受将变成低效。
(1)如果pending中断使能仅产生一个default中断
软件仅想知道将发往vPE的pending中断。如果中断disable,我们也不再需要将vPE变成runnable。
(2)通过将vPE变成被调度可以清除pending default doorbell
如果一个vPE被调度,但仍有未处理default doorbell,中断将被清除。因为hypervisor不再需要知道仍需要工作由vPE做。
Default doorbell的产生由GICR_VPENDBASER中两个位控制:
GICR_VPENDBASER.PendingLast
当vPE变成non-scheduled时,该位报告对于该vPE是否存在未完成pending中断。
GICR_VPENDBASER.Doorbell
软件设置该位表明它是否想产生doorbell。
当PendingLast报告vPE存在pending中断时,Doorbell被当作为0(no doorbell)。否则它表明必须立即产生一个doorbell。
vPE的default doorbell的INTID通过ITS命令进行设置,后面我们会再讲叙。
Default doorbell为物理LPI,这意味着它们的配置保存在内存中。更明确的,在物理LPI配置表中。如果软件想修改物理LPI的配置,它需要写该表并通过INV命令无效化旧的配置中的任何缓存。
虽然default doorbell为物理LPI,但是以缓存来看它们与其他LPI也不一样。软件必须发INVDB命令来将其用于default doorbell。
一个individual doorbell可以选择性的对每个虚拟中断作设置,而不是对每个vPE。这意味着Hypervisor可以依赖中断而执行不同行为。比如,大多数中断可以使用default doorbell并将vPE标记为runnable。一个高优先级的中断可以被赋予给一个individual doorbell并会造成立即调度。
Individual doorbell没有default doorbell那样的相同的保证。特别是:
Individual doorbell的支持是可选的,由GITS_TYPER.nID报告。
ITS负责转换将到来的MSI并以虚拟中断将其发送。在之前文档中,我们覆盖基本事务机制。
对于虚拟中断,ITS记录了中断目标为哪个vPE以及虚拟INTID。ITS也需要记录一个vPE被映射到哪组Redistributor。ITS有两种方法来完成,GITS_TYPER.SVEPT表明支持哪种模式:
SVEPT=0
ITS使用私有表来记录vPE映射。软件必须为该表分配内存,并将GITS_BASER2指向分配的内存。
SVEPT=1
ITS重用Redistributor的vPE配置表。软件必须将GITS_BASER2指向Redistributor分配的vPE配置表。
使用VMAPP命令创建vPE
VMAPP
其中:
在vPE配置表被分配和建立目标Redistributor之前软件不能使用VMAP创建vPE。
EventID/DeviceID被映射到vINTID和vPE。命令VMAPI用于当EventID=vINTID时:
VMAPI
当EventID与vINTID不相同时,使用VMAPTI命令:
VMAPTI
在这些命令中:
在使用VMAPP命令创建vPE之前软件不能映射中断到一个vPE。
一个外设的DeviceID为5。它产生两个EventID, 0和1。两个EventID都可以被映射到vINTID,vINTID属于vPEID为6的vPE。
vPE 6被映射到Redistributor 7并使用8192作为default doorbell。
命令如下:
VWAPP 6, 7, 14,
VMAPTI 5, 0, 8725, 1023, 6
VMAPTI 5, 1, 9000M 1023, 6
VSYNC 6
NOTE: 该例子假定GITS_TYPER.PTA=0时,之前发送MAPD命令映射ITT。
如果hypervisor将迁移vPE到Redistributor(CommonLPIAff组的一部分),ITS映射必须被更新因此虚拟中断能够被发送到正确的位置。ITS映射使用VMOVP命令进行更新,同时跟随VSYNC来同步上下文。
系统可以包含多个ITS。当超过一个ITS对某个vPE存在映射时,任何修改将适用到包含原始映射的所有ITS。GICv4支持两种模式,GITS_TYPER.VMOVP表明使用哪种模式:
GITS_TYPER.VMOVP=1
VMOVP命令必须仅被一个ITS发送,无论有多少个ITS对该vPE有映射。要求硬件传递这个修改并修改同步。这意味着不需要ITS list和SequenceNumber域。
VMOVP
Arm期望它可以由GIC实现的模式。
GITS_TYPER.VMOVP=0
VMOVP命令必须由所有对vPE有映射的ITS发送。
VMOVP
在这个命令中:
VMOVI命令将EventID和DeviceID重新映射到不同的vINTID和vPE。
VMOVI
在这个命令中:
映射命令VMAPP和VMAPTI存在v域。当v=1时,它们被当作映射命令。当v=0时,它们被当作取消映射命令。
当对vPE移除映射,软件必须首先移除vPE的所有中断映射。
与物理LPI类似,允许Redistributor缓存vLPI的配置。如果一个vLPI的配置被修改,缓存必须被无效化。有两个ITS命令实现此功能。
当修改单个或几个vLPI的配置时,通常需要使用INV命令。对每个被修改的vLPI,要求单独INV命令。
VINVALL命令无效化某个vPE的所有vLPI的配置。当修改很多vLPI时,通常使用该命令。
在GICv4.0中,对每个Redistributor,可以使用GICR_INVLPIR寄存器进行无效化。Arm建议软件使用命令或寄存器来实现,但不建议使用两者的混合。
GICv4.1引入新的特性,vSGI的直接注入。该特性通过减少进入hypervisor的次数减少overhead。
软件通过写GITS_SGIR寄存器产生vSGI。软件写目标vPE的vPEID和要发送SGI的INTID。ITS查找特定vPE的映射并将vSGI发送给目标Redistributor。
下图显示了发送vSGI的流程:
如果vPE没有被调度,中断被记录为pending。可能会产生default doorbell。
这个过程仍需要一定程度的hypervisor交互来将写入到ICC_SGIR寄存器的虚拟亲和性值转化为vPEID。在这之后,GIC的直接注入机制可以处理剩下过程。
为了支持vSGI直接注入,Redistributor需要知道SGI的配置(使能,优先级,group)。这个信息被记录在虚拟LPI pending表中。GICv4.1使用小的空间来存储vSGI配置。
不像vLPI,软件不能直接写表来更新vSGI的配置。通过新的ITS命令设置配置:
VSGI
这里:
当软件产生一个vSGI时,被写的寄存器指定发送的Group:
接受者也为每个SGI INTID指定一个group。对于物理中断,通过GICR_IGROUPR0和GICR_IGRPMODR0寄存器指定。对于虚拟vSGI,通过VSGI命令设置Group。
对于物理SGI,GIC根据接受者配置的Group检查发送的Group。仅当它们匹配时发送的中断才被设置为pending。
对于虚拟SGI,GITS_SGIR中没有Group域。GIC将使用接受者配置的Group。因此,它依赖于软件,通常为hypervisor,来根据接受者配置的Group来检查发送的Group。
在大多数情况下虚拟和物理SGI的行为都是相同。这里仅有一个地址它们不同:vSGI使用LPI相同的状态机,如下图所示:
这可以减少GIC硬件的复杂性。在大多情况下这个不同点对软件不可见。为也看到该不同点,VM中的软件必须使用EOImode=1。这种情况下使用两个分开的寄存器写来完成priority drop和deactivation。对于pSGI,相同的INTID直接deactivation后才会被看到。对于vSGI,相同的INTID在priority drop后被看到,在deactivation之前。
有时hypervisor需要检查vSGI是否处于Pending。比如,VM为处理GICR_ISPENDR0寄存器的读取。
为了使能它,Redistributor新增两个寄存器来查询vPE的vSGI当前pending状态。软件需要做:
Redistributor中没有寄存器来模拟GICR_ISPENDR0和GICR_ICPENDR0寄存器的写。可以通过ITS模拟:
下一篇:【JavaSE】继承那些事儿