PCI 中断模型
PCI 总线可使用多种中断方式,即 INTx 中断、MSI 中断以及 MSI-X 中断。MSI 中断机制在 PCI 总线 V2.2 规范提出后,已成为 PCIe 总线的主流,但是 PCI 设备中并不常用。即便是支持 MSI 中断机制的 PCI 设备,在设备驱动中也很少使用这种机制。
INTx 中断
PCI 总线使用 INTA#、INTB#、INTC#和 INTD#信号向处理器发出中断请求。这些中断请求信号为低电平有效,并与处理器的中断控制器连接。在 PCI 体系结构中,这些中断信号属于边带信号,PCI 总线规范并没有明确规定在一个处理器系统中如何使用这些信号,因为这些信号对于 PCI 总线是可选信号。
不同处理器使用的中断控制器不同,如 x86 处理器使用 APIC 中断控制器,而 PowerPC 处理器使用 MPIC 中断控制器。这些中断控制器都提供了一些外部中断请求引脚 IRQ_PINx#。外部设备,包括 PCI 设备可以使用这些引脚向处理器提交中断请求。本文全部以 x86 处理器为例讲解 PCI 的相关内容,且只介绍使用方法,具体中断控制原理可自行查阅。
在 x86 处理器系统中,由 BIOS 或者 APCI 表记录 PCI 总线的 INTA~D#信号与中断控制器之间的映射关系,保存这个映射关系的数据结构也称为中断路由表。BIOS 初始化代码根据中断路由表中的信息,可以将 PCI 设备使用的中断向量写入到该 PCI 设备配置空间 Interrupt Line register 寄存器中,该寄存器在前文简要介绍过。
SylixOS 中通过一个中断表管理中断,其中每个条目描述一个 IO 中断或 Local 中断,由 X86_MP_INTERRUPT 结构体管理,该结构体的详细信息如下所示:
typedef struct {
UINT8 INT_ucEntryType;
UINT8 INT_ucInterruptType;
UINT16 INT_usInterruptFlags;
UINT8 INT_ucSourceBusId;
UINT8 INT_ucSourceBusIrq;
UINT8 INT_ucDestApicId;
UINT8 INT_ucDestApicIntIn;
} X86_MP_INTERRUPT;
- INT_ucEntryType:中断条目类型。
- INT_ucInterruptType:中断类型。
- INT_usInterruptFlags:中断标志。
- INT_ucSourceBusId:源总线 ID。
- INT_ucSourceBusIrq:源总线 IRQ 号。
- INT_ucDestApicId:目的 APIC ID。
- INT_ucDestApicIntIn: 目的 APIC IRQ 号。
中断条目中的所有信息在系统初始化时已经填充好,PCI 驱动程序只需根据自己所在的总线 ID,槽 ID 以及中断引脚便可确定对应的中断条目,获取真正的 IRQ 号,后续可使用该 IRQ 号进行中断绑定等操作。
SylixOS 在安装 PCI 主控制驱动时,会配置 PCI 总线上的所有设备,获取每个设备的 IRQ 资源信息以及其他资源信息,此时获取的 IRQ 资源默认便是 INTx。在获取资源时,将根据设备的总线 ID,槽 ID 以及中断引脚获取全局 IRQ 号。因此如果该 PCI 设备支持 INTx,驱动程序可直接通过 IRQ 资源获得中断向量号以及其他信息。关于 PCI 设备模型及 PCI 设备资源的相关内容,将在后续章节讲解。
MSI 中断
在 PCI 总线中,所有需要提交中断请求的设备,必须能够通过 INTx 引脚(这个引脚会连接到 8129 或者 I/O APIC 上)提交中断,而 MSI 机制是一种可选机制。而 PCIe 总线中,PCIe 设备必须支持 MSI 或者 MSI-X 中断请求机制,而可以不支持 INTx 中断。目前一般的 PCIe 设备都使用 MSI 机制来提交中断。
MSI 使用了 MSI Capability 结构体来实现中断请求。PCIe 设备在提交 MSI 中断请求时,总是向这种 Capability 结构体中的 Message Address 的地址写 Message Data,从而组成一个寄存器写 TLP,向处理器提交中断请求。MSI Capability 的结构体如下图所示。
MSI Capability 结构体有几种不同的类型,根据位数和是否带 MASK 来区分。其中:
- Capability ID:值为 0x05,表示的是 MSI 的 ID 号。
- Next Pointer:指向下一个 Capability。
- Message Control:该字段存放当前设备使用 MSI 机制进行中断请求的状体与控制信息,详细信息可查看 PCI V3.0 规范。
- Message Address/Message Upper Address:存放目的地址。
- Message Data:用来存放 MSI 报文使用的数据。
- Mask Bits:一个 PCIe 设备使用 MSI 机制时,最多可以使用 32 个中断,对应到这里的 32 位,BIT 置 1 时表示屏蔽中断。
- Pending Bits:该字段对于系统软件只读,也是 32 位,对应到可用的 32 个中断,PCIe 设备内部逻辑可修改该字段。
当 Mask Bits 字段的相应位为 1 时,如果 PCIe 设备需要发送对应的中断请求,Pending Bits 字段的对应位将被 PCIe 设备的内部逻辑置 1,此时 PCIe 设备并不会使用 MSI 报文向中断控制器提交请求;但系统软件将 Mask Bits 字段的相应位从 1 改写成 0 时,PCIe 设备将发送 MSI 报文向出来提交中断请求,同时将 Pending Bits 字段的对应位清零。
在 x86 处理器系统中,PCIe 设备通过向 Message Address 写入 Message Data 指定数值实现 MSI/MSI-X 机制。Message Address 字段保存 PCI 总线域的地址,其格式如下图所示。
其中第 31~20 位存放 FSB Interrupts 存储器空间的基地址,其值为 0xFFE。当 PCIe 设备对 0xFFEX-XXXX 这段“PCI 总线域”的地址空间进行写操作时,MCH/ICH 会首先进行“PCI 总线域”到“存储器域”的地址转换,之后将这个写操作翻译为 FSB 总线的 Interrupt Message 总线事务,从而向 CPU 内核提交中断请求。
Message Address 字段其他位的含义如下所示。
- Destination ID:该字段保存目标 CPU 的 ID 号,目标 CPU 的 ID 与该字段相等时,目标 CPU 奖接收这个 Interrupt Message。
- RH(Redirection Hint Indication):该字段位为 0 时,表示 Interrupt Message 将直接发向与 Destination ID 字段相同的目标 CPU;如果 RH 为 1 时,将使能中断转发功能。
- DM(Destination Mode):该位表示在传递优先权最低的中断请求时,Destination ID 字段是否被翻译为 Logical 或 Physical APIC ID。
在 SylixOS 中默认设置 Destination ID 为启动核,即 0 核;RH 为 0,即不使用转发功能,Interrupt Message 将直接发向与 Destination ID 字段相同的目标CPU,也就是 CPU0。
Message Data 字段的格式如下图所示:
- Trigger Mode:该字段表示中断触发模式。
- Delivery Mode:该字段表示如何处理来自 PCIe 设备的中断请求。
- Vector:该字段表示这个请求使用的中断向量。如果 PCIe 设备需要多个中断请求,则该字段必须连续。在许多中断控制器中,该字段连续也意味着中断控制器需要为这个 PCIe 设备分片连续的中断向量号。
SylixOS 中使用 PCI_MSI_DESC 结构体描述一组连续的 MSI 中断向量区域,其详细内容如下:
typedef struct {
UINT32 PCIMSI_uiNum;
ULONG PCIMSI_ulDevIrqVector;
PCI_MSI_MSG PCIMSI_pmmMsg;
UINT32 PCIMSI_uiMasked;
UINT32 PCIMSI_uiMaskPos;
PVOID PCIMSI_pvPriv;
} PCI_MSI_DESC;
typedef PCI_MSI_DESC *PCI_MSI_DESC_HANDLE;
- PCIMSI_uiNum:中断数量。
- PCIMSI_ulDevIrqVector:中断向量基值。
- PCIMSI_pmmMsg:MSI 中断消息。
- PCIMSI_uiMasked:中断掩码。
- PCIMSI_uiMaskPos:中断掩码位置。
- PCIMSI_uiMaskPos:私有数据。
其中 MSI 中断消息的内容即是上文所说的 Message Address 和 Message Data,其详细内容如下;
typedef struct {
UINT32 uiAddressLo; /* low 32 bits of address */
UINT32 uiAddressHi; /* high 32 bits of address */
UINT32 uiData; /* 16 bits of msi message data */
} PCI_MSI_MSG;
- uiAddressLo:要写入的低 32 位地址。
- uiAddressHi:要写入的高 32 为地址。
- uiData:要写入的数据。
SyilxOS 中,申请 MSI 中断时系统动态分配中断向量号,且默认配置 Trigger Mode 为 0,即采用边沿触发方式申请中断;Delivery Mode 为 0,即使用“Fixed Mode”方式,此时这个中断请求被 Destination ID 字段指定的CPU处理。
SylixOS 已经完整的实现了 MSI 中断机制,编写驱动时不需要关心具体的实现流程,调用相关的API接口即可。其中设置 MSI 使能控制状态的 API 接口如下:
#include <SylixOS.h>
INT API_PciDevMsiEnableSet (PCI_DEV_HANDLE hHandle, INT iEnable);
函数 API_PciDevMsiEnableSet 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备控制句柄,将在后续章节介绍。
- 参数 iEnable 是使能与禁能标志,0 为禁能,1 为使能。
函数 API_PciDevMsiEnableSet 在使能 MSI 的同时会禁能 INTx,对应的,在禁能 MSI 时会使能 INTx。如果 PCI 设备不支持 MSI 中断模式,或设置失败都会返回 PX_ERROR。该函数只是设置了 MSI 的控制状态,并没有申请具体的中断。申请中断可通过调用如下API实现。
#include <SylixOS.h>
INT API_PciDevMsiRangeEnable (PCI_DEV_HANDLE hHandle,
UINT uiVecMin,
UINT uiVecMax);
函数 API_ PciDevMsiRangeEnable 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备控制句柄。
- 参数 uiVecMin 是使能区域中断向量最小值。
- 参数 uiVecMax 是使能区域中断向量最大值。
函数 API_ PciDevMsiRangeEnable 设置 MSI 区域使能,并申请一段连续的中断向量,但是真正申请到的向量数量可能小于申请数量。驱动程序在调用该函数时,不仅要判断返回值是否正确,还要判定申请到的中断向量数目是否正确,并做相应的处理。