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)。其实现路径如下:
- 参数校验:检查
dev是否为NULL,以及设备是否已正确枚举。 - 读取当前 Command 值:通过
pci_read_config_word(dev, PCI_COMMAND, &cmd)获取当前值。 - 置位 Bus Master 位:
cmd |= PCI_COMMAND_MASTER(宏定义为0x0004)。 - 写回配置空间:通过
pci_write_config_word(dev, PCI_COMMAND, cmd)写入。 - 更新软件状态:设置
dev->is_busmaster = 1,供驱动后续查询。
关键宏定义:
#define PCI_COMMAND_MASTER 0x0004 — 对应 Command 寄存器的 bit 2。#define PCI_COMMAND 0x04 — 配置空间 Command 寄存器偏移。
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标志是否为+,验证驱动是否正确使能。
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;
}
关键点回顾:
•
• 错误路径中需调用
• 该函数不分配任何资源,仅操作配置空间的一个 bit,执行速度极快。
•
pci_set_master 必须在 pci_enable_device 之后、DMA 相关配置之前调用。• 错误路径中需调用
pci_clear_master 回滚状态。• 该函数不分配任何资源,仅操作配置空间的一个 bit,执行速度极快。