4. pci_set_master 函数:函数原型与头文件、函数内部实现机制、调用时机与注意事项

pci_set_master 是 PCI 子系统中最核心的辅助函数之一,它直接决定了设备是否具备总线主控能力(Bus Mastering),进而影响 DMA 传输的合法性。本节从函数原型出发,深入内核源码剖析其实现机制,并给出严格的调用时机与常见陷阱。

4.1 函数原型与头文件

该函数声明在 Linux 内核头文件 include/linux/pci.h 中,原型如下:

int pci_set_master(struct pci_dev *dev);
项目 说明
头文件 #include <linux/pci.h>
参数 struct pci_dev *dev — 指向目标 PCI 设备的描述符
返回值 0 成功;-EINVAL 参数无效或设备不支持
内核版本 自 Linux 2.4 引入,所有主线版本均支持
编码规范提示: 驱动中通常不直接操作 PCI 配置空间的 Command 寄存器,而是通过 pci_set_master / pci_clear_master 这对封装函数,以保证跨架构兼容性。

4.2 函数内部实现机制

pci_set_master 的核心操作是设置 PCI 配置空间 Command 寄存器(偏移 0x04)中的 Bus Master 位(bit 2)。其实现路径如下:

  1. 参数校验:检查 dev 是否为 NULL,以及设备是否已正确枚举。
  2. 读取当前 Command 值:通过 pci_read_config_word(dev, PCI_COMMAND, &cmd) 获取当前值。
  3. 置位 Bus Master 位:cmd |= PCI_COMMAND_MASTER(宏定义为 0x0004)。
  4. 写回配置空间:通过 pci_write_config_word(dev, PCI_COMMAND, cmd) 写入。
  5. 更新软件状态:设置 dev->is_busmaster = 1,供驱动后续查询。
关键宏定义:
#define PCI_COMMAND_MASTER 0x0004 — 对应 Command 寄存器的 bit 2。
#define PCI_COMMAND 0x04 — 配置空间 Command 寄存器偏移。
pci_set_master 内部执行流程 入口:pci_set_master(dev) 校验 dev 是否为 NULL 读取 Command 寄存器 cmd |= PCI_COMMAND_MASTER 写回 Command 寄存器 设置 is_busmaster = 1 配置空间操作细节 偏移:0x04 (PCI_COMMAND) 位掩码:0x0004 (bit 2) 读取函数:pci_read_config_word 写入函数:pci_write_config_word

4.3 调用时机

错误的调用时机是导致 DMA 传输失败或系统崩溃的常见原因。下表总结了不同阶段的调用策略:

阶段 是否可调用 说明
probe() 早期 ✅ 推荐 在 pci_enable_device() 之后、dma_set_mask() 之前调用
probe() 中期 ✅ 可 在分配 DMA 缓冲区之前完成,确保 DMA 引擎已就绪
probe() 晚期 ⚠️ 不推荐 若 DMA 缓冲区已分配但主控未使能,可能导致 DMA 请求被 PCI 桥拒绝
remove() ❌ 不应 使用 pci_clear_master() 撤销主控权限,而非再次调用 pci_set_master
中断上下文 ❌ 禁止 涉及 PCI 配置空间读写,可能睡眠;必须保证在进程上下文调用
常见错误: 在 pci_enable_device() 之前调用 pci_set_master。此时设备尚未获得 I/O 和内存资源,配置空间访问可能返回无效数据,导致 Command 寄存器被错误覆写。

4.4 注意事项与避坑指南

  • 配对使用:pci_set_master 与 pci_clear_master 必须成对出现,在 remove() 或错误处理路径中撤销主控使能。
  • 不影响 DMA 掩码:该函数只设置总线主控位,不涉及 DMA 地址掩码(dma_set_mask / dma_set_coherent_mask),两者是独立的配置步骤。
  • 多次调用安全:内核实现是幂等的——多次调用只会反复置位同一 bit,不会产生副作用。
  • 虚拟化环境:在 PCI 直通(Passthrough)场景下,pci_set_master 可能被 Hypervisor 拦截,需确认 Guest OS 是否具有实际配置空间写入权限。
  • 调试手段:可通过 lspci -vvv 查看 Command 寄存器中的 Bus Master 标志是否为 +,验证驱动是否正确使能。
pci_set_master 调用时序对比 ✅ 正确时序 1. pci_enable_device() 2. pci_set_master() 3. dma_set_mask() 4. 分配 DMA 缓冲区 5. 注册中断处理程序 6. 启动 DMA 传输 → 设备资源就绪 → 主控使能 → 地址掩码 → 缓冲区 ❌ 错误时序 1. pci_set_master() ← 设备未使能,配置空间不可靠 2. pci_enable_device() 3. 分配 DMA 缓冲区 4. 启动 DMA 传输 ← 主控位可能被覆盖或未生效 → 主控使能在设备使能之前 → 不可预测行为

4.5 典型代码片段

以下展示一个符合内核编码规范的 probe 函数片段:

static int my_pci_driver_probe(struct pci_dev *pdev,
                               const struct pci_device_id *id)
{
    int ret;

    /* 1. 使能 PCI 设备 */
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    /* 2. 使能总线主控 — 必须在 DMA 配置之前 */
    pci_set_master(pdev);

    /* 3. 设置 DMA 掩码 */
    ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
    if (ret) {
        ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
        if (ret) {
            pci_clear_master(pdev);
            pci_disable_device(pdev);
            return ret;
        }
    }

    /* 4. 分配 DMA 缓冲区、注册中断、启动硬件 */
    // ...

    return 0;
}
关键点回顾:
• pci_set_master 必须在 pci_enable_device 之后、DMA 相关配置之前调用。
• 错误路径中需调用 pci_clear_master 回滚状态。
• 该函数不分配任何资源,仅操作配置空间的一个 bit,执行速度极快。