linux pcie驱动,pcie驱动详解

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字(每个字==32位)。通道将被告知预期数据量和偏移量。如果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/...

Linux下PCI设备驱动开发详解(七)

本章节开始,我们专注于通过PCI Express总线实现CPU与FPGA间的数据通信简易框架。该框架即RIFFA(reusable integration framework for FPGA accelerators),一个针对FPGA加速器的可重用性集成框架,同时也是第三方开源PCIe框架。RIFFA要求用户拥有支持PCIe的工作站和带有PCIe连接器的FPGA板卡。该框架兼容多种操作系统,如Windows、Linux,以及硬件平台,如Altera和Xilinx。用户可使用多种编程语言,包括C/C++、Python、MATLAB、Java等,实现FPGA数据的发送与接收。每个系统最多支持5个FPGA设备。

框架在用户端提供独立的发送和接收端口,用户仅需编写少量代码即可与FPGA IP内核进行通信。RIFFA使用直接存储器访问(DMA)传输和中断信号传输数据,以实现高带宽的PCIe链路运行,达到链路饱和点。

本章节将深入探讨消息队列的实现,特别是与Riffa相关的riffa.c和riffa.h文件。Riffa的消息队列是为了内核使用而设计的同步工具,用于协调中断与进程。了解消息队列之前,我们先回顾队列(queue)的概念,它是一种数据结构,支持从左侧插入元素(入队)和从队头获取元素(出队)。

消息队列,作为独立的中间件产品,与队列类似但功能更强大,用于在不同系统组件间传输数据。接下来,我们将探讨消息队列在解耦、异步处理和削峰效应中的应用。

消息队列能够显著降低系统组件之间的耦合度。以系统A为例,假设它需要通知系统B和系统C在生成数据后做出响应。通过引入消息队列,系统A只需将数据写入队列,而无需直接调用系统B或C的接口。即使新增系统D或系统C不再需要数据,系统A无需进行任何更改,这极大简化了系统的维护和扩展。

异步处理则是通过消息队列实现的另一种优势。在同步模型中,用户操作需要等待系统完成所有处理步骤,而在异步模型下,操作可立即返回用户响应,之后系统内部处理数据。这提高了用户体验,减少了等待时间。

RIFFA驱动中也采用了消息队列技术。消息队列在Linux应用编程中通常被称为无名管道,实际上是一个FIFO(先进先出)数据结构。它的主要作用在于同步和数据传递,同时具备阻塞功能。尽管Riffa实现的消息队列本身不支持阻塞,但可以通过阻塞I/O操作来实现其完整功能。

消息队列的数据结构和操作函数定义在circ_queue.c与circ_queue.h文件中。其中,circ_queue.h文件定义了消息队列的数据结构,并使用了原子变量来处理读写指针的竞争问题。同时,提供了消息队列初始化、读取和写入的函数。通过这些函数,可以实现高效的数据传输,满足不同应用场景的需求。

至此,本章节详细介绍了RIFFA框架中的消息队列实现,并讨论了其在解耦、异步处理和提高系统性能方面的作用。下一章节将深入探讨RIFFA在用户态的使用方式。

Linux下PCI设备驱动开发详解(五)

本系列文章旨在深入解析Linux下PCI设备驱动开发过程,本文作为五部曲的第五章,将详细探讨通过PCI Express总线实现CPU与FPGA间数据通信的简单框架。这一框架即RIFFA(reuseable integration framework for FPGA accelerators),一个由第三方开源的PCIe框架,支持Windows、Linux环境,适用于Altera和Xilinx的FPGA板卡。

RIFFA框架要求具备一个支持PCIe的工作站与FPGA板卡,能够通过PCIe连接实现数据交互。该框架支持多种编程语言,如C/C++、Python、MATLAB、Java,可实现数据发送与接收。每一系统最多支持5个FPGA设备。

框架的核心在于简化硬件接口,利用FIFO进行数据读取与存储。数据传输由RX和TX DMA引擎模块通过分散聚合方法执行,其中RX引擎接收上位机数据,完成后传递给通道模块;TX引擎则收集通道模块的数据,打包发送至PCIe端点。

在软件层面,PC接收FPGA数据时,调用库函数fpga_recv,然后通过FPGA启动操作。用户应用程序线程进入内核驱动程序,接收FPGA的读请求,分包发送数据,并在未收到请求时等待响应。启动发送函数后,服务器建立数据散列收集元素列表,将地址、长度等信息写入共享缓冲区,用户应用程序将这些信息传递给FPGA,后者读取散列收集数据,发出地址对应的数据写入请求。如果列表中有多个地址,FPGA将通过中断发出相应请求。

传输过程遵循直接存储器访问(DMA)和中断信号传输,以实现PCIe链路的高带宽,运行速率可达链路饱和点。驱动程序在开始前需要调用pci_present()检查PCI总线支持情况,通过pci_register_driver()函数注册驱动程序,并提供“demo_pci_driver”结构,其中的probe探测例程负责硬件检测。

文章接下来将对用户逻辑、PCIe硬IP、TX/RX引擎以及RIFFA模块进行深入分析,结合理论基础、实际操作与源代码,逐步构建对整个框架的理解。首先,我们将从FPGA xilinx integrated block for PCI express出发,探讨其配置与功能。

在PCIe硬IP部分,我们将关注配置参数,如AXI总线时钟、总线接口位宽、ID设定、厂商ID与设备ID、基类菜单、bar空间配置等。此外,我们将详细分析中断配置、IP核接口参数,以及顶层代码接口,理解其在设计架构中的作用。

接下来,我们深入探讨tx_engine与rx_engine模块,这些模块负责转换axis数据与tlp数据。文章将提供源代码示例,展示这些核心模块的实现方式,以及如何通过C_NUM_CHNL、C_PCI_DATA_WIDTH、C_LOG_NUM_TAGS配置通道、数据位宽与tag个数。

最后,我们将介绍user logic部分,即如何使用CHNL_TX_和CHNL_RX_接口,实现数据的发送与接收。此外,文章将总结框架的结构与功能,以及如何在Linux环境下开发、安装驱动程序。

敬请期待Linux下PCI设备驱动开发详解(六),我们将深入探讨内核态驱动的开发与实现,以完成这一系列文章的内容。

阅读剩余
THE END