PCIE 的 per-vector masking
1. PCIe Per-Vector Masking 是什么
Per-Vector Masking,逐向量屏蔽,用于单独屏蔽某一个 MSI 或 MSI-X 中断向量,而不影响设备的其他中断向量。
例如,一个 PCIe 网卡申请了 4 个中断向量:
- Vector 0:RX Queue 0
- Vector 1:RX Queue 1
- Vector 2:TX Completion
- Vector 3:Error
如果只屏蔽 Vector 1:
- Vector 1 的中断不会发送给 CPU
- Vector 0、2、3 仍然可以正常产生中断
这比直接关闭整个设备的 MSI/MSI-X 更精细。
2. MSI-X 的 Per-Vector Masking
MSI-X 天然支持逐向量屏蔽。每一个 MSI-X Table Entry 都有独立的 Vector Control 字段。
一个 MSI-X Table Entry 通常为 16 字节:
| 偏移 | 字段 | 作用 |
|---|---|---|
0x00 |
Message Address Low | MSI 写入地址低 32 位 |
0x04 |
Message Address High | MSI 写入地址高 32 位 |
0x08 |
Message Data | MSI 中断数据 |
0x0C |
Vector Control | 向量控制字段 |
Vector Control 的 bit 0 是:
bit 0: Mask Bit
含义:
0 = 该 MSI-X Vector 未屏蔽
1 = 该 MSI-X Vector 被屏蔽
其他位为保留位。
例如:
Vector Control = 0x00000001
表示这个 MSI-X 向量已经被屏蔽。
3. Function Mask 和 Per-Vector Mask 的区别
MSI-X Capability 中还有一个 Function Mask。
| 屏蔽方式 | 作用范围 |
|---|---|
| Per-Vector Mask | 只屏蔽一个 MSI-X 向量 |
| Function Mask | 屏蔽该 PCI Function 的所有 MSI-X 向量 |
| MSI-X Enable 清零 | 完全禁用 MSI-X 功能 |
有效屏蔽条件可以理解为:
Effective Mask =
Function Mask
OR
当前 Vector 的 Mask Bit
只要任意一个为 1,该 Vector 就不能发送 MSI-X 中断。
例如:
Function Mask = 0
Vector 0 Mask = 0 -> Vector 0 可以触发中断
Vector 1 Mask = 1 -> Vector 1 被屏蔽
Vector 2 Mask = 0 -> Vector 2 可以触发中断
如果:
Function Mask = 1
则无论各 Vector 的 Mask Bit 是多少,所有 MSI-X Vector 都被屏蔽。
4. 中断被 Mask 后,设备如何处理
MSI-X 中断被屏蔽后,设备不能向 Host 发出对应的 MSI-X Memory Write。
如果屏蔽期间出现了对应的中断事件,设备通常需要设置 MSI-X Pending Bit Array,也就是 PBA 中对应的 bit。
Vector N 被屏蔽
|
v
设备产生 Vector N 对应事件
|
v
不发送 MSI-X Message
|
v
设置 PBA[N] = 1
之后软件解除屏蔽:
Vector N Mask Bit: 1 -> 0
如果此时:
PBA[N] = 1
设备应当发送该 Vector 的 MSI-X Message,并清除或更新对应的 Pending 状态。
需要注意,PBA 记录的是“存在待处理中断”,不是事件计数器。屏蔽期间同一个 Vector 发生多次事件,PBA 通常仍然只是一个 bit:
发生 1 次事件:PBA[N] = 1
发生 10 次事件:PBA[N] 仍然是 1
实际发生了多少个事件,需要设备内部队列、状态寄存器或描述符环保存。
5. MSI 的 Per-Vector Masking
普通 MSI 也可以支持 Per-Vector Masking,但它是可选功能。
PCI MSI Capability 中的 Message Control 寄存器包含:
Per-Vector Masking Capable
如果这个 bit 为 1,MSI Capability 结构中会额外存在:
- Mask Bits
- Pending Bits
每个 MSI Vector 对应一个 Mask Bit:
Mask Bits[N] = 1 -> MSI Vector N 被屏蔽
Mask Bits[N] = 0 -> MSI Vector N 未屏蔽
Pending Bits 表示该 Vector 屏蔽期间是否发生了中断请求。
MSI 和 MSI-X 的区别
| 特性 | MSI | MSI-X |
|---|---|---|
| Per-Vector Masking | 可选 | 必须支持 |
| Mask 存放位置 | MSI Capability 配置空间 | MSI-X Table |
| Pending 状态 | Pending Bits 寄存器 | PBA |
| 最大 Vector 数量 | 通常最多 32 个 | 最多 2048 个 |
| 每个 Vector 独立地址和数据 | 通常不独立 | 独立 |
6. Linux 驱动中的表现
Linux 驱动通常不需要直接操作 MSI-X Table 的 Mask Bit,而是通过 IRQ 子系统操作:
disable_irq(irq);
作用:
- 等待当前 IRQ Handler 执行完成
- 禁止后续该 IRQ 的处理中断
- 底层 IRQ Chip 可能调用 MSI/MSI-X 的 mask 回调
如果不能等待当前 Handler 完成,可以使用:
disable_irq_nosync(irq);
重新启用:
enable_irq(irq);
disable_irq() 的作用
void disable_irq(unsigned int irq);
主要作用:
- 增加 IRQ 的 disable depth。
- 屏蔽后续中断处理。
- 等待正在执行的中断 Handler 完成。
- 底层可能通过 MSI/MSI-X Mask Bit 屏蔽硬件 Vector。
disable_irq_nosync() 的作用
void disable_irq_nosync(unsigned int irq);
主要作用:
- 禁止后续该 IRQ。
- 不等待其他 CPU 上正在执行的 IRQ Handler 完成。
- 适合不能睡眠或不希望同步等待的场景。
- 调用方需要自行处理并发问题。
7. Linux 下查看 MSI/MSI-X
查看设备使用的中断:
lspci -s <BDF> -vv
例如:
lspci -s 0000:01:00.0 -vv
可能看到:
Capabilities: [b0] MSI-X: Enable+ Count=16 Masked-
Vector table: BAR=4 offset=00000000
PBA: BAR=4 offset=00002000
字段含义:
| 字段 | 含义 |
|---|---|
Enable+ |
MSI-X 已启用 |
Count=16 |
设备支持 16 个 MSI-X Vector |
Masked- |
Function Mask 没有置位 |
Vector table |
MSI-X Table 所在 BAR 和偏移 |
PBA |
Pending Bit Array 所在 BAR 和偏移 |
这里的:
Masked+
通常表示 MSI-X Capability 中的 Function Mask 被置位,不代表某一个 Table Entry 的 Per-Vector Mask Bit。
lspci 默认不会逐项打印每个 MSI-X Table Entry 的 Mask Bit。
查看设备实际使用的 IRQ:
grep -i '<设备驱动或者设备名称>' /proc/interrupts
例如:
grep -i nvme /proc/interrupts
8. Mask 一个 MSI-X Vector 的标准流程
如果底层软件需要直接修改 MSI-X Table,通常采用以下流程:
1. 找到目标 MSI-X Table Entry
2. 设置 Vector Control.Mask = 1
3. 执行必要的 MMIO 写屏障或读回
4. 修改 Message Address 或 Message Data
5. 执行必要的 MMIO 写屏障或读回
6. 清除 Vector Control.Mask
7. 如果 PBA 对应 bit 为 1,设备发送 pending interrupt
核心目的是避免在修改 MSI-X Entry 的过程中,设备使用“部分更新”的地址或数据发送中断。
伪代码:
u32 ctrl;
/* 屏蔽该 Vector */
ctrl = readl(entry + MSI_X_VECTOR_CTRL);
ctrl |= BIT(0);
writel(ctrl, entry + MSI_X_VECTOR_CTRL);
/* 确保 Mask 写入已经到达设备 */
readl(entry + MSI_X_VECTOR_CTRL);
/* 更新 MSI-X Message */
writel(msg_addr_lo, entry + MSI_X_ADDR_LO);
writel(msg_addr_hi, entry + MSI_X_ADDR_HI);
writel(msg_data, entry + MSI_X_DATA);
/* 确保配置写入已经完成 */
readl(entry + MSI_X_DATA);
/* 解除屏蔽 */
ctrl &= ~BIT(0);
writel(ctrl, entry + MSI_X_VECTOR_CTRL);
实际 Linux PCI 驱动不建议直接这样操作,因为 MSI-X Table 一般由 PCI/MSI 子系统管理。驱动优先使用:
disable_irq(irq);
enable_irq(irq);


浙公网安备 33010602011771号