【发布时间】:2020-01-31 06:24:26
【问题描述】:
我最近在 ARMv8A 内核的裸机软件环境中提出了局部特定中断 (LPI)。 LPI 特别需要执行一组相对较大的步骤,并且仅从规范中获得所有所需步骤的更高级别视图有点困难。在我搜索的范围内,我无法在任何地方在线获得按顺序提到的所有所需步骤的单点参考。因此,我提出这个问题并用我自己的答案来回答这个问题 - 通过我自己提出 LPI 的经验获得。
【问题讨论】:
我最近在 ARMv8A 内核的裸机软件环境中提出了局部特定中断 (LPI)。 LPI 特别需要执行一组相对较大的步骤,并且仅从规范中获得所有所需步骤的更高级别视图有点困难。在我搜索的范围内,我无法在任何地方在线获得按顺序提到的所有所需步骤的单点参考。因此,我提出这个问题并用我自己的答案来回答这个问题 - 通过我自己提出 LPI 的经验获得。
【问题讨论】:
在这篇博文中,我的目标是提供在裸机 ARM v8A 验证框架上触发 LPI(局部特定中断)所需的编程的简明高级一站式描述。
ARM 架构定义了一个称为通用中断控制器 (GIC) 的特殊组件,它负责管理架构中定义的所有中断。定义了 4 种中断 - SGI(软件生成中断)、PPI(私有外设中断)、SPI(共享外设中断)和 LPI(局部特定中断)。
从 ARM/GIC 规范中我们可以看出,在裸机框架上生成 LPI 所需的编程远远超过其他 3 种类型所需的编程。
这篇文章并非旨在作为触发 LPI 的独立指南。在通过 GIC 规范的 LPI 相关部分后,它可以理想地用于形成清晰的高级编程图。
Linux for ARM 的所有实现也支持 LPI。如果有可用的引导操作系统,则可以采用更高级别的 LPI 编程方法(使用 Linux 功能)并且任务变得更容易。但是,在大多数裸机框架上,我们需要自己执行所有必需的编程,这就是我希望下面的一组步骤将有助于实现更快启用 LPI 的目标。
注意 - GIC 组件(分发器、再分发器、GITS)是内存映射组件。下面提到的所有设置都将采用加载和存储的形式。 SoC 文档应该能够提供这些组件在内存映射中的位置信息。
我建议阅读 ARM 和 GIC 规范,以了解在以下步骤中访问的寄存器字段的所有更深层含义。
基础知识
LPI 通常由 SoC 中的一个设备触发,并以系统中的一个 CPU 为目标。 每个设备可能需要针对它可能需要的不同类型的服务触发不同类型的目标不同内核的中断。 因此,LPI 由元组 - (DeviceID, EventID) 标识。 架构中支持大量的 LPI(8192 到 2^16-1 或事件 2^24-1)。通常,大型 LPI 中断集可以从同一个 CPU 获得服务。 因此,引入了称为中断收集 ID (ICID) 的概念。这指的是要发送到同一个 CPU 的一组 LPI。 数据结构
在开始编程之前,让我们看一下设置 LPI 所需的所有数据结构以及它们之间的交互。
设置 LPI 需要六 (6) 个数据结构 -
一个。 LPI 属性表
包含有关每个 LPI 的中断优先级和启用位的信息 - 每个 LPI 一个字节。 此表中不存在前 8192 个字节 由 GICR_PROPBASER 指向 请阅读 GIC 规范以了解此寄存器中各字段的含义 湾。 LPI 待处理表
包含有关每个 LPI 的未决状态的信息 - 每个 LPI 一个字节 前 8192 个字节在此表中实现定义,LPI 未决位状态放置在此内存块之后 由 GICR_PENDBASER 指向 请阅读 GIC 规范以了解此寄存器中各字段的含义 C。 ITS 命令队列
包含要由 ITS(中断翻译服务)执行的命令序列 GIC 实现是否允许在不使用 ITS 的情况下触发 LPI 由实现定义 在这种情况下实现寄存器 GICR_SETLPIR 和 GICR_CLRLPIR 请阅读 GIC 规范以了解更多详细信息 如果要使用 ITS 来触发 LPI,我们需要创建 ITS 专门理解的命令序列。 GITS_CBASER 指向内存中命令队列的基数 ITS 正在读取执行的当前命令由 GITS_CREADR 指向 队列中的最后一条命令由 GITS_CWRITER 指向。 d。设备表
此表存储从给定 DeviceID 到该设备的中断转换表的映射。 e.中断翻译表
中断转换表存储从给定 EventID 到相应物理中断 ID(向内核呈现哪个中断号)和 ICID(进一步查找以获取目标 CPU)的映射
f。采集表
此表存储从给定 ICID 到物理中断 ID 发送到的目标再分配器的映射。 查找流程
LPI 的查找流程(4 个查找和 1 个表集)简述如下 -
一个。 LPI 由(设备 ID、事件 ID)的组合触发
b.在Devices表中查找Device ID得到对应的Interrupt Translation Table (ITT)
c。在ITT中查找Event ID,得到要触发的物理中断ID和中断收集ID(ICID)
d。进一步在collection table中查找ICID得到目标redistributor
e。查找 LPI 属性表以检查 LPI 是否启用
如果启用了 LPI,则更新 LPI 挂起表,以便将物理中断置于挂起状态。
当核心进入适当的状态(异常级别、优先级等)时,待处理的 LPI 被核心“采用”。
编程步骤
现在进入实际的编程步骤(7 个步骤)-
一个。在分发器中启用非安全组 1 中断。
GICD_CTLR.EnableGrp1NS = 1 LPI 中断始终是非安全组 1 中断
b.让再分配器脱离睡眠状态。
这有一个架构和一个实现定义的部分。 架构部分 - 将 GICR_WAKER.ProcessorSleep 设置为 0 并等到 GICR_WAKER.ChildrenAsleep = 0。 IMP DEF 部分 - 这对于不同的 GIC 实现是不同的。例如 - 对于 GIC-600 实现,有一个名为 GICR_PWRR 的寄存器,在按照上述步骤写入 GICR_WAKER 之前,需要将 GICR_PWRR.RDPD 和 GICR_PWRR.RDAG 设置为 0。 SoC 文档应该能够在此处提供所需的步骤
对用于 LPI 的所有表使用的内存块进行零初始化
为 LPI 属性表、LPI 挂起表和 ITS 命令队列分配内存
在许多裸机框架中,“分配”可能只是离线决定使用哪些地址范围,并确保将上述表放置在内存中的不同区域中 设置 GICR_PROPBASER 指向一个合适的内存区域(64kB 对齐是首选) 将 GICR_PENDBASER 设置为指向合适的内存区域(64kB 对齐) 设置 GICR_CBASER 指向一个合适的内存区域(64kB 对齐)
这些表由 GITS_BASER 寄存器指向 需要先读取这些寄存器,以了解哪个寄存器指向哪种表(GITS_BASER.Type 字段)。这是由实现解决的。 将 GITS_BASER 的相应索引设置为指向一个空的 Device 表和 Collection 表
设置 GICR_CTLR.EnableLPIs = 1
下面提供了触发 LPI 所需的基本序列 - MAPD 创建设备表条目映射 DeviceID -> ITT 表 马普蒂 创建一个 ITT 表条目映射 EventID -> (physical LPI ID (pINTID), ICID (Interrupt Collections ID)) MAPC 创建一个集合表条目映射 ICID -> Redistributor 地址 再分配器地址有两种可能的格式 - 物理地址或处理器编号 GITS_TYPER.PTA 指示要使用哪种格式 GICR_TYPER.ProcessorNumber 表示分配给再分配器的处理器号 INT LPI 的“软件”触发 - 该指令采用 DeviceID 和 EventID,将它们通过 ITS 转换并请求目标再分配器设置适当的 LPI 挂起表位。
设置 GITS_TYPER.Enable = 1。 这将开始执行命令队列中的 ITS 命令,如果一切设置正确,应该会导致 LPI 在与目标内核关联的再分配器处更改为挂起状态。
感谢您阅读这篇文章,我真的希望它有助于更快地在裸机系统上触发 LPI。
如果您想添加更多信息或需要进行任何更正,请在 cmets 中告诉我。
谢谢!!!
【讨论】: