什么是 Active Device(“主动设备”)?
“Active Device(主动设备)”这一说法,其实是开发者中广泛使用但并不严谨的术语。
与其说设备是“主动的”,不如说:
设备会频繁向 Root Complex 发送中断事件,而这些中断在很多高性能设备中,往往与 DMA(Direct Memory Access,直接内存访问) 数据传输同时出现。
当一次 DMA 传输完成后,设备通常会通过 中断(Interrupt) 的方式通知 CPU 数据已经准备就绪。
这一机制常被形象地称为 Doorbell(门铃)机制 ——
就像快递员按门铃通知你包裹已送达一样,设备通过中断“敲门”提醒 CPU。
DMA 与中断之间的关系
一个典型流程如下:
- 设备向系统内存发起 DMA 读 / 写
- DMA 传输完成
- 设备向 CPU 发送中断
- CPU 响应中断并处理数据
如果一个设备持续地产生 DMA 并伴随中断通知,在驱动语境中就会被称为“Active Device”。
PCIe 中断的三种实现方式
在 PCIe(Peripheral Component Interconnect Express)体系中,中断主要有三种方式:
- Legacy Interrupt
传统 PCI 中断方式,依赖物理中断线,效率低、共享严重,已逐步淘汰。 - MSI(Message Signaled Interrupt)
通过内存写事务触发中断,更高效,是现代 PCIe 设备的主流方式。 - MSI-X(Message Signaled Interrupt Extended)
MSI 的增强版本,支持更多中断向量和独立配置,高性能设备(网卡、NVMe、GPU)普遍采用。
当设备在 PCIe 配置空间(CFG Space)中启用了 MSI 或 MSI-X 后,设备将使用相应机制向 Root Complex(RC)发送中断。
如何让你的设备“表现为 Active”
使用 Xilinx PCIe IP 的 CFG Interrupt 接口(Legacy / MSI)
在 Xilinx PCIe IP Core 中,可以通过 cfg interrupt 接口 向 RC 发送中断信号。
以下为一个完整、连续的示例代码,用于周期性地产生中断:
assign ctx.cfg_interrupt_di = cfg_int_di;
assign ctx.cfg_pciecap_interrupt_msgnum = cfg_msg_num;
assign ctx.cfg_interrupt_assert = cfg_int_assert;
assign ctx.cfg_interrupt = cfg_int_valid;
assign ctx.cfg_interrupt_stat = cfg_int_stat;
always @ ( posedge clk_pcie ) begin
if ( rst ) begin
cfg_int_valid <= 1'b0;
cfg_msg_num <= 5'b0;
cfg_int_assert <= 1'b0;
cfg_int_di <= 8'b0;
cfg_int_stat <= 1'b0;
end else if (cfg_int_ready && cfg_int_valid) begin
cfg_int_valid <= 1'b0;
end else if (o_int) begin
cfg_int_valid <= 1'b0;
end
end
time int_cnt = 0;
always @ ( posedge clk_pcie ) begin
if (rst) begin
int_cnt <= 0;
end else if (int_cnt == 32'd100000) begin
int_cnt <= 0;
end else if (int_enable) begin
int_cnt <= int_cnt + 1;
end
end
assign o_int = (int_cnt == 32'd100000);
该方式在 Legacy Interrupt 或 MSI 模式下完全有效,也是许多示例工程和仿真设备常用的做法。
⚠️ 重要限制:CFG Interrupt 不支持 MSI-X
当你的模拟设备包含 MSI-X Capability(MSI-X CAP)时,上述方法将无法发送中断。
原因在于:
Xilinx PCIe IP Core 提供的 cfg interrupt 接口本身并不支持 MSI-X 中断类型。
一旦 RC 为该设备启用了 MSI-X:
- cfg interrupt 接口将被忽略
- 必须使用 MSI-X 规定的方式发送中断
MSI-X 的正确做法:手动构造并发送 TLP
从 PCIe 协议层面来看:
MSI / MSI-X 中断,本质上就是一次特定格式的 Memory Write TLP。
因此,在 MSI-X 模式下,必须由用户逻辑:
- 手动构造 MEMWR64 TLP
- 将中断数据写入 MSI-X Table 中指定的地址
- 通过 TX 通道发送给 Root Complex
下面是一个完整、未拆分的 MSI-X 中断发送示例:
bit msix_valid;
bit msix_has_data;
bit[127:0] msix_tlp;
assign tlps_static.tdata[127:0] = msix_tlp;
assign tlps_static.tkeepdw = 4'hf;
assign tlps_static.tlast = 1'b1;
assign tlps_static.tuser[0] = 1'b1;
assign tlps_static.tvalid = msix_valid;
assign tlps_static.has_data = msix_has_data;
wire [31:0] HDR_MEMWR64 = 32'b01000000_00000000_00000000_00000001;
wire [31:0] MWR64_DW2 = {`_bs16(pcie_id), 8'b00000000, 8'b00001111};
wire [31:0] MWR64_DW3 = {i_addr[31:2], 2'b0};
wire [31:0] MWR64_DW4 = {i_data};
always @ ( posedge clk_pcie ) begin
if ( rst ) begin
msix_valid <= 1'b0;
msix_has_data <= 1'b0;
msix_tlp <= 127'b0;
end else if (msix_valid) begin
msix_valid <= 1'b0;
end else if (msix_has_data && tlps_static.tready) begin
msix_valid <= 1'b1;
msix_has_data <= 1'b0;
end else if (o_int) begin
msix_has_data <= 1'b1;
msix_tlp <= {MWR64_DW4, MWR64_DW3, MWR64_DW2, HDR_MEMWR64};
end
end
time int_cnt = 0;
always @ ( posedge clk_pcie ) begin
if (rst) begin
int_cnt <= 0;
end else if (int_cnt == 32'd100000) begin
int_cnt <= 0;
end else if (int_enable) begin
int_cnt <= int_cnt + 1;
end
end
assign o_int = (int_cnt == 32'd100000);
总结
- “Active Device”并非严格术语,本质是 DMA + 中断行为
- Xilinx CFG Interrupt:
- ✔ 支持 Legacy / MSI
- ❌ 不支持 MSI-X
- 启用 MSI-X 的设备:
- 必须手动构造并发送 MEMWR TLP
- 从协议角度看:
- 中断 ≈ 一次特殊的 PCIe Memory Write
📌 完整实现代码将发布在:
Comments NOTHING