Fork me on GitHub
侧边栏

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);

主要作用:

  1. 增加 IRQ 的 disable depth。
  2. 屏蔽后续中断处理。
  3. 等待正在执行的中断 Handler 完成。
  4. 底层可能通过 MSI/MSI-X Mask Bit 屏蔽硬件 Vector。

disable_irq_nosync() 的作用

void disable_irq_nosync(unsigned int irq);

主要作用:

  1. 禁止后续该 IRQ。
  2. 不等待其他 CPU 上正在执行的 IRQ Handler 完成。
  3. 适合不能睡眠或不希望同步等待的场景。
  4. 调用方需要自行处理并发问题。

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);
posted @ 2026-07-30 22:31  yooooooo  阅读(20)  评论(0)    收藏  举报