4. pci_set_dma_mask() API 详解:函数原型、参数含义、返回值处理、典型调用示例

pci_set_dma_mask() 是 PCI/PCIe 设备驱动程序中最核心的 DMA 配置接口之一。它决定了设备能够访问的主存物理地址范围,直接影响 DMA 缓冲区的分配策略和系统内存映射的兼容性。本节从函数原型出发,逐层解析参数语义、返回值处理逻辑,并通过典型示例展示其在驱动中的标准用法。

pci_set_dma_mask() 调用流程与地址空间映射 驱动调用 pci_set_dma_mask() 参数 mask DMA_BIT_MASK(32/64) 内核检查 设备地址能力 成功 (返回 0) DMA 掩码生效 失败 (返回 -EIO) 降级或退出 地址空间映射说明: • 32-bit 掩码 (0xFFFFFFFF):设备只能访问 0~4GB 物理地址空间 • 64-bit 掩码 (0xFFFFFFFFFFFFFFFF):设备可访问全部 64-bit 地址空间

4.1 函数原型与参数含义

pci_set_dma_mask() 定义在 <linux/pci.h> 中,其原型如下:

int pci_set_dma_mask(struct pci_dev *dev, u64 mask);
参数 类型 含义 典型值
dev struct pci_dev * 指向目标 PCI 设备的设备结构体指针,在 probe() 中由 pci_get_device() 或 pci_dev_get() 获得 —
mask u64 DMA 地址掩码,表示设备能够寻址的物理地址范围。掩码中为 1 的位表示设备可以使用的地址位 DMA_BIT_MASK(32) = 0xFFFFFFFF
DMA_BIT_MASK(64) = 0xFFFFFFFFFFFFFFFF

关键理解:掩码 mask 并非限制 DMA 地址必须小于等于该值,而是定义设备硬件能够解码的地址位宽。例如 DMA_BIT_MASK(32) 表示设备只能使用低 32 位地址线,无法访问高于 4GB 的物理内存。

4.2 返回值处理与错误语义

函数返回 int 类型,遵循 Linux 内核标准的 0 成功 / 负错误码约定:

返回值 宏定义 含义 驱动处理策略
0 — 成功设置 DMA 掩码,设备可以按指定地址范围进行 DMA 操作 继续 probe 流程,可调用 pci_set_consistent_dma_mask() 设置一致性掩码
-EIO #define -EIO 5 设备不支持该掩码对应的地址宽度。常见原因:设备硬件地址线不足、PCI 桥限制、IOMMU 限制 尝试降级到更小的掩码(如从 64-bit 降级到 32-bit),若仍失败则返回错误退出 probe
-EINVAL #define -EINVAL 22 参数无效,通常因为 dev 为 NULL 或 mask 为 0 检查参数传递逻辑,修复驱动 bug

常见陷阱:许多驱动在 pci_set_dma_mask() 返回非零时直接返回 -ENODEV,这是不严谨的做法。正确的做法是区分 -EIO 和 -EINVAL,前者可以尝试降级,后者应视为驱动 bug。

4.3 典型调用示例:64-bit 优先降级策略

以下代码展示了工业级驱动中标准的 DMA 掩码配置模式——优先尝试 64-bit,若失败则降级到 32-bit:

static int my_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    int ret;

    /* 步骤 1:启用 PCI 设备 */
    ret = pci_enable_device(dev);
    if (ret)
        return ret;

    /* 步骤 2:尝试设置 64-bit DMA 掩码 */
    ret = pci_set_dma_mask(dev, DMA_BIT_MASK(64));
    if (ret) {
        /* 64-bit 失败,降级到 32-bit */
        ret = pci_set_dma_mask(dev, DMA_BIT_MASK(32));
        if (ret) {
            dev_err(&dev->dev, "无法设置 DMA 掩码 (32-bit 也失败)\n");
            pci_disable_device(dev);
            return ret;
        }
        dev_info(&dev->dev, "使用 32-bit DMA 寻址\n");
    } else {
        dev_info(&dev->dev, "使用 64-bit DMA 寻址\n");
    }

    /* 步骤 3:设置一致性 DMA 掩码(通常与流式掩码一致) */
    ret = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64));
    if (ret) {
        ret = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(32));
        if (ret) {
            dev_err(&dev->dev, "无法设置一致性 DMA 掩码\n");
            pci_disable_device(dev);
            return ret;
        }
    }

    /* 步骤 4:分配 DMA 缓冲区并继续初始化 */
    // ...

    return 0;
}

4.4 调用顺序与依赖关系

pci_set_dma_mask() 的调用必须遵循严格的时序依赖:

  1. 必须先调用 pci_enable_device():该函数初始化 PCI 配置空间,使能设备的总线主控能力,是 DMA 操作的前提。
  2. 必须在分配任何 DMA 缓冲区之前调用:DMA 掩码影响 dma_alloc_coherent() 和 pci_alloc_consistent() 的地址分配策略。
  3. 一致性掩码应在流式掩码之后设置:pci_set_consistent_dma_mask() 依赖于 pci_set_dma_mask() 的成功状态。
  4. 若设备支持多个功能,每个功能需独立设置:多功能 PCI 设备每个 function 有独立的 DMA 地址能力。
pci_set_dma_mask() 调用时序依赖 pci_enable_device() 使能总线主控 pci_set_dma_mask() 设置流式掩码 pci_set_consistent_dma_mask() 设置一致性掩码 dma_alloc_coherent() 分配 DMA 缓冲区 ⚠ 顺序不可颠倒:先使能设备 → 设置流式掩码 → 设置一致性掩码 → 最后分配缓冲区

4.5 常见错误模式与调试建议

错误模式 现象 根因 调试方法
掩码设置成功但 DMA 失败 DMA 传输超时或数据损坏 设备实际地址线少于掩码声称的宽度;PCI 桥或 IOMMU 限制 检查 /sys/bus/pci/devices/.../dma_mask 和 lspci -vv 的 Address 字段
64-bit 掩码设置失败 返回 -EIO 设备不支持 64-bit 寻址;系统没有 64-bit DMA 地址空间 降级到 32-bit;检查内核日志 dmesg 中的 DMA 相关消息
一致性掩码设置失败 返回 -EIO 设备或系统不支持一致性 DMA 的地址范围 尝试使用 dma_set_coherent_mask() 替代;检查 IOMMU 配置
忘记调用 pci_enable_device() 内核 oops 或返回 -EINVAL 设备未使能,PCI 配置空间不可访问 确保 probe 中第一行调用 pci_enable_device()

调试技巧:在 /sys/kernel/debug/dma-api/ 中可以查看当前系统的 DMA 掩码使用情况。使用 echo 1 > /sys/kernel/debug/dma-api/dump 可以输出所有设备的 DMA 掩码信息。

4.6 与 dma_set_mask() 的关系

pci_set_dma_mask() 是 dma_set_mask() 的 PCI 封装版本。两者关系如下:

函数 所属层 参数差异 推荐使用场景
pci_set_dma_mask() PCI 子系统 第一个参数为 struct pci_dev * PCI/PCIe 设备驱动(传统方式)
dma_set_mask() DMA API 通用层 第一个参数为 struct device * 平台设备、USB 设备等非 PCI 设备;新驱动推荐使用

在 PCI 驱动中,两者可以互换使用:

/* 等价调用 */
pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));

内核演进趋势:Linux 内核社区推荐新驱动使用 dma_set_mask() 和 dma_set_coherent_mask() 通用接口,以实现设备无关性。但 pci_set_dma_mask() 在现有驱动中仍广泛使用,且与 PCI 错误处理逻辑结合更紧密。