提示
该博客主要为个人学习,通过阅读官网手册整理而来(个人觉得阅读官网的英文文档非常有助于理解各个IP特性)。若有不对之处请参考参考文档,以官网参考文档为准。
Arm Generic Interrupt Controller v3 and v4学习一共分为三章,这是第二章
- 第一章:讲解GIC,主要为基础知识,SPI、PPI与SGI配置
- 第二章:讲解LPI(Locality-Specific Peripheral Interrupts)
- 第三章:讲解Virtualization
1. Introduction
🔔背景:Interrupt Translation Service (ITS) 是一种硬件和软件结合的机制,用于处理中断信号在虚拟化环境中的传递和转换。ITS旨在解决虚拟化环境中的中断共享和管理问题。
中断是发送给处理器的一个信号,表明已经发生了需要处理的事件。中断通常是由外围设备产生的。LPIs通常用于产生消息信号中断(Message-Signaled Interrupts, MSIs)的外设。
LPI的配置和管理与其他中断类型不同,因为它们的状态保存在内存中,而不是保存在寄存器中。LPI是具有消息信号的中断(MSI),并由中断转换服务(Interrupt Translation Service ,ITS)提供转换。
2. LPIs
LPI的配置与其他中断类型非常不同,涉及以下内容:
- Redistributors
- ITSs (Interrupt Translation Service)
LPI始终是基于消息的中断,它们可以由ITS提供支持。ITS负责接收来自外设的中断,并将其作为LPI转发给适当的Redistributor。一个系统可能包括多个ITS,在这种情况下,每个ITS都必须单独配置。
对LPI的支持是可选的,并由GICD_TYPER.LPIS控制。如果至少存在一个ITS,外设也可以绕过ITS,直接发送LPI到Redistributor。
3. Redistributors
Redistributors使用内存中保存的表来获取LPI配置信息和每个物理LPI的状态。LPI的配置信息存储在LPI配置表中,由GICR_PROPBASER指向。LPI配置是全局的,也就是说,所有的Redistributors必须看到相同的配置。通常,系统有一个由所有Redistributors共享的LPI配置表。
LPI的状态信息也存储在内存表中。这些是由GICR_PENDBASER指向的LPI Pending表。每个Redistributor都有自己的LPI Pending表,并且这些表不会在Redistributors之间共享。
下图显示了三个Redistributors和相关的LPI表:
3.1 Initial configuration of a Redistributor
初始化系统中的Redistributors的步骤如下:
- 为LPI配置表(Configuration table)分配内存,并使用每个LPI的适当配置初始化该表。
- 在每个Redistributor中设置GICR_PROPBASER,以指向LPI配置表。
- 为每个Redistributor的LPI Pending表分配内存,并初始化每个表的内容。在系统启动时,这通常意味着将内存归零,这意味着所有的LPI接口都处于非活动状态(inactive state)。
- 在每个Redistributor设置GICR_PENDBASER,以指向与该Redistributor关联的LPI Pending表。
- 使能每个Redistributor中的GICR_CTLR.EnableLPIs,以启用LPIs。
3.1.1 LPI Configuration table
LPI配置表为每个LPI INTID设置了一个字节。下图显示了每个字节的格式:
尽管SPI、PPI和SGI的优先级可以设置8位,但表中只有6位用于记录LPI的优先级。LPI优先级的较低两位总是被视为0b00
这里没有用于记录Group配置的字段。LPI总是被视为Non-secure Group 1中断。
LPI配置表的大小取决于LPI的数量。GIC支持的INTIDs(SPIs、PPIs、SGIs和LPI)的最大数量由GICD_TYPER.IDbits表示。LPI配置表只处理LPI,其中使用的属性大于8191。因此,为了支持所有可能的LPI,LPI配置表的大小计算如下:
🔔Size in bytes = 2GICD_TYPER.IDbits+1 – 8192
然而,可以支持更小范围的INTIDs。GICR_PROPBASER还包括一个IDbits字段,它表示LPI配置表所支持的INTIDs的数量。这个数字必须等于或小于GICD_TYPER.IDbits中的值。软件必须为这个数量的入口分配足够的内存。在这种情况下,LPI配置表的要求大小变为:
🔔Size in bytes = 2GICR_PROPBASER.IDbits+1 – 8192
中断控制器必须能够读取为LPI配置表分配的内存。然而,它从不写入这个内存。
3.1.2 LPI Pending tables
LPIs的状态信息存储在内存中。LPIs有两种状态:非活动状态(inactive)或挂起状态(pending)。
在它们被响应时,中断从pending切换到inactive。
因为只有两个状态,所以在LPI Pending表中,每个LPI只有1位。因此,为了在实现中支持所有可能的INTID,LPI Pending表的大小必须为:
🔔Size in bytes = (2GICD_TYPER.IDbits+1) / 8
中断控制器必须能够从为LPI Pending表分配的内存中读取和写入。通常,当缓存中有太多的Pending中断或进入低功耗状态时,Redistributor在内部缓存最高优先级的Pending中断,并将状态信息写入LPI Pending表。
虽然在拥有的Redistributor中使能了LPIs,但软件永远不能直接访问LPI Pending表。
3.2 Reconfiguring LPIs
LPI配置信息存储在内存中的表中,而不是存储在寄存器中。出于性能原因,Redistributor缓存了LPI配置信息。这意味着要重新配置LPI,软件必须:
- 更新LPI配置表入口
- 确保更新的全局可见性
- 无效Redistributor中配置的任何缓存
通过发出ITS INV或INVALL命令,使Redistributor中的缓存失效。INV命令会使特定中断入口无效,因此,在重新配置少量LPIs时,通常会使用此命令。INVALL命令会使指定集合中的所有中断入口无效。
有关ITS命令的更多信息,请参见 Adding a new command to the command queue。
如果没有实现ITS,软件必须写入GICR_INVLPIR或GICR_INVALLR寄存器,以使Redistributor重新加载中断配置。
4. ITS
外设通过在ITS中向GITS_TRANSLATER写入消息来生成LPI。这为ITS提供了以下信息:
- EventID
EventID写入到GITS_TRANSLATER中。EventID标识外围设备正在发送的哪个中断。EventID可能与INTID相同,也可能被ITS转换为INTID。 - DeviceID
设备ID标识外围设。生成DeviceID的机制是由定义实现(IMPLEMENTATION DEFINED)。例如:AXI User signals可能会被用到
ITS handles将消息转换为可以传递到已连接的core的INTID。
物理LPI在集合中。一个集合中的所有LPI都被路由到同一Redistributor。软件将LPI分配给集合,允许它有效地将中断从一个处理元素(PE)移动到另一个处理元素(PE)。
ITS使用三种类型的表来处理LPIs的转换和路由。它们包括:
- Device table
每个ITS都有一个设备表(Device table)。设备表将device id映射到中断转换表(Interrupt Translation Tables)。 - Interrupt Translation Tables
每个DeviceID或外设都有一个中断转换表(ITT)。ITT包含EventID和INTID之间的外设特定的映射。它们还包含了INTID是其成员的集合。 - Collection table
每个ITS有一个集合表。收集表将集合映射到重新分配器。
当一个外设向GITS_TRANSLATER写入消息时,ITS会执行以下操作:
- 使用DeviceID从Device table中选则合适的入口。此入口标识要使用的ITT
- 使用EventID从ITT中选则合适的入口,该入口提供了INTID和集合ID(Collection ID)
- 使用集合ID在集合表(Collection Table)中选择所需的入口,然后返回路由信息
- 将中断转发给目标Redistributor。(该步骤省略了distributor,也就是说路由的动作是由distributor来做的)
一个ITS可以选择支持许多硬件集合(hardware collections)。硬件集合是ITS在内部保存配置的地方,而不是将其存储在内存中。GITS_TYPER.HCC将报告支持的硬件集合的数量。您仍然可以认为这是集合表的一部分(Collection Table),它只是不存储在内存中。
4.1 The command queue
ITS将使用内存中的命令队列进行控制。命令队列是一个循环缓冲区,它由三个寄存器定义。
- GITS_CBASER
此寄存器指定消息队列的基地址和大小。命令队列必须是64KB对齐的,并且大小必须是4KB的倍数。消息队列中的每个入口都是32bytes。GITS_CBASER还指定了ITS在访问消息队列时所使用的可缓存性和可共享性设置。 - GITS_CREADR
此寄存器指向ITS将处理的下一个命令。 - GITS_CWRITER
此寄存器指向队列中下一个应该写入新命令的入口
GIC3.0和4.0提供了ITS支持的所有命令以及如何编码这些命令的详细信息。
4.2 Initial configuration of an ITS
要在系统启动时配置ITS,软件必须:
- 为设备表和集合表分配内存
GITS_BASER[0…7]寄存器指定ITS设备和集合表的基地址和大小。软件使用这些寄存器来发现ITS所支持的表的数量和类型。然后,软件必须分配所需的内存,并将GITS_BASERn寄存器设置为指向这个已分配的内存。 - 为命令队列分配内存
软件必须为命令队列分配内存,并将GITS_CBASER和GITS_CWRITER设置为指向此所分配内存的起点。 - 使能ITS
在分配了表和命令队列后,就可以启用ITS。这是通过设置GITS_CTLR.Enable = 1。
一旦GITS_CTLR.Enable 被设置,GITS_BASERn和GITS_CBASER寄存器变为只读。
4.3 The sizes and layout of Collection and Device tables
集合表和设备表的位置和大小将使用GITS_BASERn寄存器进行配置。软件在启用ITS之前,必须为这些表分配内存,并配置GITS_BASERn寄存器。
软件可以分配一个单级表或二级表。这是由GITS_BASERn.Indirect指定的。
对两级表的支持是可选的。如果ITS只支持单级表,则支持GITS_BASERn.Indirect的是RAZ/WI。
- 单级表(Flat/Single level tables)
对于单级表,将单个连续的内存块分配给ITS来记录映射。在启用ITS之前,软件需要用0填充内存。然后,在处理命令队列中的命令时,由ITS填充该表。
下图显示了一个单级表。该表是一个连续的内存块,一个GITS_BASERn寄存器指向表的基地址:
表的大小可根据DeviceID或CollectionID的宽度进行调整。所需大小可计算如下:
📌Size in bytes = 2ID_width * entry_size
其中entry_size是每个表入口的字节数,由GITS_BASERn.Entry_Size报告。
在配置GITS_BASERn寄存器时,表的大小被指定为页。页的大小由GITS_BASERn.Page_Size控制。可以是4KB、16KB或64KB。因此,上面给出的公式的结果必须四舍五入到下一个整页的大小。
例如,如果系统实现了8bit的DeviceID,每个表的字节为8bytes,一个4K大小的页将被使用:
📌28 * 8 = 2048 bytes = 4K (rounded up to the next full page)
- 二级表(Two-level tables)
对于二级表,软件分别分配一个第一级表和几个二级表,如下图所示:
第一级表由软件填充,每个入口要么指向第二级表,要么被标记为无效。第二级表必须用0来填充,然后再将它们分配给ITS,并在处理从命令队列中获得的命令时由ITS填充。
当使能ITS时(GITS_CTLR.Enabled== 1)软件可能会分配额外的二级表,并更新相应的第一级表条目指向这些附加表。在使能ITS时,软件不能删除分配,或更改现有的分配。
每个二级表的大小是一页。与平面表(flat tables)一样,页面大小是由GITS_BASERn.Page_Size配置的。因此,它包含(page_size / entry_size)入口。
每第一级表入口有(page_size/entry_size)个id,可以指向二级表,也可以标记为无效。任何使用与无效条目对应的ID的ITS命令都将被丢弃。
一级表的所需大小可以通过计算:
📌Size in bytes = (2ID_width / (page_size / entry_size)) * 8
与单级表一样,第一级表的大小被指定为页数。因此,该公式的结果必须四舍五入到下一个整页的大小。
4.4 Adding a new command to the command queue
若要向命令队列中添加新命令,软件必须:
- 将新命令写入队列
GITS_CWRITER指向的下一个入口,该入口在命令队列中不包含有效命令。软件必须将该命令写入此入口,并且必须确保全局可见性。 - 更新GITS_CWRITER
软件必须将GITS_CWRITER更新到下一个不包含新命令的入口。更新GITS_CWRITER通知ITS已经添加了一个新的命令。
软件可以同时向队列中添加多个命令,前提是命令队列中有足够的空间,并且GITS_CWRITER会相应地更新。 - 等待ITS系统读取该命令
软件可以通过轮询GITS_CREADR来检查ITS是否已经读取了该命令。当GITS_CWRITER.Offset == GITS_CREADR.Offset表示所有命令都已被ITS读取。
或者,可以添加一个INT命令来产生一个中断,以表明ITS已经读取了一组命令。
ITS会按顺序从命令队列中读取这些命令。但是,这些命令对Redistributors的影响可能是无序可见的。SYNC命令可以确保以前发布的命令的效果可见。
当GITS_CWRITER指向GITS_CREADR之前的位置时,命令队列已满。在尝试添加新命令之前,软件必须检查队列中是否有足够的空间。
4.5 Mapping an interrupt to a Redistributor
每个可以向ITS发送中断的外设都有自己的DeviceID。每个DeviceID都需要它自己的中断转换表(ITT)来保存其EventID到INTID的映射。软件必须为ITT分配内存,然后使用MAPD命令将DeviceID映射到ITT,如下命令所示:
📌MAPD <DeviceID>, <ITT_Address>, <Size>
当外设的DeviceID已映射到ITT时,必须将它可以发送的不同EventID映射到INTID和集合(collections)。每个集合都被映射到一个Redistributor。
使用MAPTI和MAPI命令,可以将这些命令映射到集合。
- 当EventID和INTID相同时,使用MAPI命令,如下所示:
📌MAPI <DeviceID>, <EventID>, <Collection ID>
- 当EventID和INTID不同时,使用MAPTI命令,如下所示:
📌MAPTI <DeviceID>, <EventID>, <INTID>, <Collection ID>
目标Redistributor的识别取决于GITS_TYPER.PTA:
- GITS_TYPER.PTA == 0
Redistributor由其ID指定,可以从GICR_TYPER.Processor_Number中读取。 - GITS_TYPER.PTA == 1
Redistributor由其物理地址指定。
例如:
一个计时器有DeviceID 5,并使用一个2位的EventID。我们希望将“EventID 0”映射到INTID 8725。为计时器分配的ITT在地址0x84500000处。
我们决定使用collection number 3,并将中断发送给ID 7的Redistributor。
其命令顺序如下:
MAPD 5, 0x84500000, 2 // Map DeviceID 5 to an ITT
MAPTI 5, 0, 8725, 3 // Map EventID 0 to INTID 8725 and collection 3
MAPC 3, 7 // Map collection 3 to Redistributor 7
SYNC 7该示例假设之前没有设置任何映射,并且是GITS_TYPER.PTA==0。
4.6 Migrating(迁移) interrupts between Redistributors
我们可以使用几种不同的技术将中断从一个Redistributor移动到另一个Redistributor:
- Remap a collection
软件可以通过重新映射整个集合,将所有中断从一个Redistributor移动到另一个Redistributor。这通常在连接到Redistributor的PE断电时完成,并且中断必须移动到另一个Redistributor。可以使用以下命令序列完成:
MAPC <Collection ID>, <RDADDR2> // Remap collection to new Redistributor
SYNC <RDADDR2> // Ensure visibility of the mapping
MOVALL <RDADDR1>, <RDADDR2> // Move pending state to new Redistributor
SYNC <RDADDR1> // Ensure visibility of move
在这个命令序列中,RDADDR1是以前的目标Redistributor,而RDADDR2是新的目标Redistributor。
如果有多个针对RDADDR1的集合,那么我们将需要多个MAPC命令,每个集合对应一个。此序列假设所有集合都被重新映射到相同的新目标Redistributor。
- 将一个中断映射到另一个集合。单个中断可以重新映射到不同的集合。可以使用以下命令序列完成
MOVI <DeviceID>, <EventID>, <ID of new Collection>
SYNC <RDADDR1>
在这个命令序列中,RDADDR1是在重新映射中断之前,中断最初分配到的集合的目标Redistributor。
4.7 Removing interrupt mappings
要重新映射或删除中断的映射,软件必须执行以下操作:
- 失能中断当前映射到的物理INTID。这将在LPI配置表中完成。有关详细信息,请参见3.2节 Reconfiguring LPIs。
- 发出DISCARD 命令。这将删除中断的映射,并清除已映射的INTID的挂起状态。
- 发出一个SYNC命令,并等待到该命令完成为止。
命令完成后,不再将中断发送到之前映射到的再分配器。
4.8 Remapping or removing the mapping of devices
要更改或删除设备的映射,软件必须执行以下操作:
- 当前映射外设的每个EventID按照4.7节Removing interrupt mappings的步骤。
- 发出MAPD命令以重新映射该设备。或者,将有效位清除为0的MAPD命令将删除该映射
- 发出一个SYNC命令,并等待到该命令完成为止。
5. Example
请点这里AArch64_GIC_v3_v4_example下载示例。该示例演示了初始化GIC、使用ITS配置LPI以及处理生成的LPI中断。
该示例包含以下文件:
- gicv3_basic.c:包含与GIC交互的函数。
- gicv3_lpis.c:包含与LPIs特别相关的函数。
- main_lpi.c:一个使用LPIs的简短测试程序。
在本例中,我们查看文件main_lpi.c如下:
int main(void)
{
uint32_t type, entry_size;
uint32_t rd, target_rd;
//
// Configure the interrupt controller
//
rd = initGIC();
函数initGIC()执行GIC的基本初始化。有关更多信息,请参见:Arm Generic Interrupt Controller v3 and v4(GICv3v4)学习(一)。
以下代码为LPI配置和挂起表分配内存:
//
// Set up Redistributor structures used for LPIs
//
setLPIConfigTableAddr(rd, CONFIG_TABLE, GICV3_LPI_DEVICE_nGnRnE /*Attributes*/, 15 /* Number of ID bits */);
setLPIPendingTableAddr(rd, PENDING_TABLE, GICV3_LPI_DEVICE_nGnRnE /*Attributes*/, 15 /* Number of ID bits */);
enableLPIs(rd);
配置表由多个Redistributors共享,但每个Redistributors都有一个挂起表(Pending Table)。本示例使用单个核心,因此它只分配单个LPI挂起表。
接下来,该代码将配置ITS:
// Allocate memory for the ITS command queue
initITSCommandQueue(CMD_QUEUE, GICV3_ITS_CQUEUE_VALID /*Attributes*/,
1 /*num_pages*/);
// Allocate Device table
setITSTableAddr(0 /*index*/,
DEVICE_TABLE /* addr */,
(GICV3_ITS_TABLE_PAGE_VALID | GICV3_ITS_TABLE_PAGE_DIRECT |
GICV3_ITS_TABLE_PAGE_DEVICE_nGnRnE),
GICV3_ITS_TABLE_PAGE_SIZE_4K,
16 /*num_pages*/);
//Allocate Collection table
setITSTableAddr(1 /*index*/,
COLLECTION_TABLE /* addr */,
(GICV3_ITS_TABLE_PAGE_VALID | GICV3_ITS_TABLE_PAGE_DIRECT |
GICV3_ITS_TABLE_PAGE_DEVICE_nGnRnE),
GICV3_ITS_TABLE_PAGE_SIZE_4K,
16 /*num_pages*/);
// Enable the ITS
enableITS();这段代码为ITS的Command Queue, Device table和 Collection table。当表被初始化后,ITS将被使能。
使能ITS后,可以通过向命令队列中添加命令来映射中断,如下代码所示:
// Set up a mapping
itsMAPD(0 /*DeviceID*/, ITT /*addr of ITT*/, 2 /*bit width of ID*/);
itsMAPTI(0 /*DeviceID*/, 0 /*EventID*/, 8193 /*intid*/, 0 /*collection*/);
itsMAPC(target_rd /* target Redistributor*/, 0 /*collection*/);
itsSYNC(target_rd /* target Redistributor*/);
此示例将执行以下操作:
- 将DeviceID 0映射到中断转换表(Interrupt Translation Table, ITT)
- 将EventID 0从DeviceID 0映射到INTID 8193,并将其分配到集合0(Collection 0)
- 将集合0映射到当前的Redistributor(0.0.0.0)
此时,我们已经为GIC的DeviceID/EventID启用了其映射。接下来,我们对INTID 8193的配置如下:
configureLPI(rd, 8193 /*INTID*/, GICV3_LPI_ENABLE, 0 /*Priority*/);
printf("main(): Sending LPI 8193\n");
itsINV(0 /*DeviceID*/, 0 /*EventID*/);
itsINT(0 /*DeviceID*/, 0 /*EventID*/);
此代码启用INTID 8193并设置其优先级。GIC可能已经缓存了旧的配置,因此需要使用INV命令来确保使用新的配置。
FVP基础平台模型不包括能够生成此MSI的外设,因此我们使用INT命令来生成它。
6. Check your konwledge
- LPI的状态机与其他中断类型有何不同?
💡其他中断类型有四种状态:Inactive, Pending, Active和Active and Pending。
LPIs只有两种状态:Inactive和Pending。 - LPI的配置和状态存储在哪里?
🔔在内存中。Redistributors共享一个存储该配置的公共LPI配置表。每个Redistributor有一个LPI挂起表(Pending table),它存储pending状态。 - MSI包含哪些允许ITS转换它信息?
📌一个DeviceID和一个EventID。DeviceID标识哪个外设发送中断。EventID标识外设正在发送的哪个中断。 - 软件如何在ITS中创建映射?
👉通过使用ITS命令队列发出命令。一个MAPD命令将为该设备创建一个映射。MAPI和MAPTI命令映射外设的EventID。 - 软件如何改变LPI的优先级?
👋该软件必须执行以下操作:- 更新LPI配置表(Configuration Table)中的相应入口
- 确保改变的全局可见性
- 通过使用ITS发出无效操作或写入其中一个无效寄存器,来使GIC cache中的任何配置失效
参考文档
Locality-Specific Peripheral Interrupts Arm Generic Interrupt Controller v3 and v4