1.Linux PCIe驱动开发简介

Linux PCIe驱动开发是一个复杂的主题,但我可以为您提供一个详细的概述和基本步骤。以下是 Linux PCIe 驱动开发的主要方面:

1. 基础知识

- 了解 PCIe 总线架构和协议
- 熟悉 Linux 内核模块开发
- 了解设备树(Device Tree)概念(对于嵌入式系统)

2. 驱动框架

Linux 内核提供了 PCI 子系统,简化了 PCIe 设备驱动的开发。主要使用以下结构和函数:

- struct pci_driver:描述 PCI 驱动程序
- struct pci_device_id:定义驱动程序支持的设备
- pci_register_driver():注册 PCI 驱动程序
- pci_unregister_driver():注销 PCI 驱动程序

3. 驱动程序开发步骤

a. 创建驱动程序结构:

```c
static struct pci_driver my_pcie_driver = {
    .name = "my_pcie_driver",
    .id_table = my_pcie_ids,
    .probe = my_pcie_probe,
    .remove = my_pcie_remove,
};
```

b. 定义支持的设备 ID:

```c
static const struct pci_device_id my_pcie_ids[] = {
    { PCI_DEVICE(VENDOR_ID, DEVICE_ID) },
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pcie_ids);
```

c. 实现 probe 函数:

```c
static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    // 启用设备
    if (pci_enable_device(pdev))
        return -EIO;

    // 请求 I/O 和内存资源
    if (pci_request_regions(pdev, "my_pcie_driver"))
        goto err_disable_device;

    // 映射 BAR 地址
    void __iomem *mmio = pci_iomap(pdev, 0, 0);
    if (!mmio)
        goto err_release_regions;

    // 其他初始化操作...

    return 0;

err_release_regions:
    pci_release_regions(pdev);
err_disable_device:
    pci_disable_device(pdev);
    return -EIO;
}
```

d. 实现 remove 函数:

```c
static void my_pcie_remove(struct pci_dev *pdev)
{
    // 取消映射 BAR 地址
    pci_iounmap(pdev, mmio);

    // 释放 I/O 和内存资源
    pci_release_regions(pdev);

    // 禁用设备
    pci_disable_device(pdev);
}
```

e. 模块初始化和清理:

```c
static int __init my_pcie_init(void)
{
    return pci_register_driver(&my_pcie_driver);
}

static void __exit my_pcie_exit(void)
{
    pci_unregister_driver(&my_pcie_driver);
}

module_init(my_pcie_init);
module_exit(my_pcie_exit);
```

4. DMA 操作

对于需要 DMA 的设备,您需要:

- 使用 dma_alloc_coherent() 分配 DMA 缓冲区
- 设置 DMA 掩码(pci_set_dma_mask())
- 使用 pci_set_master() 启用总线主控

5. 中断处理

- 使用 request_irq() 注册中断处理程序
- 在 probe 函数中设置 MSI 或 MSI-X 中断(如果支持)

6. 电源管理

实现 suspend 和 resume 回调函数,以支持系统休眠和唤醒。

7. 调试技巧

- 使用 printk() 输出调试信息
- 利用 /sys/kernel/debug/ 下的调试接口
- 使用 KGDB 进行内核调试

8. 编译和测试

- 编写 Makefile 用于编译驱动程序
- 使用 insmod 和 rmmod 命令加载和卸载驱动
- 通过 /sys/bus/pci/devices/ 检查设备状态

9. 高级主题

- MSI 和 MSI-X 中断
- PCIe 配置空间访问
- 热插拔支持
- SR-IOV 虚拟化

开发 PCIe 驱动程序需要深入了解 Linux 内核和 PCIe 规范。建议仔细阅读 Linux 内核文档,特别是 Documentation/PCI/ 目录下的文件,以获取更多详细信息和最佳实践。
 

2 linux pcie开发举例 

使用字符设备接口来实现通信,这是一种常用且灵活的方法。以下是完整的PCIe驱动程序代码,包括与应用程序通信的部分:

```c
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define DRIVER_NAME "my_pcie_driver"
#define DEVICE_NAME "my_pcie_dev"

struct my_pcie_dev {
    struct pci_dev *pdev;
    void __iomem *mmio_base;
    resource_size_t mmio_len;
    struct cdev cdev;
    dev_t dev_num;
    struct device *device;
};

static int my_pcie_open(struct inode *inode, struct file *file)
{
    struct my_pcie_dev *dev = container_of(inode->i_cdev, struct my_pcie_dev, cdev);
    file->private_data = dev;
    return 0;
}

static int my_pcie_release(struct inode *inode, struct file *file)
{
    return 0;
}

static ssize_t my_pcie_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
    struct my_pcie_dev *dev = file->private_data;
    u32 value;
    
    if (*offset >= sizeof(u32))
        return 0;

    value = ioread32(dev->mmio_base);
    
    if (copy_to_user(buf, &value, sizeof(u32)))
        return -EFAULT;

    *offset += sizeof(u32);
    return sizeof(u32);
}

static ssize_t my_pcie_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
    struct my_pcie_dev *dev = file->private_data;
    u32 value;

    if (count != sizeof(u32))
        return -EINVAL;

    if (copy_from_user(&value, buf, sizeof(u32)))
        return -EFAULT;

    iowrite32(value, dev->mmio_base);
    return sizeof(u32);
}

static const struct file_operations my_pcie_fops = {
    .owner = THIS_MODULE,
    .open = my_pcie_open,
    .release = my_pcie_release,
    .read = my_pcie_read,
    .write = my_pcie_write,
};

static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct my_pcie_dev *my_dev;
    int err;

    my_dev = devm_kzalloc(&pdev->dev, sizeof(*my_dev), GFP_KERNEL);
    if (!my_dev)
        return -ENOMEM;

    my_dev->pdev = pdev;

    err = pcim_enable_device(pdev);
    if (err) {
        dev_err(&pdev->dev, "Failed to enable PCI device\n");
        return err;
    }

    err = pcim_iomap_regions(pdev, BIT(0), DRIVER_NAME);
    if (err) {
        dev_err(&pdev->dev, "Failed to request MMIO region\n");
        return err;
    }

    my_dev->mmio_base = pcim_iomap_table(pdev)[0];
    my_dev->mmio_len = pci_resource_len(pdev, 0);

    pci_set_drvdata(pdev, my_dev);

    // 创建字符设备
    err = alloc_chrdev_region(&my_dev->dev_num, 0, 1, DEVICE_NAME);
    if (err) {
        dev_err(&pdev->dev, "Failed to allocate device number\n");
        return err;
    }

    cdev_init(&my_dev->cdev, &my_pcie_fops);
    err = cdev_add(&my_dev->cdev, my_dev->dev_num, 1);
    if (err) {
        dev_err(&pdev->dev, "Failed to add character device\n");
        unregister_chrdev_region(my_dev->dev_num, 1);
        return err;
    }

    my_dev->device = device_create(THIS_MODULE, NULL, my_dev->dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(my_dev->device)) {
        dev_err(&pdev->dev, "Failed to create device\n");
        cdev_del(&my_dev->cdev);
        unregister_chrdev_region(my_dev->dev_num, 1);
        return PTR_ERR(my_dev->device);
    }

    dev_info(&pdev->dev, "PCIe device probed successfully\n");
    return 0;
}

static void my_pcie_remove(struct pci_dev *pdev)
{
    struct my_pcie_dev *my_dev = pci_get_drvdata(pdev);

    device_destroy(THIS_MODULE, my_dev->dev_num);
    cdev_del(&my_dev->cdev);
    unregister_chrdev_region(my_dev->dev_num, 1);

    dev_info(&pdev->dev, "PCIe device removed\n");
}

static struct pci_device_id my_pcie_ids[] = {
    { PCI_DEVICE(0x1234, 0x5678) },  // 替换为实际的 Vendor ID 和 Device ID
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pcie_ids);

static struct pci_driver my_pcie_driver = {
    .name = DRIVER_NAME,
    .id_table = my_pcie_ids,
    .probe = my_pcie_probe,
    .remove = my_pcie_remove,
};

module_pci_driver(my_pcie_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PCIe driver with character device interface");
```

这个驱动程序创建了一个字符设备接口,允许应用程序通过文件操作与PCIe设备进行通信。现在,让我们创建一个简单的应用程序来演示如何与这个驱动程序通信:

```c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

#define DEVICE_FILE "/dev/my_pcie_dev"

int main()
{
    int fd;
    uint32_t value_to_write = 0x12345678;
    uint32_t value_read = 0;

    // 打开设备文件
    fd = open(DEVICE_FILE, O_RDWR);
    if (fd < 0) {
        perror("Failed to open the device");
        return -1;
    }

    // 写入数据
    if (write(fd, &value_to_write, sizeof(value_to_write)) != sizeof(value_to_write)) {
        perror("Failed to write to the device");
        close(fd);
        return -1;
    }

    printf("Wrote value: 0x%08x\n", value_to_write);

    // 读取数据
    if (read(fd, &value_read, sizeof(value_read)) != sizeof(value_read)) {
        perror("Failed to read from the device");
        close(fd);
        return -1;
    }

    printf("Read value: 0x%08x\n", value_read);

    // 关闭设备文件
    close(fd);

    return 0;
}
```

这个应用程序执行以下操作:

1. 打开PCIe设备的字符设备文件。
2. 向设备写入一个32位的值。
3. 从设备读取一个32位的值。
4. 关闭设备文件。

要使用这个系统:

1. 编译并加载PCIe驱动程序。
2. 编译应用程序: `gcc -o pcie_app pcie_app.c`
3. 运行应用程序: `./pcie_app`

这个示例展示了基本的读写操作。根据你的具体需求,你可能需要添加更多的功能,如:

- 使用 ioctl 来执行特定的设备操作。
- 实现中断处理来支持异步事件。
- 添加 mmap 支持,允许直接映射设备内存到用户空间。
- 实现 DMA 传输以提高数据传输效率。

记住,在实际应用中,你需要根据具体的PCIe设备规格来调整驱动程序的行为,例如正确设置 BAR 地址,处理设备特定的寄存器等。
 

更多推荐