1.Linux下PCI设备驱动开发详解(三)
2.PCI设备驱动编写(一)
3.Linux平台下pci总线驱动
4.Linux的驱驱动PCI驱动讲解(通俗易懂!)
5.PCI-PCIe设备驱动编写方法论
6.Linux下PCI设备驱动开发详解(六)
Linux下PCI设备驱动开发详解(三)
在深入PCIe硬件和软件开发之前,动源理解几个关键概念至关重要,驱驱动它们能帮助我们把握整个PCIe异构系统的动源工作原理,以及驱动和设备在系统中的驱驱动定位。PCIe软硬异构系统基础
应用程序通过文件系统访问硬件,动源html目录列表源码通常通过读写文件操作抽象设备。驱驱动非内核功能的动源库函数直接实现,涉及硬件操作的驱驱动则通过系统调用,由内核处理。动源 Linux将硬件分为字符设备、驱驱动块设备和网络设备,动源设备通过文件名(设备文件)和设备号(主、驱驱动从设备号)区分。动源设备文件以文件形式在/dev目录下,驱驱动通过文件操作如open、read、write、close进行硬件操作。 驱动程序作为内核模块,不包含main()函数,由初始化函数启动,应用程序与驱动的工作模式不同,前者在用户态,后者在内核态。 设备驱动由总线、设备和驱动组成,总线作为硬件连接的桥梁,设备通过总线注册自身,匹配合适的驱动进行通信。总线、设备和驱动的协作
系统启动时自动创建总线目录。总线驱动通过bus_register()进行注册,生成设备和驱动文件夹,再通过device_register()和driver_register()进行设备和驱动的具体注册。 总线初始化和注册是内核初始化的一部分,新总线的添加通常由系统自动处理,驱动的probe函数在匹配成功后执行初始化。 设备和驱动的实例展示了PCI设备和其对应的驱动注册过程。 Linux设备驱动模型的核心在于总线、设备和驱动的紧密配合,它们共同构建了系统与硬件的stm32 modbus源码交互机制。后续文章将深入探讨实际PCI设备驱动的开发细节。参考资料:
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驱动程序需要遵循模仿、android v4 源码匹配、启用、配置空间访问、注册和注销等步骤。通过理解这些关键点,并借助Linux内核源码作为模板,你将能够构建出功能完善的PCIe驱动程序。
Linux平台下pci总线驱动
PCI总线,一种高性能局部总线,为满足外设间及外设与主机间的高速数据传输而诞生。在PCI系统中,PCI控制卡安装于PCI插槽内,支持位或位数据交换,实现智能PCI辅助适配器与CPU的并行任务执行。PCI允许多路复用,允许多个电子信号同时存在于总线上。每个设备配置有地址配置寄存器,初始化时配置总线地址,CPU可访问设备资源。PCI标准规定配置寄存器组最多个连续字节,包括头部部分,用于设备类型、型号和厂商信息。
PCI总线架构中,根总线链接在pci_root_buses链表,pci_bus ->device链表连接所有设备,pci_bus->children链表连接下层总线,pci_dev->bus指向所属pci_bus,pci_dev->bus_list链接在bus的device链表上。所有设备链接在pci_device链表中。
在Linux环境下,PCI驱动包含两部分:PCI总线驱动及设备驱动,如字符设备、网络设备、tty设备、音频设备等。核心是pci_driver,负责资源申请、注册设备,并在探测函数中完成初始化。以三星平台s3cXX为例,android7.0源码下载代码包括中断处理、打开、关闭和读取文件操作。
PCI驱动的代码模型包括设备支持项、中断处理程序、打开和关闭文件操作,以及文件操作结构体。misc设备注册用于管理PCI设备。设备初始化和卸载涉及使能、设置主控、注册杂项设备等步骤。
PCI I/O和PCI内存地址用于PCI设备与Linux核心驱动程序之间的通讯。例如,DEC快速以太网设备的内部寄存器映射到PCI I/O空间,Linux驱动通过读写这些寄存器控制设备。PCI视频卡使用大量PCI内存存储视频信息。
在系统初始化阶段,不允许访问PCI地址空间,直到通过PCI配置头中的命令域打开这些空间。重要的是,Linux设备驱动仅读写PCI I/O和PCI内存地址,无需直接访问配置空间,因为当系统初始化完成,设备地址空间已配置在总线上,驱动通过总线地址访问设备。
以上内容仅为PCI部分简介,深入了解可参考《linux内核情景分析》PCI驱动一章,内容详尽。
Linux的PCI驱动讲解(通俗易懂!)
Linux PCI驱动程序详解:从入门到核心
在Linux系统中,PCI设备驱动程序是关键,它连接着操作系统和PCI总线上的硬件设备。PCI设备有三种地址空间:I/O空间、存储空间和配置空间。内核负责初始化这些设备,包括分配中断号和I/O基址,并在/proc/pci中列出设备信息。
驱动程序通常使用结构体表示设备,通过次设备号区分不同设备。核心数据结构如pci_driver和pci_dev在驱动程序中扮演重要角色,分别用于设备识别和详细描述硬件信息。apk可以看到源码吗编写PCI驱动程序时,需要实现初始化、打开设备、数据操作、中断处理、释放设备和卸载驱动等模块,遵循一个相对固定的框架。
初始化模块需检查PCI总线支持和设备存在性;打开设备涉及中断申请和资源控制;数据读写模块通过demo_fops提供的接口,如demo_ioctl,提供硬件控制接口;中断处理模块负责中断识别和处理;释放设备和卸载模块则是与初始化相反的操作。
PCI总线的兼容性和Linux的发展为设备驱动编写提供了便利,尤其是在内核2.4之后,新添加的PCI驱动结构简化了设备检测和卸载。理解这些关键概念后,你将能够更好地掌握Linux下的PCI驱动开发。
关于时间管理,Linux使用系统定时器和实时时钟。系统定时器每秒产生HZ次数的中断,2.6内核后默认频率提高到Hz,提供了更高的精度。jiffies变量记录自系统启动以来的时钟滴答数,溢出问题通过time_after等宏巧妙处理。实时时钟用于持久存储系统时间,内核启动时读取它初始化wall time。
此外,Linux的时钟中断和定时器管理机制是操作系统核心功能,包括更新时间、调度任务、超时处理等。了解这些机制有助于你编写更高效的PCI驱动程序,同时避免时间相关的潜在问题。
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设备驱动开发详解(六)
本章及其后续章节将深入探讨通过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/...