4. pci_dma_set_mask 函数原型:参数解析、返回值含义与内核版本差异

pci_dma_set_mask 是 PCI 设备驱动配置 DMA 寻址能力最核心的接口。高位寻址设备(64-bit DMA capable)必须通过该函数告知内核:设备能够访问的物理地址范围。错误配置将导致 DMA 映射失败、数据损坏或内核 panic。

⚠️ 关键认知: 高位寻址设备并非“必须使用 64-bit 掩码”,而是“必须使用能覆盖设备实际地址线宽度的最小掩码”。例如,仅支持 48-bit 地址的设备应配置 DMA_BIT_MASK(48),而非 DMA_BIT_MASK(64)。

4.1 函数原型与参数解析

函数原型定义在 <linux/pci-dma-compat.h>(内核 5.x 之前)或 <linux/dma-mapping.h>(内核 5.x+):

int pci_dma_set_mask(struct pci_dev *pdev, u64 mask);
参数 类型 说明 高位寻址设备注意事项
pdev struct pci_dev * 目标 PCI 设备结构体指针,由 pci_get_device 或 probe 回调传入 必须指向已使能总线主控(bus master)的设备
mask u64 DMA 地址掩码,表示设备能访问的最高物理地址 高位设备通常使用 DMA_BIT_MASK(64) 或 DMA_BIT_MASK(48)

mask 参数构造规则:

  • 使用 DMA_BIT_MASK(n) 宏:生成低 n 位全 1 的掩码,即设备可访问 0 ~ 2^n - 1 地址范围
  • 常见值:DMA_BIT_MASK(32) = 0x00000000FFFFFFFF,DMA_BIT_MASK(64) = 0xFFFFFFFFFFFFFFFF
  • 高位设备若仅支持 48-bit 地址线,应使用 DMA_BIT_MASK(48),而非 64-bit 掩码
📌 实战要点: 对于高位寻址设备,mask 参数必须与设备 BAR 空间或内部 DMA 引擎的地址线宽度严格匹配。过大的掩码(如 64-bit 设备用 48-bit)会浪费地址空间;过小的掩码(如 48-bit 设备用 64-bit)可能导致 DMA 访问高位地址时静默截断。

4.2 返回值含义与错误处理

返回值 含义 驱动应如何处理
0 成功:内核接受该掩码,DMA 子系统已配置对应地址窗口 继续 probe 流程,可调用 pci_set_consistent_dma_mask 设置一致性掩码
-EIO 失败:平台 DMA 约束或 IOMMU 限制导致无法满足该掩码 尝试降级掩码(如 64→48→40→32),若均失败则 probe 返回错误

高位设备典型错误场景:

  • IOMMU 限制: IOMMU 地址窗口小于设备请求的掩码,返回 -EIO
  • 平台内存映射: 某些 SoC 平台高位内存不可 DMA 访问,内核拒绝 64-bit 掩码
  • 设备未使能总线主控: 调用 pci_set_master 前调用本函数,返回 -EIO
/* 高位设备标准配置模式 */
if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
    dev_info(&pdev->dev, "64-bit DMA mask configured\n");
    /* 设置一致性掩码为 64-bit */
    pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
} else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(48))) {
    dev_info(&pdev->dev, "48-bit DMA mask configured (fallback)\n");
    pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(48));
} else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) {
    dev_warn(&pdev->dev, "32-bit DMA mask configured (limited)\n");
    pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
} else {
    dev_err(&pdev->dev, "No suitable DMA mask available\n");
    return -ENXIO;
}
💡 降级策略: 高位设备应优先尝试 64-bit,然后依次降级到 48-bit、40-bit、32-bit。每次降级后必须重新调用 pci_set_consistent_dma_mask 保持一致性掩码同步。

4.3 内核版本差异与演进

不同内核版本对 pci_dma_set_mask 的实现和接口有显著差异,高位设备驱动必须根据目标内核版本选择正确的调用方式:

内核版本 头文件位置 函数实现变化 高位设备影响
2.6.x ~ 4.x <linux/pci-dma-compat.h> 直接调用 dma_set_mask(&pdev->dev, mask) 兼容性好,但需手动处理一致性掩码
5.0 ~ 5.18 <linux/dma-mapping.h> pci_dma_set_mask 标记为内联,最终调用 dma_set_mask 新增 dma_set_mask_and_coherent 统一接口
5.19+ <linux/dma-mapping.h> pci_dma_set_mask 逐步弃用,推荐直接使用 dma_set_mask 高位设备应迁移到 dma_set_mask_and_coherent
6.x+ <linux/dma-mapping.h> pci_dma_set_mask 可能完全移除,仅保留 dma_* 系列 新驱动必须使用 dma_set_mask 和 dma_set_coherent_mask

版本迁移建议:

  • 内核 5.18 及之前: 继续使用 pci_dma_set_mask,代码可读性高
  • 内核 5.19 ~ 6.x: 使用 dma_set_mask(&pdev->dev, mask) 替代
  • 新驱动开发: 直接使用 dma_set_mask_and_coherent 一次设置两个掩码
/* 内核 5.19+ 推荐写法 */
struct device *dev = &pdev->dev;

if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
    dev_err(dev, "Failed to set 64-bit DMA mask\n");
    return -ENXIO;
}
dev_info(dev, "64-bit DMA mask configured (unified API)\n");
⚠️ 版本陷阱: 内核 5.19+ 中 pci_dma_set_mask 仍存在但可能返回 -EOPNOTSUPP。高位设备驱动若未及时迁移,在 6.x 内核上可能静默失败。建议在 Makefile 或 Kconfig 中增加版本判断。

4.4 函数调用流程与内核决策路径

下图展示了 pci_dma_set_mask 从驱动调用到内核最终决策的完整路径,特别标注了高位寻址设备的关键检查点:

pci_dma_set_mask 内核决策路径(高位设备) 驱动调用 pci_dma_set_mask 内核版本判断(5.19+?) 旧版本 dma_set_mask(&pdev->dev, mask) 新版本 dma_set_mask(dev, mask) IOMMU 地址窗口检查 通过 返回 0(成功) 失败 返回 -EIO(降级或失败) 高位设备关键检查点 IOMMU 地址窗口 >= mask 高位?平台高位内存可 DMA 访问?设备总线主控已使能?

4.5 高位设备配置实战清单

配置高位寻址设备的 pci_dma_set_mask 时,请按以下清单逐项确认:

  1. 确认设备地址线宽度: 读取设备能力寄存器(如 PCIe Device Capabilities Register 的 Max Payload Size 字段间接推断)
  2. 使能总线主控: 在调用 pci_dma_set_mask 前必须调用 pci_set_master(pdev)
  3. 选择最小必要掩码: 优先使用 DMA_BIT_MASK(64),若失败则降级
  4. 同步一致性掩码: 每次设置 DMA 掩码后立即调用 pci_set_consistent_dma_mask
  5. 检查返回值: 非零返回值必须处理,不能忽略
  6. 版本适配: 根据内核版本选择 pci_dma_set_mask 或 dma_set_mask
📌 高位设备黄金法则: 永远使用设备能支持的最小掩码,而非最大掩码。64-bit 设备若仅需访问低 4GB 内存,配置 32-bit 掩码可避免 IOMMU 开销并提高 DMA 性能。