那些协议之-Pci_e驱动示例 本篇知乎主要记录在linux环境下开发一个基本的PCIe驱动程序框架,其主要需要包含以下模块:编写设备描述:根据PCIe设备规范,编写设备描述,包括设备的厂商ID、设备ID、供应商特定的信息等。分配和注册驱动程序:分配一个驱动程序对象,并在系统中注册该驱动程序。这使得操作系统可以识别和加载驱动程序。初始化设备:在驱动程序中编写设备初始化函数,初始化PCIe设备的各种配置和状态。分配和映射内存资源:如果设备使用了内存空间,需要分配和映射内存资源,以便驱动程序可以访问并与设备进行数据交换。注册中断处理程序:如果设备支持中断,驱动程序需要注册中断处理程序,以便在设备触发中断时进行相应的处理。实现具体的设备操作函数:编写设备操作函数,例如读取设备寄存器、写入设备寄存器、发送命令等,以与设备进行通信和控制。 设备描述需要用到PCI的配置寄存器。
PCI兼容配置寄存器空间 上图展示了PCI配置寄存器空间,每个PCI设备都至少有256字节的地址空间,前64字节是标准化的,其余是设备相关的。当引入PCIe之后,最初始的256byte配置空间已经不足以放下所有新需要的配置了。因此配置空间的大小从原先的256Byte扩展至了4KByte,增加了可选扩展寄存器。通常,随设备一同发布的技术文档会详细描述已支持的寄存器。本文这里关心的是如何标识设备以及驱动程序如何查询设备,因此只介绍几个常用的寄存器。 用三五个PCI寄存器便可标识一个设备:vendorID、deviceID和class是常用的三个寄存器。每个PCI设备制造商会将正确的值赋予这三个只读寄存器,驱动程序可利用它们查询设备。此外,subsystem vendorID和subsystem deviceID字段用来进一步区分相似的设备。 vendorID用于标识硬件制造商。例如,每个Intel设备被标识为同一个厂商编号,即0x8086。PCISpecialInterest Group维护有一个全球的厂商编号注册表,制造商必须申请一个唯一编号并赋于它们的寄存器。 deviceID由制造商选择;无需对设备ID进行官方注册。该ID通常和厂商ID 配对生成一个唯一的 32 位硬件设备标识符。我们使用签名(signature)一词来表示一对厂商和设备ID。设备驱动程序通常依靠于该签名来识别其设备;可以从硬件手册中找到目标设备的签名值。 class用于标识每个外部设备属于某个类 (class)。例如,“ethernet(以太网)”和“token ring(令牌环)”是同属“network(网络)”组的两个类,而“serial(串行)”和“parallel(并行)”类同属“communication(通信)”组。某些驱动程序可支持多个相似的设备,每个具有不同的签名,但都属于同一个类;这些驱动程序可依靠 class 寄存器来识别它们的外设。 我们可以通过执行lspci -nn指令来查看系统中pci设备的相关信息,输出如下图所示。
lspci命令输出结果 以图中第一行输出为例,00:00.0表示此设备位于PCI总线上的位置,Host bridge[0600]表示该设备的类型是主机桥。Intel Corporation 12th Gen Core Processor Host Bridge/DRAM Registers表示该设备是Intel12代处理器的主桥,8086:4668则分别指明了vendorID和deviceID,rev代表了版本信息。 除此之外,lspci指令还可以更详细的展示设备信息、也可以查看PCI设备的配置空间,具体用法请参见手册。 在pci驱动程序中,pci_dev、pci_driver、pci_device_id等结构体起着非常关键的作用。pci_device_id用于定义该驱动程序支持的不同类型的PCI设备列表。 pci_driver结构体用来向PCI核心描述PCI驱动程序,主要定义了驱动名称、探测函数、移除函数等变量和函数指针。所有的PCI驱动程序都应该创建该结构体并注册到内核中。该结构体定义在include/linux/pci.h中,内容如下 int (*probe) (struct pci_dev *dev, const struct pcideviceid *id)定义了指向PCI驱动程序中的探测函数的指针。当PCI核心有一个它认为驱动程序需要控制的pci_dev时,就会调用该函数。PCI核心用来做判断的pci_device_id 指针也被传递给该函数。如果 PCI驱动程序确认传递给它的pci_dev,则应该恰当地初始化设备然后返回0。如果驱动程序不确认该设备,或者发生了错误,它应该返回一个负的错误值。void (*remove) (struct pci_dev *dev)指向一个移除函数的指针,当pci_dev 被从系统中移除,或者PCI驱动程序正在从内核中卸载时,PCI核心调用该函数。 初始化一个pci_driver结构体应当重点name、id_table、probe、remove等字段。为了把struct pci_driver注册到 PCI核心中,需要调用以struct pci_driver指针为参数的pci_register_driver函数。通常在 PCI驱动程序的模块初始化代码中完成该工作。 pci_dev结构体用来描述一个pci设备,包括设备厂商id、设备id、设备类别码、设备所在总线等用来描述设备的信息。pci_dev结构体部分内容如下,完整定义请查看include/linux/pci.h文件。 pci_device_id结构体用于定义该驱动程序支持的不同类型的PCI设备列表,该结构体的一个数组id_table就被用在pci_driver中。通过将pci_device_id导入到用户空间中,热插拔和装载模块可以知道什么模块用于什么设备,宏MODULE_DEVICE_TABLE可以完成这项工作。pci_device_id结构体定义在include/linux/mod_devicetable.h文件中,内容如下。 下面以字符设备驱动程序为例,展示PCIe驱动程序的框架,具体还需要根据实际硬件和业务进行配置。 编译生成以下文件
通过sudo insmod hello_pcie.ko指令将模块加载到内核中。加载时可能会遇到由于自定义驱动没有签名导致加载模块失败的问题,这是因为从内核版本4.4.0-20开始,强制要求在启用安全启动的情况下不允许运行未签名的内核模块。生成对应的签名证书和私钥并且导入系统可以解决该问题。 通过lsmod指令可以确认该驱动模块以及加入到内核中。
通过modinfo指令我们可以查看该模块的相关信息。
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/63120.html