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 从驱动调用到内核最终决策的完整路径,特别标注了高位寻址设备的关键检查点:
4.5 高位设备配置实战清单
配置高位寻址设备的 pci_dma_set_mask 时,请按以下清单逐项确认:
- 确认设备地址线宽度: 读取设备能力寄存器(如 PCIe Device Capabilities Register 的 Max Payload Size 字段间接推断)
- 使能总线主控: 在调用
pci_dma_set_mask前必须调用pci_set_master(pdev) - 选择最小必要掩码: 优先使用
DMA_BIT_MASK(64),若失败则降级 - 同步一致性掩码: 每次设置 DMA 掩码后立即调用
pci_set_consistent_dma_mask - 检查返回值: 非零返回值必须处理,不能忽略
- 版本适配: 根据内核版本选择
pci_dma_set_mask或dma_set_mask
📌 高位设备黄金法则: 永远使用设备能支持的最小掩码,而非最大掩码。64-bit 设备若仅需访问低 4GB 内存,配置 32-bit 掩码可避免 IOMMU 开销并提高 DMA 性能。