当前位置: 首页 > news >正文

PCI总线驱动开发全解析

深入理解 PCI 总线与驱动:从理论到实践

1. 什么是 PCI 总线?

PCI 是一种高性能的、地址和数据复用的、即插即用的局部总线标准。它被设计用来连接计算机主板上的CPU、内存与各种外设扩展卡。

1.1 PCI 总线的主要特点

  • 即插即用:系统启动时自动配置 PCI 设备,分配资源(如内存地址、中断号),无需用户手动设置跳线。
  • 高带宽:32位/64位数据宽度,时钟频率通常为33MHz或66MHz。
  • 独立于处理器:不依赖于特定型号的 CPU。
  • 地址/数据复用:同一组信号线先传输地址,后传输数据,节省引脚。
  • 总线枚举:系统可以通过遍历总线来发现所有连接的设备。

2. PCI 设备识别与配置空间

每个 PCI 设备都有一个唯一的标识符,并通过一个称为 配置空间 的寄存器组来进行管理和通信。

2.1 设备识别

一个 PCI 设备由三个关键标识符唯一确定:

  • Vendor ID: 16位,由 PCI SIG 分配给设备制造商。
  • Device ID: 16位,由制造商分配给特定设备。
  • Class Code: 24位,表示设备的类别(如网络控制器、显示控制器等)。

2.2 配置空间

这是一个256字节的标准化结构,包含了设备的所有关键信息。前64字节是标准化的头部,其余为设备相关的配置寄存器。

重要寄存器包括:

  • Vendor ID / Device ID: 设备标识。
  • BAR: 最多6个 基地址寄存器,用于告知系统该设备需要多少内存或I/O空间,以及系统为其分配的实际基地址。这是驱动与设备通信的基石。
  • Interrupt Line: 系统分配给设备使用的中断号。
  • Interrupt Pin: 设备使用哪个硬件中断引脚(A, B, C, D)。

当系统启动时,BIOS或操作系统会遍历PCI总线,读取每个设备的配置空间,为其分配资源,并填充BAR寄存器。

3. Linux 内核中的 PCI 驱动框架

在 Linux 中,编写一个 PCI 驱动主要涉及以下步骤:

  1. 定义 PCI 设备ID表: 告诉内核这个驱动支持哪些设备。
  2. 注册驱动: 向内核注册一个 pci_driver 结构体。
  3. 实现 Probe 函数: 当内核发现一个设备与驱动匹配时,会调用此函数。在这里进行设备初始化。
  4. 实现 Remove 函数: 当设备被移除或驱动卸载时调用,进行资源清理。
  5. 设备操作: 在 probe 中,通常会:
    • 启用 PCI 设备。
    • 获取 BAR 映射的地址。
    • 申请中断请求线。
    • 创建设备节点(如 /dev/xxx)以供用户空间访问。

4. 代码示例:一个简单的 PCI 驱动骨架

下面是一个最简单的 PCI 驱动代码,它不实现任何具体功能,但完整展示了驱动的基本结构。

// simple_pci_driver.c
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/uaccess.h>// 1. 定义该驱动支持的设备ID表
static const struct pci_device_id my_pci_ids[] = {{ PCI_DEVICE(0x1234, 0x1111) }, // 假设支持 Vendor ID 0x1234, Device ID 0x1111 的设备{ 0, } // 必须以全0条目结束
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);// 设备私有数据结构(可选)
struct my_device_priv {void __iomem *bar0; // 用于映射BAR0的虚拟地址int irq_line;       // 中断号
};// 3. Probe 函数:设备被发现并匹配时调用
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{int ret;struct my_device_priv *priv;printk(KERN_INFO "My PCI Driver: Device found! Probing...\n");// 3.1 启用PCI设备ret = pci_enable_device(pdev);if (ret) {dev_err(&pdev->dev, "Failed to enable PCI device\n");return ret;}// 3.2 为设备申请DMA掩码(如果设备支持DMA)ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));if (ret) {dev_err(&pdev->dev, "No suitable DMA available\n");goto err_disable_device;}// 3.3 分配设备私有数据priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);if (!priv) {ret = -ENOMEM;goto err_disable_device;}pci_set_drvdata(pdev, priv); // 将私有数据与pci_dev关联// 3.4 获取并映射BAR0(假设是内存区域)priv->bar0 = pci_iomap(pdev, 0, 0); // 映射BAR0的全部长度if (!priv->bar0) {dev_err(&pdev->dev, "Cannot map BAR0\n");ret = -ENOMEM;goto err_disable_device;}printk(KERN_INFO "My PCI Driver: BAR0 mapped at virtual address %p\n", priv->bar0);// 3.5 申请中断ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);if (ret < 0) {dev_err(&pdev->dev, "Failed to allocate IRQ vectors\n");goto err_iounmap;}priv->irq_line = pci_irq_vector(pdev, 0);ret = devm_request_irq(&pdev->dev, priv->irq_line, my_interrupt_handler,IRQF_SHARED, "my_pci_driver", pdev);if (ret) {dev_err(&pdev->dev, "Failed to request IRQ %d\n", priv->irq_line);goto err_free_irq_vectors;}printk(KERN_INFO "My PCI Driver: IRQ %d requested successfully\n", priv->irq_line);// 到这里,设备基本初始化完成// 可以继续:初始化硬件、创建字符设备、sysfs节点等...printk(KERN_INFO "My PCI Driver: Probe successful\n");return 0;// 错误处理:按申请资源的逆序释放资源
err_free_irq_vectors:pci_free_irq_vectors(pdev);
err_iounmap:pci_iounmap(pdev, priv->bar0);
err_disable_device:pci_disable_device(pdev);return ret;
}// 4. Remove 函数:设备移除或驱动卸载时调用
static void my_pci_remove(struct pci_dev *pdev)
{struct my_device_priv *priv = pci_get_drvdata(pdev);printk(KERN_INFO "My PCI Driver: Removing device\n");// 释放资源,顺序与probe相反free_irq(priv->irq_line, pdev);pci_free_irq_vectors(pdev);pci_iounmap(pdev, priv->bar0);pci_disable_device(pdev);
}// 简单的中断处理函数
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{struct pci_dev *pdev = dev_id;printk(KERN_DEBUG "My PCI Driver: Interrupt occurred!\n");// 读取设备状态寄存器,确认中断,处理数据...return IRQ_HANDLED;
}// 2. 定义 pci_driver 结构体
static struct pci_driver my_pci_driver = {.name = "my_simple_pci_driver",.id_table = my_pci_ids,   // 设备ID表.probe = my_pci_probe,    // 探测函数.remove = my_pci_remove,  // 移除函数
};// 模块加载和卸载
module_pci_driver(my_pci_driver); // 这个宏简化了注册和注销操作MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example PCI driver");

代码关键点解释:

  1. pci_device_id: 定义了驱动支持的设备列表。PCI_DEVICE 宏用于创建一个 pci_device_id 条目。
  2. pci_driver: 这是驱动的核心结构,向内核注册了驱动名称、ID表、probe和remove函数。
  3. my_pci_probe
    • pci_enable_device: 启用设备,使其可以响应PCI访问。
    • pci_iomap: 将设备的物理BAR地址映射到内核的虚拟地址空间,这样驱动就可以通过指针(如 priv->bar0)直接读写设备寄存器。
    • pci_alloc_irq_vectorsdevm_request_irq: 申请中断向量并注册中断处理函数。
    • pci_set_drvdata: 将自定义的私有数据结构 my_device_privpci_dev 关联,方便在其他函数中获取。
  4. my_pci_remove: 负责清理所有在 probe 中分配的资源,防止内存泄漏。
  5. my_interrupt_handler: 当设备触发中断时,内核会调用此函数。

5. 编译与测试

  1. 编译: 需要一个合适的内核 Makefile

    obj-m += simple_pci_driver.oKDIR := /lib/modules/$(shell uname -r)/buildall:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean
    

    使用 make 命令编译。

  2. 查看PCI设备: 在加载驱动前,可以使用 lspci -vlspci -xxx 命令查看系统中的PCI设备和它们的配置空间。

  3. 加载驱动: 使用 sudo insmod simple_pci_driver.ko 加载模块。使用 dmesg 查看内核日志,应该能看到 “Probe successful” 等信息。

  4. 卸载驱动: 使用 sudo rmmod simple_pci_driver 卸载模块。

总结

理解 PCI 驱动关键在于理解 总线枚举配置空间资源分配 的概念。Linux 内核提供了完善的 PCI 核心层(drivers/pci/),驱动开发者只需遵循固定的框架:

  1. 用ID表声明支持的设备。
  2. probe 中启用设备、映射资源、注册中断。
  3. remove 中妥善清理。

这个骨架代码是理解更复杂PCI驱动(如网卡、显卡驱动)的起点。在实际开发中,你还需要在 probe 之后实现具体的设备功能,例如实现 file_operations 来提供用户空间接口。

http://www.dtcms.com/a/582545.html

相关文章:

  • 做网站数据库表设计Wordpress企业主题XShuan
  • 买完域名网站怎么设计房产中介网站开发模板
  • AVL树实现
  • Vue 组件插槽的深层传递
  • HENGSHI SENSE 6.1 发布,从 ChatBI 到 Agentic Analytics
  • 网站 哪些服务器wordpress新编辑器分类
  • 网站进度条源代码juqery-ui泰安网站建设公司
  • 11月7日星期五今日早报简报微语报早读
  • 网站维护一般都是维护什么公司注册网站需要提供什么文件
  • 网站开发现在是热门专业吗福建网站建建设
  • wordpress 首页无法访问seo信息编辑招聘
  • Nginx配置DNS缓存
  • 生信工作流框架搭建 | 01-nextflow、snakemake、wdl 对比测试
  • Windows 下 ROS/ROS2 开发环境最优解:WSL 比直接安装、虚拟机、双系统更优雅!
  • (Linux (6):从包管理到工具探索,构建系统操作基础认知)
  • 网站建设哪家专业为什么电脑打不开网页
  • wordpress 4.7.9seo推广有哪些
  • 读取证书问题so调用问题
  • Jottings-Lishaozhuo 2025.11.7
  • 聊聊怎么更好去设计数据库表
  • STM32H743-ARM例程40-U_DISK_IAP
  • 网站开发 实时更新做网站做的好的公司有哪些
  • 专门做酒店设计的网站源码屋整站源码
  • Memory MCP(记忆服务器)
  • Java基础 | 基本类型与引用类型使用规范
  • 香港1核2G云服务器当网站服务器够用不?
  • 用Python来学微积分36-牛顿 - 莱布尼茨公式的深度解析
  • 网站建设规章制度软件app开发需要多少钱
  • 网站建设外出考察信息家在深圳房网论坛首页
  • 测试开发工程师面经准备(sxf)