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设备驱动。实际开发中需根据具体硬件手册调整寄存器操作和中断处理逻辑。

相关推荐
艾伦_耶格宇28 分钟前
【ACP】阿里云云计算高级运维工程师--ACP
运维·阿里云·云计算
一位摩羯座DBA1 小时前
Redhat&Centos挂载镜像
linux·运维·centos
学习3人组1 小时前
CentOS配置网络
linux·网络·centos
weixin_307779132 小时前
Hive集群之间迁移的Linux Shell脚本
大数据·linux·hive·bash·迁移学习
漫步企鹅2 小时前
【蓝牙】Linux Qt4查看已经配对的蓝牙信息
linux·qt·蓝牙·配对
cui_win2 小时前
【网络】Linux 内核优化实战 - net.core.flow_limit_table_len
linux·运维·网络
梦在深巷、2 小时前
MySQL/MariaDB数据库主从复制之基于二进制日志的方式
linux·数据库·mysql·mariadb
风清再凯2 小时前
自动化工具ansible,以及playbook剧本
运维·自动化·ansible
深圳安锐科技有限公司2 小时前
深圳安锐科技发布国内首款4G 索力仪!让斜拉桥索力自动化监测更精准高效
运维·安全·自动化·自动化监测·人工监测·桥梁监测·索力监测
猫头虎3 小时前
猫头虎 AI工具分享:一个网页抓取、结构化数据提取、网页爬取、浏览器自动化操作工具:Hyperbrowser MCP
运维·人工智能·gpt·开源·自动化·文心一言·ai编程