Linux驱动开发实战之PCIE驱动(一)

以下是针对Linux下PCI设备驱动开发的详细步骤指南及示例代码,适合刚入门的小白逐步学习和实践:


一、开发环境准备

  1. 安装开发工具

    bash 复制代码
    sudo apt install build-essential linux-headers-$(uname -r)
  2. 创建项目目录

    bash 复制代码
    mkdir pci_driver && cd pci_driver

二、编写最简单的PCI驱动框架

1. 创建驱动源码文件 my_pci_driver.c
c 复制代码
#include <linux/module.h>
#include <linux/pci.h>

/* 定义驱动支持的PCI设备ID列表 */
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x1234, 0x5678) }, // 替换为你的设备厂商ID和设备ID
    { 0, }  // 结束标记
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

/* PCI设备探测函数(当系统发现匹配设备时调用) */
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    printk(KERN_INFO "My PCI Driver: Device detected!\n");
    return 0; // 返回0表示成功
}

/* PCI设备移除函数(驱动卸载或设备拔出时调用) */
static void my_pci_remove(struct pci_dev *pdev)
{
    printk(KERN_INFO "My PCI Driver: Device removed.\n");
}

/* 定义PCI驱动结构体 */
static struct pci_driver my_pci_driver = {
    .name     = "my_pci_driver",   // 驱动名称
    .id_table = my_pci_ids,        // 支持的设备ID表
    .probe    = my_pci_probe,      // 探测函数
    .remove   = my_pci_remove,     // 移除函数
};

/* 模块加载函数 */
static int __init my_pci_init(void)
{
    return pci_register_driver(&my_pci_driver);
}

/* 模块卸载函数 */
static void __exit my_pci_exit(void)
{
    pci_unregister_driver(&my_pci_driver);
}

module_init(my_pci_init);
module_exit(my_pci_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple PCI Driver Example");
2. 创建Makefile
makefile 复制代码
obj-m += my_pci_driver.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

三、获取设备厂商ID与设备ID

  1. 查找PCI设备信息

    bash 复制代码
    lspci -nn  # 显示所有PCI设备,例如:
                # 01:00.0 Ethernet controller [0200]: Intel Corporation Device [8086:1533]
    • 8086是厂商ID(Vendor ID)
    • 1533是设备ID(Device ID)
  2. 修改代码中的PCI_DEVICE宏

    c 复制代码
    { PCI_DEVICE(0x8086, 0x1533) }, // 替换为你的设备ID

四、编译与加载驱动

  1. 编译驱动

    bash 复制代码
    make  # 生成my_pci_driver.ko文件
  2. 加载驱动

    bash 复制代码
    sudo insmod my_pci_driver.ko
  3. 查看日志

    bash 复制代码
    dmesg | tail  # 应显示"Device detected!"
  4. 卸载驱动

    bash 复制代码
    sudo rmmod my_pci_driver
    dmesg | tail  # 显示"Device removed."

五、进阶功能实现

1. 启用PCI设备与映射内存
c 复制代码
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret;
    void __iomem *regs;

    // 启用PCI设备
    ret = pci_enable_device(pdev);
    if (ret) {
        printk(KERN_ERR "Failed to enable PCI device\n");
        return ret;
    }

    // 请求内存区域(假设使用BAR0)
    ret = pci_request_regions(pdev, "my_pci_driver");
    if (ret) {
        printk(KERN_ERR "Failed to request regions\n");
        goto err_disable;
    }

    // 映射BAR0到内核虚拟地址
    regs = pci_ioremap_bar(pdev, 0);
    if (!regs) {
        printk(KERN_ERR "Failed to map BAR0\n");
        ret = -ENOMEM;
        goto err_release;
    }

    // 示例:读取第一个寄存器(假设32位)
    u32 value = ioread32(regs);
    printk(KERN_INFO "Register value: 0x%x\n", value);

    // 保存映射地址到设备私有数据
    pci_set_drvdata(pdev, regs);
    return 0;

err_release:
    pci_release_regions(pdev);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

static void my_pci_remove(struct pci_dev *pdev)
{
    void __iomem *regs = pci_get_drvdata(pdev);

    iounmap(regs);
    pci_release_regions(pdev);
    pci_disable_device(pdev);
}
2. 处理中断
c 复制代码
#include <linux/interrupt.h>

static irqreturn_t my_pci_interrupt(int irq, void *dev_id)
{
    struct pci_dev *pdev = dev_id;
    void __iomem *regs = pci_get_drvdata(pdev);

    // 读取中断状态寄存器
    u32 status = ioread32(regs + 0x10);

    if (status & 0x1) {
        printk(KERN_INFO "Interrupt occurred!\n");
        // 清除中断标志
        iowrite32(status & 0x1, regs + 0x10);
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}

// 在probe函数中添加:
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY);
if (ret < 0) {
    printk(KERN_ERR "Failed to allocate IRQ\n");
    goto err_unmap;
}

ret = request_irq(pci_irq_vector(pdev, 0), my_pci_interrupt,
                  IRQF_SHARED, "my_pci_irq", pdev);
if (ret) {
    printk(KERN_ERR "Failed to request IRQ\n");
    goto err_irq;
}

// 在remove函数中添加:
free_irq(pci_irq_vector(pdev, 0), pdev);
pci_free_irq_vectors(pdev);

六、调试技巧

  1. 查看驱动日志

    bash 复制代码
    dmesg -w  # 实时监控内核日志
  2. 检查设备是否被识别

    bash 复制代码
    lspci -v -s 01:00.0  # 替换为你的设备地址
  3. 查看驱动加载状态

    bash 复制代码
    lsmod | grep my_pci_driver

七、注意事项

  1. 内核版本兼容性 :确保头文件路径正确(/lib/modules/$(uname -r)/build)。
  2. 内存安全 :使用ioread32/iowrite32访问寄存器,避免直接指针操作。
  3. 错误处理:所有内核函数调用必须检查返回值!
  4. 测试环境:建议在虚拟机或专用开发板测试,避免主机崩溃。

通过以上步骤,可以逐步构建一个完整的PCI设备驱动。实际开发中需根据具体硬件手册调整寄存器操作和中断处理逻辑。

相关推荐
用户9718356334665 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪6 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言