PCIe 的 MSI 中断
前言
- 什么是 MSI 中断 (Message Signaled Interrupts)
概念与内容介绍待补充
正文
- 对 EP 的初始化
- 需要对 EP 的配置空间 MSI 相关功能的寄存器进行初始化,主要有两个寄存器 Message Address 和 Message Data。它们分别的含义是 EP 产生 MSI 中断的操作是往 Message Address 寄存器的地址写入 Message Data 指定的数据,从而来产生 MSI 中断。
- Message Address 在不同的 CPU 有不同的配置,例如 rk3568 写入的地址为 GICD 中断控制器的某个地址,其他有些 CPU 配置任意地址都可。
- 还需要对 EP MSI 中断能力进行使能。即下图中的 Message Control Configuration MSI Control Status Register Field Descriptions 寄存器进行配置。
截图来自 F-Tile Avalon® Streaming Intel® FPGA IP for PCI Express* User Guide
- 对 RC 的寄存器初始化
本文以 designware IP 核的某型 CPU 为例。其他的 CPU 会存在写 GICD 的地址,和下面的配置会有区别。
- 写入 PCIE_MSI_ADDR_LO , 即需要和 EP 配置的 Message Address 相匹配。RC 会捕获 EP 写入这个地址的操作,并触发中断。
- 写入 PCIE_MSI_INTR0_ENABLE,即使能所有的 MSI 中断
#define PCIE_MSI_ADDR_LO 0x820
#define PCIE_MSI_ADDR_HI 0x824
void dw_pcie_msi_init(struct pcie_port *pp)
{
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
u64 msi_target = (u64)pp->msi_data;
if (!IS_ENABLED(CONFIG_PCI_MSI))
return;
/* Program the msi_data */
dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
}
EXPORT_SYMBOL_GPL(dw_pcie_msi_init);
#define PCIE_MSI_INTR0_ENABLE 0x828
#define PCIE_MSI_INTR0_MASK 0x82C
#define PCIE_MSI_INTR0_STATUS 0x830
void dw_pcie_setup_rc(struct pcie_port *pp)
{
u32 val, ctrl, num_ctrls;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
/*
* Enable DBI read-only registers for writing/updating configuration.
* Write permission gets disabled towards the end of this function.
*/
dw_pcie_dbi_ro_wr_en(pci);
dw_pcie_setup(pci);
if (pci_msi_enabled() && !pp->ops->msi_host_init) {
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
/* Initialize IRQ Status array */
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
pp->irq_mask[ctrl] = ~0;
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
(ctrl * MSI_REG_CTRL_BLOCK_SIZE),
pp->irq_mask[ctrl]);
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
(ctrl * MSI_REG_CTRL_BLOCK_SIZE),
~0);
}
}
// 省略了其他操作
}
-
中断执行
在中断服务函数中,需要读PCIE_MSI_INTR0_STATUS
寄存器来判断是几号MSI中断,这个是由配置的 Message Data 来决定的。 -
中断状态清除
需要写PCIE_MSI_INTR0_STATUS
寄存器来清除对应的中断,否则中断会持续触发。同时也需要写 GIC 本身的寄存器来清除 PCIe 的中断。
#define PCIE_MSI_INTR0_STATUS 0x830
/* MSI int handler */
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp)
{
int i, pos, irq;
unsigned long val;
u32 status, num_ctrls;
irqreturn_t ret = IRQ_NONE;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
for (i = 0; i < num_ctrls; i++) {
status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
(i * MSI_REG_CTRL_BLOCK_SIZE));
if (!status)
continue;
ret = IRQ_HANDLED;
val = status;
pos = 0;
while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
pos)) != MAX_MSI_IRQS_PER_CTRL) {
irq = irq_find_mapping(pp->irq_domain,
(i * MAX_MSI_IRQS_PER_CTRL) +
pos);
generic_handle_irq(irq);
pos++;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(dw_handle_msi_irq);
总结
至此,EP 可以正确触发 MSI 中断,并被 RC 捕获后往 GIC 投递 PCIe 中断。本例的 MSI 中断并不是直接写 GIC 的 ITS ((Interrupt Translation Service)在GICv3中是可选的。ITS负责接收来自外设的中断,并将它们转化为LPI INTID发送到相应的Redistributor) 产生的,而是由 RC 中转了然后投递到 GIC 的。
其他
EP 生成 MSI 中断的消息填充,图中的含义是生成一个 MSI Mwr 的 TLP 消息,其组包的数据来源,包括 Message Address 和 Message Data。