linux 设备模型 基于Linux的小项目

大家好,今天给各位分享linux 设备模型的一些知识,其中也会对基于Linux的小项目进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!

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设备有最多256个连续配置空间,包含厂商ID、设备ID、IRQ、设备存储区信息等。通过动态查询PCI设备信息的PCI总线功能,我们可以在x86平台上使用保留的0xCF8~0xCFF的8个寄存器进行读写操作。格式包括总线号、设备号、功能号、寄存器号以及有效位。

总线枚举入口分析:PCI代码分为平台相关与平台无关两部分,PCI设备的枚举由pcibios_scan_root()函数完成。在x86平台下,这个过程通常在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下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设备驱动程序——bus

总线(bus)在Linux内核中扮演着统一管理所有设备的角色,它将硬件总线或虚拟总线抽象为一种设备模型,使得系统能够以统一的方式来识别和控制各种设备。Linux系统中,设备通常会被挂载在总线上,形成一种抽象的设备树结构,这种设计有助于简化设备管理和驱动程序的开发。

总线工作流程主要包含两部分:driver(驱动程序)和device(设备)。driver实现了与特定类型设备的驱动程序实现,而device则向系统注册所需资源。当添加新的driver到总线上时,系统会调用总线的match函数,尝试匹配对应的device(驱动程序)。如果匹配成功,将调用probe函数,实现设备初始化、配置以及生成用户空间的文件接口。

以AT24CXX(一种常用的存储设备)为例,对于同系列的设备(如AT24C01、AT24C02),它们的操作方式相似,但容量不同。因此,没有必要为每种型号分别编写驱动程序。通过编写一份兼容所有AT24CXX设备的驱动程序,并根据型号参数进行调整,可以实现对这些设备的统一管理,大大提高了复用性和内存空间的节省。

在Linux驱动管理模型中,设备被注册在总线上,用户需要使用特定型号的硬件时,只需构建与型号对应的device并注册到总线上。总线的match函数匹配上后,调用probe函数即可完成设备初始化和用户空间文件接口的注册。

Linux中的总线由struct bus_type结构体进行描述,包含了总线名称、设备名、设备结构、匹配回调函数、事件回调函数、初始化和卸载函数等。这一结构使得总线能够管理注册的设备和驱动程序,并通过match函数实现设备和驱动程序的自动匹配。

总线注册过程主要涉及物理总线(如SPI、I2C)和虚拟总线(如platform)的初始化。物理总线通过postcore_initcall()将init函数注册到系统中,而虚拟总线在系统初始化时直接调用init函数。物理总线和虚拟总线分别包含特定的struct bus_type描述结构体,实现各自的注册和初始化。

当新设备或驱动程序被注册到总线上时,bus_register()接口负责初始化相关资源。以I2C为例,通过i2c_new_device接口添加设备,并调用device_register和bus_add_device函数将设备添加到总线上。同时,I2C驱动程序的注册与匹配过程通过i2c_driver_register函数实现。

设备与驱动程序的匹配主要通过设备和驱动程序中的属性进行,如设备名称、ID表、设备树转换过程中的兼容性属性等。总线的match函数负责实现这一匹配过程,确保正确的设备和驱动程序能够自动关联。

Linux的总线机制为设备管理和驱动程序开发提供了强大的支持,简化了系统对多种硬件设备的管理。通过上述介绍,我们对Linux内核中的总线机制有了更深入的理解。

阅读剩余
THE END