1. 简介


本文介绍ARM 的 Locality-specific Peripheral Interrupts ( LPIs ) 中断 ( 这是 GICv3/v4 中引入的一种中断类型) 与中断转换服务( Interrupt Translation Service, ITS )。


中断是向处理器发出的信号,表明需要处理的事件发生了。中断通常由外设生成。LPIs 通常用于产生Message-Signaled Interrupts (MSIs)中断的外设。由于 LPIs 的状态保存在内存中而不是寄存器中,因此 LPIs 的配置和管理与其他中断类型不同。SPI可以是MSI中断也可以不是,但LPI一定是MSI中断(注:MSI 和 LPI是两个不同概念),由中断转换服务(Interrupt Translation Service , ITS)提供转换。


2. LPIs


LPIs的配置与其他中断类型非常不同,涉及以下内容:

  • GICR
  • ITS


LPIs 总是基于MSI,并且由 ITS 支持。ITS负责接收来自外设的中断,并将它们作为 LPIs 转发到合适的GICR。系统可能包含多个 ITS,在这种情况下,每个 ITS必须单独配置。


注:对 LPIs 的支持是可选的,由GICD_TYPER.LPIS 指示。


3. Redistributors, GICR


GICR使用内存中的Table来存储 LPI 配置信息和每个物理 LPI 的状态。LPI 的配置信息存储在 LPI 配置表中, GICR_PROPBASER指向该表。LPI 配置是全局的,也就是说,所有的 GICR 必须看到相同的配置。通常,一个系统有一个单独的 LPI Configuration Table,由所有的 GICR 共享。LPI 的状态信息也存储在内存中的LPI Pending Table中,由GICR_PENDBASER 指向。每个 GICR都有自己的LPI Pending Table,这些Table不在 GICR 之间共享。


下图展示了三个 GICR 和相关的 LPI 表格:


Untitled


3.1 GICR的初始配置


初始化GICR的步骤如下:

  1. LPI Configuration Table分配内存,并初始化该表并配置每个LPI。
  2. 在每个GICR中设置GICR_PROPBASER,使其指向LPI Configuration Table
  3. 为每个GICRLPI Pending Table分配内存,并初始化每个表的内容。在系统启动时,这通需要将该部分内存清零,即所有LPI处于inactive状态。
  4. 在每个GICR中设置GICR_PENDBASER,使其指向与该GICR相关联的LPI Pending Table
  5. 在每个GICR中将GICR_CTLR.EnableLPIs设置为1,以启用LPI。


LPI Configuration Table


LPI Configuration Table为每个LPI INTID 分配一个字节。下图显示了该表中条目的格式:


Untitled


虽然SPI、PPI和SGI的优先级配置有8个比特位,但上述条目中只有6位用于记录LPI的优先级。LPI的优先级的低两位始终被视为0b00。没有字段用于记录中断分组配置。LPI始终被视为非安全Group 1中断


LPI Configuration Table的大小取决于LPI的数量。GIC支持的最大INTID数量(SPI、PPI、SGI和LPI)由GICD_TYPER.IDbits指示。LPI Configuration Table仅处理LPI,它们使用的INTID大于8191。因此,为了支持所有可能的LPI,LPI Configuration Table的大小计算如下:

LPI Configuration Table 大小(字节)= 2 ^ (GICD_TYPER.IDbits + 1) – 8192


但是,也可能只支持较小范围的INTID。GICR_PROPBASER还包括一个IDbits字段,指示LPI配置表支持的INTID数量。此数字必须等于或小于GICD_TYPER.IDbits中的值。软件必须为此数量的条目分配足够的内存。在这种情况下,LPI Configuration Table所需的大小如下:

LPI Configuration Table 大小(字节)= 2 ^ (GICR_PROPBASER.IDbits + 1) – 8192

中断控制器必须能够读取为LPI Configuration Table分配的内存,但不会写入此内存区域。


LPI Pending Table


LPI的状态信息存储在内存中。LPI只有两种状态:inactivepending


Untitled


当中断被PE确认时,中断从pending状态转变为inactive状态。

由于只有两种状态,因此在LPI Pending Table中每个 LPI 只有 1 位。因此,为了支持实现中所有可能的 INTIDLPI Pending Table的大小必须为:

LPI Configuration Table 大小 (字节) = (2 ^ (GICD_TYPER.IDbits + 1)) / 8


LPI Configuration Table不同,LPI Pending Table的大小没有考虑LPI INTID 是从8192开始。LPI Pending Table的前 1KB(对应于 INTID 0 到 8291 的条目)的内容取决于具体实现。


如同在LPI Configuration Table中描述的那样,可以使用比硬件支持的 INTID范围更小的范围。GICR_PROPBASER.IDBits 控制 INTID 范围的大小。因此,它影响 LPI Configuration TableLPI Pending Table的大小。在这种情况下,所需的LPI Pending Table大小如下:

LPI Pending Table 大小  (字节) = (2 ^ (GICR_PROPBASER.IDbits + 1)) / 8


中断控制器必须能够从分配给 LPI Pending Table的内存中读取和写入。通常,当有太多Pending中断需要缓存或进入低功耗状态时,GICR会在内部缓存最高优先级的Pending中断,并将状态信息写入 LPI Pending Table


在对应的 GICR中启用 LPI 后,软件将不会直接访问LPI Pending Table


3.2 重新配置LPIs


LPI 配置信息存储在内存中的表中,而不是寄存器中。为了性能原因,GICR会缓存 LPI 配置信息。这意味着当要重新配置一个 LPI时,软件必须:

  1. 更新 LPI Configuration Table中的条目。
  2. 确保更新的全局可见性。
  3. Invalidate GICR中的配置缓存。

使 GICR中缓存失效是通过发出ITS INVINVALL 命令来实现的。INV 命令使特定的中断条目失效,因此当重新配置少量 LPI 时通常使用此命令。INVALL 命令使指定集合中所有中断的条目失效。有关 ITS 命令的更多信息,请参见4.4小节。

如果ITS未实现上述功能,则软件必须通过写 GICR_INVLPIRGICR_INVALLR 寄存器,以使 GICR重新加载中断配置。


4. ITS


一个外设通过向 ITSGITS_TRANSLATER写入消息来生成 LPI,该消息为 ITS提供了以下信息:


Event ID (事件ID)

这是写入到 GITS_TRANSLATER的值。事件 ID 标识外设正在发送的中断。事件 ID 可能与 INTID 相同,也可能由 ITS 转换为 INTID


Device ID (设备 ID)

设备 ID 标识外设。生成设备 ID 的机制由具体实现决定。

ITS处理外设消息,将其转换为可传递到连接的CPU的 INTID

物理 LPI 被分组到集合中,某个集合中的所有 LPI 都路由到同一个 GICR。软件将 LPI 分配给不同集合,可以将中断从一个PE移动到另一个PE。

ITS使用三种类型的表来处理 LPI 的转换和路由:


Device Table (设备表, DT)

每个 ITS都有一个设备表。设备表将设备 ID 映射到中断转换表。


Interrupt Translation Tables (中断转换表, ITT)

每个设备 ID 或外设都有一个中断转换表(ITT)。ITT 包含特定于外设的事件 ID 和 INTID 之间的映射。它们还包含 INTID 所属的集合。


Collection Table (集合表, CT)

每个 ITS 都有一个集合表。集合表将集合映射到 GICRs

当外设向 GITS_TRANSLATER 写入消息时,ITS 执行以下操作:

  1. 使用Device ID 从设备表中选择适当的条目。此条目标识要使用的中断转换表。
  2. 使用Event ID 从所选的中断转换表中选择适当的条目。此条目提供 INTID 和 **Collection** ID
  3. 使用**Collection** ID 从集合表中选择所需的条目,返回路由信息。
  4. 将中断转发到目标 GICR


下图展示了此过程:


Untitled


注:ITS 可选支持多个硬件集合。硬件集合是 ITS 内部保存配置的地方,这样就无需将配置存储在内存中。GITS_TYPER.HCC 报告支持的硬件集合数量。你仍然可以将其视为 Collection Table的一部分,只是不存储在内存中。


4.1 The Command Queue (命令队列)


使用Command Queue来控制ITS。命令队列是一个循环的内存缓冲区,涉及以下三个寄存器:

  • GITS_CBASER:此寄存器指定命令队列的基地址和大小。命令队列必须是 64KB 对齐的,并且大小必须是 4KB 的倍数。命令队列中的每个条目为 32 字节。GITS_CBASER 还包含 ITS在访问命令队列时使用的缓存和共享性设置。
  • GITS_CREADR:此寄存器指向 ITS 将要处理的下一个命令。
  • GITS_CWRITER:此寄存器指向队列中下一个新命令应该被写入的位置。


下图显示了命令队列的简化表示:


Untitled


4.2 初始化配置一个ITS


要在系统启动时配置 ITS,软件需执行以下步骤:

  1. Device TablesCollection Tables分配内存 GITS_BASER[0..7] 寄存器指定了 ITS 设备表和集合表的基地址和大小。软件使用这些寄存器来发现 ITS 支持的表的数量和类型。然后,软件必须分配所需的内存,并将 GITS_BASERn 寄存器设置为指向这个分配的内存。
  2. Command Queue分配内存 软件必须为命令队列分配内存,并将 GITS_CBASERGITS_CWRITER 设置为指向此分配内存的起始地址。
  3. 启用 ITS 当表和命令队列被分配后,可以启用 ITS。通过将 GITS_CTLR.Enable 位设置为 1 来完成此操作。一旦设置了 GITS_CTLR.EnableGITS_BASERnGITS_CBASER 寄存器就变为只读状态。


4.3 设备表和集合表的大小与格式


设备表和集合表的位置和大小是使用GITS_BASERn寄存器进行配置的。在启用ITS之前,软件必须为这些表分配内存并配置GITS_BASERn寄存器。软件可以分配一个单级表或两级表。这由GITS_BASERn.Indirect指定。对两级表的支持是可选的, 如果ITS仅支持单级表,则GITS_BASERn.Indirect为 RAZ/WI。


单级表


对于单级表,分配给ITS的是一个连续的内存块,用于记录映射。在启用ITS之前,软件需要将该内存填充为零。之后,当ITS处理命令队列中的命令时,表会被ITS填充。


下面的图显示了一个单级表。该表是一个连续的内存块,其中GITS_BASERn寄存器指向表的基地址:


Untitled


表的大小随着 Device IDCollection ID 的个数而变化,应当根据如下方式计算所需大小:

Size in bytes = 2 ^ ID_width * entry_size 

其中,entry_size 是每个表条目的字节数,由 GITS_BASERn.Entry_Size 指定。


在配置 GITS_BASERn 寄存器时,表的大小被指定为页的整数倍。一个页的大小由 GITS_BASERn.Page_Size 控制,可以是 4KB、16KB 或 64KB。因此,上述给出的公式的结果必须向上舍入到下一个整数页面大小。


例如,如果一个系统实现了 8 位的 Device ID,表的每个条目占的字节数为 8,且使用了 4K 的页面大小, 则表大小为

2^8 * 8 = 256 * 8 = 2048 字节 --> 4K(向上舍入到下一个整数页面大小)


两级表


使用两级表,软件分配一个一级表和若干个二级表,如下图所示:


Untitled


第一级表由软件填充,每个条目要么指向一个二级表,要么标记为无效。在分配给 ITS之前,必须将二级表填充为零,并在 ITS处理命令队列时由其填充。


ITS启用的情况下(GITS_CTLR.Enabled == 1),软件可以分配额外的二级表,并更新相应的一级表条目以指向这些新分配的表。在 ITS 启用状态下,软件不得删除分配或更改现有分配。


每个二级表的大小为一个页面。与单级表一样,页面大小由 GITS_BASERn.Page_Size 配置。因此,它包含(page_size / entry_size)个条目。


每个一级表条目表示(page_size / entry_size)个 ID,并且可以指向一个二级表或标记为无效。任何使用与无效条目对应的 ID 的 ITS 命令都将被丢弃。


第一级表的所需大小可以通过以下公式计算:

Size in bytes = (2 ^ ID_width /(page_size / entry_size))* 8

与单级表一样,第一级表的大小指定为页面数。因此,公式的结果必须向上舍入到下一个整个页面大小。


4.4 添加新命令到Command Queue


要将新命令添加到命令队列,软件必须:

  1. 将新命令写入队列 GITS_CWRITER 指向命令队列中下一个不包含有效命令的条目。软件必须将命令写入此条目,并确保全局可见性。
  2. 更新 GITS_CWRITER 软件必须更新 GITS_CWRITER到下一个不包含新命令的条目。更新 GITS_CWRITER 通知 ITS已添加新命令。软件可以同时向队列添加多个命令,前提是命令队列中有足够的空间,并相应地更新了 GITS_CWRITER
  3. 等待 ITS 读取命令 软件可以通过轮询 GITS_CREADR 来检查 ITS 是否已读取命令。当 GITS_CWRITER.Offset 等于 GITS_CREADR.Offset 时,ITS 已读取所有命令。或者,可以添加 INT 命令以生成中断,当 ITS 读取完一组命令后发送中断。

ITS 按顺序从命令队列中读取命令。但是,这些命令对 GICR的可见性可能是乱序的。SYNC 命令可以确保先前发送的命令生效。

注:当 GITS_CWRITER 指向 GITS_CREADR 前面的位置时,命令队列已满。软件在尝试添加新命令之前必须检查队列中是否有足够的空间。


4.5 映射一个中断到GICR


每个能够向 ITS 发送中断的外设都有自己的 Device ID。每个 Device ID 都需要有自己的中断转换表(ITT)来保存其 Event IDINTID 的映射关系。软件必须为 ITT 分配内存,然后使用 MAPD 命令将 Device ID 映射到 ITT,如下所示:

MAPD <DeviceID>, <ITT_Address>, <Size>


当外设的Device ID 被映射到一个 ITT后,它可以发送的不同 Event ID 必须被映射到INTIDCollections。每个Collection都被映射到一个目标 GICR。可以使用 MAPTIMAPI 命令将 INTID 映射到Collection。当 Event IDINTID 相同时,使用 MAPI 命令,如下所示:

MAPI <DeviceID>, <EventID>, <Collection ID>


Event IDINTID不同时,使用MAPTI

MAPTI <DeviceID>, <EventID>, <INTID>, <Collection ID>


通过MAPC命令将Collections映射到GICR

MAPC <Collection ID>, <Target GICR>

目标 GICR的寻址方式取决于 GITS_TYPER.PTA

  • GITS_TYPER.PTA==0 时,通过 ID 指定 GICR,可从 GICR_TYPER.Processor_Number 中读取
  • GITS_TYPER.PTA==1 时,通过其物理地址指定 GICR


示例

一个定时器的 Device ID 为 5,并使用 2 位的 Event ID。我们希望将 Event ID 0 映射到 INTID 8725。为定时器分配的 ITT 的地址是 0x84500000。我们使用集合Collection 3,并将中断传递给 ID 为 7 的 GICR。

此操作的命令序列如下:

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 GICR 7
SYNC 7

注:这个例子假设之前没有配置过任何映射,并且 GITS_TYPER.PTA==0


4.6 中断在GICR之间迁移


可以使用几种不同的技术将中断从一个 GICR移动到另一个 GICR

  • 重新映射一个 Collection 软件可以通过重新映射整个 collection将所有中断从一个 GICR 移动到另一个 GICR。通常情况下,当连接到 GICR 的处理单元关闭电源时,必须将中断移动到另一个 GICR 。这可以通过以下命令序列完成:

    MAPC <Collection ID>, <RDADDR2>    // Remap collection to new GICR
    SYNC <RDADDR2>                     // Ensure visibility of the mapping
    MOVALL <RDADDR1>, <RDADDR2>        // Move pending state to new GICR
    SYNC <RDADDR1>                     // Ensure visibility of move
    

    在这个命令序列中,RDADDR1是之前的目标 GICRRDADDR2是新的目标 GICR

    如果有多个 collection 指向 RDADDR1,则我们需要多个 MAPC 命令,每 个 collection 一个。这个序列假设所有的 collections 都被重新映射到相同的新目标 GICR


  • 将一个中断映射到另一个Collection

    可以使用以下命令序列来将单个中断重新映射到不同的collection

    MOVI <DeviceID>, <EventID>, <ID of new Collection>
    SYNC <RDADDR1>
    

    在此命令序列中,RDADDR1是最初分配给中断的集合所针对的GICR


4.7 移除中断映射


要重新映射或删除中断的映射,软件必须执行以下操作:

  1. 禁用该映射的物理INTID的中断。这是在LPI Configuration Table中配置完成的。
  2. 发出DISCARD命令。这会删除中断的映射并清除映射INTID的Pending状态。
  3. 发出SYNC命令并等待命令完成。

命令完成后,不会再将更多中断传递到先前将中断映射到的GICR。


4.8 重映射与移除设备映射


要更改或删除设备的映射,软件必须执行以下操作:

  1. 针对当前映射的每个Event ID,按照4.7小节的步骤进行操作。
  2. 发出MAPD命令以重新映射设备。或者,使用将有效位清除为0的MAPD命令来删除映射。
  3. 发出SYNC命令并等待命令完成。