1. PCIe驱动开发框架概览
Linux PCIe驱动遵循标准的内核设备模型,主要流程包括:
- 驱动注册 → 2. 设备探测 → 3. 资源分配 → 4. 功能初始化 → 5. 用户交互 → 6. 设备移除
2. 驱动注册与设备匹配
(1) 定义PCI设备ID表
static const struct pci_device_id my_driver_ids[] = {
{ PCI_DEVICE(0x1234, 0x5678) }, // 匹配Vendor ID 0x1234, Device ID 0x5678
{ 0, } // 结束标记
};
MODULE_DEVICE_TABLE(pci, my_driver_ids);
(2) 定义PCI驱动结构体
static struct pci_driver my_pci_driver = {
.name = "my_pcie_driver", // 驱动名称
.id_table = my_driver_ids, // 设备ID表
.probe = my_pcie_probe, // 设备探测函数
.remove = my_pcie_remove, // 设备移除函数
// .suspend = my_pcie_suspend, // 电源管理函数(可选)
// .resume = my_pcie_resume,
};
(3) 注册驱动模块
module_pci_driver(my_pci_driver); // 自动处理模块的init/exit
3. 设备探测(Probe函数)
(1) 基本流程
static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
int ret;
// 1. 启用PCI设备
ret = pci_enable_device(pdev);
if (ret < 0) return ret;
// 2. 请求内存/I/O资源
ret = pci_request_regions(pdev, "my_pcie_driver");
if (ret < 0) goto err_disable;
// 3. 映射BAR空间
void __iomem *bar0 = pci_iomap(pdev, 0, 0); // 映射BAR0
if (!bar0) {
ret = -ENOMEM;
goto err_release;
}
// 4. 配置DMA掩码(若需DMA支持)
if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
} else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) {
pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
} else {
ret = -EIO;
goto err_unmap;
}
// 5. 初始化MSI/MSI-X中断
int irq_count = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (irq_count < 0) {
ret = irq_count;
goto err_unmap;
}
// 6. 注册中断处理函数
ret = request_irq(pci_irq_vector(pdev, 0), my_interrupt_handler,
IRQF_SHARED, "my_pcie_irq", bar0);
if (ret < 0) goto err_free_irq;
// 7. 创建设备节点(如字符设备)
ret = my_create_device_node(pdev);
if (ret < 0) goto err_free_irq;
// 8. 保存私有数据
struct my_private_data *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
priv->bar0 = bar0;
pci_set_drvdata(pdev, priv);
return 0;
err_free_irq:
pci_free_irq_vectors(pdev);
err_unmap:
pci_iounmap(pdev, bar0);
err_release:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
4. 资源管理
(1) BAR空间映射
// 物理地址映射到内核虚拟地址
void __iomem *bar = pci_iomap(pdev, bar_index, size);
// 读写寄存器
u32 reg_value = ioread32(bar + offset);
iowrite32(value, bar + offset);
(2) DMA内存分配
// 分配一致性DMA内存(CPU和设备均可访问)
dma_addr_t dma_handle;
void *dma_buf = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL);
// 释放DMA内存
dma_free_coherent(&pdev->dev, size, dma_buf, dma_handle);
5. 中断处理
(1) MSI/MSI-X配置
// 请求中断向量
int pci_alloc_irq_vectors(pdev, min_vec, max_vec, flags);
// 获取中断号
int irq = pci_irq_vector(pdev, vec_index);
// 注册中断处理函数
request_irq(irq, handler, flags, name, dev_id);
(2) 中断处理函数
static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
struct my_private_data *priv = dev_id;
// 1. 读取中断状态寄存器
u32 status = ioread32(priv->bar0 + STATUS_REG);
if (!(status & IRQ_TRIGGERED))
return IRQ_NONE;
// 2. 处理中断(如读取数据、唤醒等待队列)
wake_up_interruptible(&priv->wait_queue);
// 3. 清除中断标志
iowrite32(status, priv->bar0 + STATUS_REG);
return IRQ_HANDLED;
}
6. 用户空间交互
(1) 字符设备接口
// 定义文件操作结构体
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.read = my_read,
.write = my_write,
.open = my_open,
.release = my_release,
.unlocked_ioctl = my_ioctl,
};
// 创建设备节点
int my_create_device_node(struct pci_dev *pdev) {
dev_t devno = MKDEV(MAJOR_NUM, minor++);
struct cdev *cdev = cdev_alloc();
cdev_init(cdev, &my_fops);
cdev_add(cdev, devno, 1);
device_create(class, NULL, devno, NULL, "my_pcie_dev%d", minor);
}
(2) Sysfs属性
// 定义设备属性
static ssize_t show_version(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "1.0\n");
}
static DEVICE_ATTR(version, 0444, show_version, NULL);
// 在Probe函数中添加属性
device_create_file(&pdev->dev, &dev_attr_version);
7. 设备移除与清理
static void my_pcie_remove(struct pci_dev *pdev) {
struct my_private_data *priv = pci_get_drvdata(pdev);
// 1. 释放中断
free_irq(pci_irq_vector(pdev, 0), priv);
pci_free_irq_vectors(pdev);
// 2. 解除BAR映射
pci_iounmap(pdev, priv->bar0);
// 3. 释放DMA内存
if (priv->dma_buf)
dma_free_coherent(&pdev->dev, priv->dma_size, priv->dma_buf, priv->dma_handle);
// 4. 删除设备节点
device_destroy(class, MKDEV(MAJOR_NUM, minor));
cdev_del(priv->cdev);
// 5. 释放私有数据
kfree(priv);
// 6. 释放PCI资源
pci_release_regions(pdev);
pci_disable_device(pdev);
}
8. 调试与工具
(1) 常用调试命令
lspci -vvv # 查看设备详细信息
dmesg | grep my_pcie # 查看内核日志
cat /proc/interrupts # 查看中断统计
devmem 0xFE000000 # 直接读取物理地址(需root权限)
(2) 内核打印
printk(KERN_INFO "Probe device at %04x:%02x:%02x.%d\n",
pci_domain_nr(pdev->bus), pdev->bus->number,
PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
9. 关键注意事项
- 错误处理:所有资源分配(如
pci_enable_device
、pci_iomap
)必须检查返回值。 - 并发安全:中断处理函数和用户IO操作需考虑锁(如
spin_lock
、mutex
)。 - 内存泄漏:确保
remove
函数与probe
的资源释放完全对称。 - 跨平台兼容性:处理64位地址和字节序(使用
le32_to_cpu
等宏)。 - 内核版本差异:如旧内核使用
pci_enable_msi()
,而新内核推荐pci_alloc_irq_vectors()
。
10. 完整示例代码
GitHub示例仓库 包含完整驱动代码和Makefile。
https://github.com/example/pcie_driver_demo
参考: