Linux PCIe设备驱动开发框架

1. PCIe驱动开发框架概览

Linux PCIe驱动遵循标准的内核设备模型,主要流程包括:

  1. 驱动注册 → 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. 关键注意事项

  1. 错误处理:所有资源分配(如pci_enable_devicepci_iomap)必须检查返回值。
  2. 并发安全:中断处理函数和用户IO操作需考虑锁(如spin_lockmutex)。
  3. 内存泄漏:确保remove函数与probe的资源释放完全对称。
  4. 跨平台兼容性:处理64位地址和字节序(使用le32_to_cpu等宏)。
  5. 内核版本差异:如旧内核使用pci_enable_msi(),而新内核推荐pci_alloc_irq_vectors()

10. 完整示例代码

GitHub示例仓库 包含完整驱动代码和Makefile。

https://github.com/example/pcie_driver_demo

参考:

  1. PCIE学习系列 五(Linux之PCIe设备驱动开发框架)_pcie驱动-CSDN博客
  2. https://blog.csdn.net/qq_42208449/category_12420791.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩瀚之水_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值