1.V4L2-PCI驱动程序样例分析
2.Linux下PCI设备驱动开发详解(六)
3.PCI-PCIe设备驱动编写方法论
4.Linux内核:Pci设备驱动——设备枚举
5.Linux os 下PCIe字符设备驱动开发记录
6.PCI设备驱动编写(一)
V4L2-PCI驱动程序样例分析
一、内核简介
本文解析基于Linux内核的内核PCI驱动模板v4l2-pci-skeleton.c,位于linux/samples/v4l目录下。内核通过分析该驱动,内核读者将了解PCI驱动整体结构与V4L2视频捕获驱动的内核开发流程。
二、内核区块兽源码驱动头文件与基础数据结构
驱动程序包含了关键头文件,内核对模块进行了基本功能、内核作者与许可证的内核描述。接着,内核定义了struct skeleton结构体,内核用于封装数据结构、内核锁及过程参数等。内核
同时,内核定义了一个buffer链表,内核用于管理视频数据传输。
三、PCI驱动框架结构
V4L2-pci驱动采用PCI驱动框架实现,通过创建struct pci_driver实例skeleton_driver,并指定其入口函数.probe和.remove,完成驱动封装。
最后,使用module_pci_driver()函数将skeleton_driver导出为模块。
四、.probe详解
.probe作为驱动入口函数,核心操作包括:初始化探查所需结构、启用PCI设备、配置DMA、创建驱动实例、分配中断、设置格式初始化、注册v4l2设备、初始化互斥锁、添加控制器、配置vb2参数与队列、初始化buf链表与自旋锁、注册video_device。
五、.remove详解
.remove作为驱动出口,执行资源释放与清理操作,具体流程包括:反注册设备、mnn源码分析释放分配资源、关闭相关配置。
此框架与流程为V4L2-pci驱动提供了高效、灵活的开发基础。
Linux下PCI设备驱动开发详解(六)
本章及其后续章节将深入探讨通过PCI Express总线实现CPU与FPGA之间数据通信的简单框架,并介绍Linux PCI内核态设备驱动(KMD)的实战开发。
该框架以开源界知名的RIFFA(可重用集成框架,用于FPGA加速器)为基础,这是一个针对FPGA加速器的可重用集成框架,同时也是一款第三方开源的PCIe框架。
该框架需要使用支持PCIe的工作站以及带有PCIe连接器的FPGA板卡。RIFFA支持Windows、Linux操作系统,以及altera和xilinx的FPGA,可以通过c/c++、python、matlab、java等编程语言实现数据的发送和接收。驱动程序可在Linux或Windows系统上运行,每个系统最多支持5个FPGA设备。
在用户端,存在独立的发送和接收端口,用户只需编写少量代码即可实现与FPGA IP内核的通信。
RIFFA使用直接存储器访问(DMA)传输和中断信号传输数据,从而在PCIe链路上实现高带宽,运行速率可达到PCIe链路的饱和点。
开源地址:github.com/KastnerRG/ri...
一、Linux下PCI驱动结构
在《Linux下PCI设备驱动开发详解(四)》中,我们了解到,通常用模块方式编写PCI设备驱动,至少需要实现以下几个部分:初始化设备模块、设备打开模块、数据读写模块、中断处理模块、设备释放模块、设备卸载模块。通常的编写方式如下:
好的,带着这个框架,我们将进入RIFFA框架的driver源代码分析。
二、源码的重要初始化设备模块
我们直接给出源代码:
OK,我们已经看到了几个关键词,驱动程序、字符设备、class、文件节点。在《Linux下PCI设备驱动开发详解(三)》中,我们知道总线、设备、驱动模型:
硬件拓扑描述Linux设备模型中四个重要概念:
三、probe探测硬件设备
这个fpga_probe函数非常重要和关键:
四、写操作
基本的读写操作通过ioctl来调用对应的driver驱动的实现。我们补充一下,ioctl是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设ioctl()命令的方式实现。
直接给出代码:
在处理ioctl_send的时候,我们发现实现用户数据拷贝到内核态之后,调用了chnl_send_wrapcheck,将api层打包过来的参数一一传递过去。
直接给出chnl_send_wrapcheck():
这段代码主要做了一些避免错误的判断,值得一提的就是通过自旋锁避免了多线程错误的判断,其实我们可以知道riffa架构支持多线程,之后调用了chnl_send。
将数据写入指定的FPGA通道。除非配置了非零超时,否则将阻塞,直到所有数据都发送到FPGA。如果超时不为零,则该函数将阻塞,直到发送所有数据或超时毫秒过去。来自bufp指针的用户数据将被发送,最多len字(每个字==位)。通道将被告知预期数据量和偏移量。如果last==1,缺口函数源码则FPGA通道将在发送后将此事务识别为完成。如果last==0,则FPGA通道将需要额外的事务。
成功后,返回发送的字数。出错时,返回负值。
核心思想就是,初始化sg_maps,通过bar空间告知FPGA通道号、长度、大小等信息、使用通用buffer发送数据、更新sg_mapping,最后进入到while(1)的循环函数中。
while(1)大循环,只有当处理完Tx数据完成中断或出错时函数才会返回。在每一轮执行中,首先执行内嵌的小while,在小while中首先读取对应通道上的send消息队列,若返回值为0说明成功出队,小while运行一遍后就会执行下面的代码;若返回值为1说明队列可能是空的,也就是还没有中断到来,此时调用prepare_to_wait函数将本进程添加到等待队列里,然后执行schedule_timeout休眠该进程(有阻塞时间限制),此时在用户看来表现为ioctl函数阻塞等待,但中断还能在后台运行(中断也是一个进程)。
若此时驱动接收到一个该通道的Tx中断,那么在中断回调函数里将中断信息推入消息队列后就会唤醒chnl_send所在的进程。进程唤醒后调用finish_wait函数将本进程pop出等待队列并用signal_pending查看是否因信号而被唤醒,如果是需要返回给用户并让其再次重试。如果不是被信号唤醒,则再去读一下消息队列,此时会将消息类型存入msg_type,消息存入msg中,然后退出小while。
接下来进入一个switch语句,这个switch是根据msg_type消息类型选择处理动作的,即中断处理的下半部。
若执行Tx SG读完成中断,隐藏溯源码则消息类型发送EVENT_SG_BUF_READ,数据填0,其实是没用的数据。在这里如果剩余长度大于0或者剩余溢出值大于0时就会重新执行上一段讲述的过程,即从上一次分配的结尾处再分配SG缓冲区,并发送SG链表给FPGA等等,不过一般不会发送这种情况,除非分配页时的get_user_pages函数锁定物理页出现了问题,少分了页才会出现这样的现象。
然后FPGA就会按SG链表一个一个SG缓存块的进行流式DMA传输,传输完毕后FPGA发送一个Tx数据读完成中断,即EVENT_TXN_DONE消息类型。这里比较好处理,调用dma_unmap_sg取消内存空间的SGDMA映射,然后释放掉页。
五、读操作
读操作和写操作类似,不再详细描述。
函数chnl_recv用于将FPGA发送的数据读到缓冲区内。
首先调用宏DEFINE_WAIT初始化等待队列项;然后把传入的参数timeout换算成毫秒,这个时间是最长阻塞时间。
剩下的就是中断处理过程,等待读完成。
六、销毁/卸载设备
释放设备模块主要是负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好和打开设备模块相反。
本文详细介绍了RIFFA框架的驱动模块,涉及的内容非常多,包括内核页面、中断处理等。
一个驱动的框架主要包括:初始化设备模块、设备打开模块、数据读写模块、中断处理模块、设备释放模块、设备卸载模块。
七、未完待续
《Linux下PCI设备驱动开发详解(七)》将详细分析RIFFA的环形通信队列,最大的好处就是不需要对后续的队列内容进行搬移,可以后续由入队(写入)覆盖。
八、参考资料
blog.csdn.net/mcupro/...
zhuanlan.zhihu.com/p/...
PCI-PCIe设备驱动编写方法论
在进行PCI或PCIe设备驱动编写时,首先需要明确硬件拓扑结构。PCI/PCIe是设备互联的规范,属于总线规范。编写驱动通常涉及两大部分:一是PCI/PCIe接口芯片本身的驱动,二是针对字符设备、块设备、网络设备的驱动。
在Linux操作系统下,PCI/PCIe接口芯片的驱动框架由内核实现,开发者主要需要确定VID/DID/bar的使用情况、中断与DMA的启用等。针对字符设备、块设备、网络设备的驱动,则需根据具体芯片手册,明确时钟、复位、中断、读写操作及涉及的寄存器。
在Linux中,PCI设备驱动程序从总线0开始搜索整个PCI系统,记录所有PCI设备和桥接器,并建立描述系统拓扑层次的数据结构链表。PCI BIOS提供在bios-pci-bios描述中的服务。PCI Fixup是针对特定系统相关的初始化修补代码。
PCI有三种地址空间:I/O空间、内存空间和配置空间。I/O空间和内存空间由设备驱动程序使用,配置空间则由Linux内核的PCI初始化代码使用,用于配置PCI设备,如中断号和I/O或内存基地址。
简而言之,Linux内核的主要任务是枚举和配置PCI设备,通常在内核初始化阶段完成。通过BIOS或内核自身实现对PCI设备的初始化,根据PCI access mode选项提供选择。在枚举和配置过程中,PCI配置寄存器组提供设备配置信息,用于进一步解释和处理。
PCI配置空间头部包含设备类型、性质等信息,用于检测PCI总线上的设备。头部类型分为标准的字节头部和3种类型,用于识别设备。配置寄存器通过统一的入口点访问,由PCI桥提供具体读写操作。对于地址访问,Linux内核保留了特定地址空间,如i结构处理器的0xCF8~0xCFF,用于配置寄存器组的访问。
PCI设备驱动编写时,需要确定是内存访问还是I/O访问,然后按照设备手册实现相应的逻辑。
Linux内核:Pci设备驱动——设备枚举
Linux文件系统详解
Linux进程管理---实时调度
Linux内核内存管理-缺页异常
Linux内核内存管理-brk系统调用
PCI设备驱动简介:PCI设备驱动遵循设备驱动模型,使用设备模型的相应函数。PCI设备被挂载到PCI总线的device队列,而对应的驱动则挂载到pci总线的driver队列。安装PCI设备驱动与USB设备驱动模式相似,主要复杂之处在于如何发现设备并将其添加到PCI设备队列中。
PIC架构概貌:所有根总线链接在pci_root_buses链表中,pci_bus与device之间建立连接,pci_bus与它的下层总线通过children链表关联。每个pci设备的pci_dev->bus指向所属的pci_bus,而pci_dev->bus_list则链接在它所属bus的device链表上。所有pci设备链接在pci_device链表中。
PIC设备的配置空间:每个PCI设备有最多个连续配置空间,包含厂商ID、设备ID、IRQ、设备存储区信息等。通过动态查询PCI设备信息的PCI总线功能,我们可以在x平台上使用保留的0xCF8~0xCFF的8个寄存器进行读写操作。格式包括总线号、设备号、功能号、寄存器号以及有效位。
总线枚举入口分析:PCI代码分为平台相关与平台无关两部分,PCI设备的枚举由pcibios_scan_root()函数完成。在x平台下,这个过程通常在pci_legacy_init()函数中被调用。通过分析pci_direct_init()函数,我们了解到PCI设备的枚举过程主要依赖于pci_direct_probe()函数的返回值,即使用type1配置机制。
PCI设备的枚举过程:pcibios_scan_root()从根总线开始枚举设备,通过pci_scan_bus_parented()函数进一步扫描子总线,最终通过pci_scan_slot()函数扫描每个设备的所有功能号对应的设备。对于每个设备,pci_scan_single_device()函数检查其是否存在,并将设备添加到所属总线的devices链表上。
PCI设备信息的读取与处理:对于常规设备,读取6个存储区间和一个ROM;PCI桥设备包含2个存储区间和一个ROM;Cardbus设备则只有一个存储区间但没有ROM。设备IRQ号、内部存储区间等信息的确定与处理涉及具体寄存器的读取与解析。
PCI桥的处理:在枚举PCI桥时,除了常规设备配置字段,还需处理过滤窗口配置,以控制地址访问方向和启用内存访问、I/O访问功能。PCI桥提供三个过滤窗口,分别用于控制地址访问方向。读取PCI桥配置信息的pci_read_bridge_bases()函数负责完成这一任务。
总结:Linux的PCI架构采用深度优先遍历算法进行设备枚举。通过这一章的分析,读者应能理解PCI架构并解决设备枚举过程中的疑问。后续章节将深入分析其他PCI架构相关问题。
Linux os 下PCIe字符设备驱动开发记录
Linux操作系统下的PCI/PCIe字符设备驱动开发涉及一系列基本概念和流程。首先,PCI/PCIe是一种连接主板与外部设备的总线标准,由电路接口和编程接口构成,其设备配置寄存器遵循严格的规范。操作系统通过枚举设备树发现即插即用设备,无论是PCI还是PCIe,它们都包含配置空间,但PCI是并行,配置空间为B,而PCIe则是串行,配置空间扩大至4K,且支持IO、内存空间的分离。
在系统启动阶段,firmware或Linux内核会枚举PCI(或PCIe)设备,分配安全的IO和存储空间。成功枚举后,我们可以通过lspci工具查看设备信息,以确定设备类型,如本文中涉及的自研串口设备,属于字符设备。PCIe设备配置空间利用前B,如图所示,部分硬件连接部分未使用。
Linux kernel中的PCIe驱动架构基于字符设备驱动模型,关键的配置空间如command寄存器和BAR地址寄存器分别控制设备的I/O访问、内存访问和中断。BAR地址映射PCIe设备的内部空间,CPU通过访问BAR来读取设备空间,前提是在配置空间区域。
驱动的编译和加载是开发流程的重要环节,根据开发条件选择合适的编译方式,如make load和make install。通过查看syslog或dmesg日志,确认驱动是否成功加载,同时可以检查/proc/iomem来观察系统RAM中PCIe设备的空间分配。在用户空间调试阶段,我们会编写内存读写工具,验证驱动的正确性和适用性,进行初步的逻辑测试。
PCI设备驱动编写(一)
编写PCIe驱动程序的关键步骤主要围绕模仿、匹配、启用设备、配置空间访问以及驱动程序的注册和注销。
模仿是开始编写PCIe驱动的基础。通过下载Linux内核源码,找到相关PCIe驱动作为模板,你可以学习到如何构建自己的驱动。
在匹配PCI设备时,需了解厂商ID、子厂商ID、设备ID和子设备ID。这些ID能确保驱动与特定PCI设备正确匹配。在Linux中,使用设备树查找并加载适当的驱动程序,确保PCI设备被操作系统识别并使用。
启用设备的步骤在驱动程序的probe函数中完成。调用pci_enable_device函数唤醒设备,并分配中断线和I/O区域,确保设备在驱动程序中被正确启用,以便后续访问资源。
访问配置空间对驱动程序至关重要。配置空间是设备在系统中的映射位置,驱动程序通过读写配置空间来获取设备的详细信息。配置空间的访问方式依赖于CPU与PCI控制器的交互,Linux提供标准接口供驱动程序使用。
配置空间通过8位、位或位数据传输进行访问。Linux定义了相关函数原型,提供访问PCI设备配置空间的接口,驱动程序借此找到设备映射位置,与设备通信和控制。
在完成驱动程序的开发后,需要进行注册和注销。注册时,需将驱动程序添加到系统中;注销时,需从系统中移除驱动程序,确保资源的释放和系统的稳定性。
总结而言,编写PCIe驱动程序需要遵循模仿、匹配、启用、配置空间访问、注册和注销等步骤。通过理解这些关键点,并借助Linux内核源码作为模板,你将能够构建出功能完善的PCIe驱动程序。