陷阱二:中断号映射错误——pci_irq_vector() 返回值的校验与常见错误
在 MSI/MSI-X 中断初始化流程中,驱动开发者最常犯的错误之一就是错误地获取或使用中断号。Linux 内核提供了 pci_irq_vector() 函数来将 MSI 向量索引映射为 Linux 中断号(IRQ number),但该函数的返回值校验往往被忽视,导致中断无法触发、触发到错误的处理函数,甚至内核崩溃。
2.1 函数原型与语义
pci_irq_vector() 定义在 <linux/pci.h> 中,其原型如下:
int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
| 参数/返回值 | 说明 |
|---|---|
dev |
指向 PCI 设备的 struct pci_dev * 指针 |
nr |
MSI/MSI-X 向量的索引,从 0 开始 |
| 返回值 | 成功时返回 Linux IRQ 号(正数);失败时返回 负的错误码(如 -EINVAL) |
⚠️ 常见陷阱:许多驱动开发者误以为
pci_irq_vector() 返回 0 表示失败,或者直接使用返回值而不检查负数。实际上,IRQ 0 在 x86 架构上对应系统定时器,但 MSI 中断号永远不会是 0;正确的校验方式是检查返回值 是否小于 0。
2.2 错误案例:未校验返回值
以下代码片段展示了典型的错误用法:
/* ❌ 错误示例:未校验 pci_irq_vector() 返回值 */
static int my_driver_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
int irq;
int ret;
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return ret;
irq = pci_irq_vector(pdev, 0); /* 假设总是成功 */
ret = request_irq(irq, my_isr, 0, "my_driver", dev);
if (ret) {
dev_err(&pdev->dev, "request_irq failed\n");
return ret;
}
/* ... */
}
如果 pci_irq_vector() 因为 nr 越界或设备未正确分配向量而返回 -EINVAL,该负值会被直接传递给 request_irq(),导致 request_irq() 返回 -EINVAL,但错误日志中不会暴露真正的原因——中断号映射失败。
2.3 正确做法:严格校验
正确的做法是在获取 IRQ 号后立即检查返回值是否小于 0:
/* ✅ 正确示例:严格校验 pci_irq_vector() 返回值 */
static int my_driver_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
int irq;
int ret;
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return ret;
irq = pci_irq_vector(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev,
"Failed to map MSI vector 0 to IRQ: %d\n", irq);
pci_free_irq_vectors(pdev);
return irq;
}
ret = request_irq(irq, my_isr, 0, "my_driver", dev);
if (ret) {
dev_err(&pdev->dev, "request_irq failed: %d\n", ret);
pci_free_irq_vectors(pdev);
return ret;
}
/* ... */
}
2.4 映射错误的原因分析
导致 pci_irq_vector() 返回负值的常见原因包括:
- 向量索引越界:
nr参数大于等于实际分配的向量数量。例如分配了 2 个向量,却请求索引 2。 - 设备未启用 MSI/MSI-X:在调用
pci_alloc_irq_vectors()之前就调用pci_irq_vector()。 - 中断控制器映射失败:底层 IRQ 域(irq domain)无法为 MSI 向量分配中断号,通常与 ACPI/Device Tree 中断映射配置错误有关。
- 热插拔后状态不一致:设备被移除后重新枚举,但驱动未重新分配向量。
2.5 多向量场景下的索引管理
当使用多个 MSI/MSI-X 向量时,索引管理变得更加复杂。以下表格总结了不同场景下的索引规则:
| 中断模式 | 向量数量 | 有效索引范围 | 典型错误 |
|---|---|---|---|
| MSI | 1, 2, 4, 8, 16, 32 | 0 到 (n-1) | 使用索引 n 或负数索引 |
| MSI-X | 1 到 2048 | 0 到 (n-1) | 使用未分配的索引(如跳过索引 0) |
| 传统 INTx | 1 | 仅索引 0 | 使用索引 0 以外的值 |
💡 最佳实践:建议在驱动中定义一个宏或枚举来管理向量索引,避免硬编码数字。例如:
#define MSI_VEC_TX 0
#define MSI_VEC_RX 1
#define MSI_VEC_ERR 2
irq_tx = pci_irq_vector(pdev, MSI_VEC_TX);
if (irq_tx < 0) { /* 处理错误 */ }
2.6 与 pci_request_irq() 的对比
Linux 内核 4.15 之后引入了 pci_request_irq() 辅助函数,它内部封装了 pci_irq_vector() 和 request_irq(),并自动处理错误校验:
/* 使用 pci_request_irq() 简化代码 */
static int my_driver_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
int ret;
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return ret;
/* 内部自动校验 pci_irq_vector() 返回值 */
ret = pci_request_irq(pdev, 0, my_isr, NULL, "my_driver", dev);
if (ret) {
dev_err(&pdev->dev, "pci_request_irq failed: %d\n", ret);
pci_free_irq_vectors(pdev);
return ret;
}
/* ... */
}
对于新驱动,优先推荐使用 pci_request_irq();对于需要精细控制中断亲和性或使用 threaded IRQ 的场景,仍需手动调用 pci_irq_vector() 并严格校验。
2.7 调试检查清单
- ☐ 调用
pci_irq_vector()后是否检查返回值 < 0? - ☐ 向量索引是否在
pci_alloc_irq_vectors()返回的数量范围内? - ☐ 是否在
pci_free_irq_vectors()之后还尝试获取 IRQ 号? - ☐ 热插拔路径中是否重新分配向量并重新获取 IRQ 号?
- ☐ 是否在
devm资源管理框架下正确使用,避免资源泄漏?