linux驱动模型 linux内核驱动模型详解
大家好,今天来为大家解答linux驱动模型这个问题的一些问题点,包括linux内核驱动模型详解也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~
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内核|驱动模型】initcall和module_init
内核版本:Linux-6.1
文章目录汇总:所有文章目录-知乎(zhihu.com)
模块初始化的宏观:module_init
在Linux内核开发和驱动开发中,module_init是一个常见的宏,定义在 include/linux/module.h文件中。它的实现会根据是否定义了 MODULE宏有所不同,这决定了驱动是与内核编译到一起,还是单独编译为.ko文件。
MODULE的定义通常通过编译时的参数传递,可通过查看 Makefile文件,如在编译.ko时使用特定的编译选项,而链接到内核时则不会使用这些选项。
未使能 MODULE情况下,module_init实际上是作为特殊 initcall,用于声明初始化函数并控制函数调用顺序。initcall有多个级别,module_init实际对应于 device_initcall,级别为 6。initcall会在编译时声明一个 initcall_t类型的静态变量,并放入内核的.init.data段。
initcall的实现和行为可以通过查看 arch64-linux-gnu-nm-n vmlinux命令的输出进行分析。以 __initcall__kmod_cpuinfo__334_359_cpuinfo_regs_init6为例,这个 initcall_t类型的静态变量的名称和行为可从 __initcall_name和 __initcall_id的输出得出。
rootfs_initcall在 5秒后被调用,它在 do_basic_setup中执行,需要在此之前将存储介质准备好,如读取文件系统镜像。
console_initcall用于尽早输出日志,其初始化函数在 console_init中调用,而 console_init尽量选择较早时机进行。
链接脚本中,initcall声明的变量放入以.initcall开头的段中,每个级别对应一个段,并按顺序放入.init.data段。
initcall的执行时机包括 do_pre_smp_initcalls和 do_basic_setup,前者在多核处理器和调度系统初始化之前执行,后者按 initcall级别依次执行指定函数。链接时和多次编译的顺序可能影响同级别 initcall的执行顺序。
当 MODULE使能时,Linux中的某些模块可选择链接到内核或编译为.ko文件。initcall宏被定义为 module_init以兼容两者。分析 module_init实现,可以参考《module_init源码》。
__inittest:代码中未找到调用地方,但从 v2.6.0对 module_init的注释推测,可能是为了防止编译器警告。
init_module是 initfn的别名,具有相同的地址,通常为静态函数,而 init_module为全局函数。在命令行使用 insmod或 modprobe安装模块时,系统最终调用 init_module或 finit_module。
init_module和 finit_module用于从用户态加载.ko文件,查看 man 2 init_module可以了解这两个函数的具体使用。
加载模块的流程最终会调用 load_module,其流程如下。
Linux内核开发与Linux驱动开发有什么关系
我做过驱动开发,说说我的看法。本质上说Linux内核开发和Linux驱动开发是不一样的,或者说驱动开发是内核开发的一部分,因为驱动属于内核。目前国内驱动开发和内核开发一般是一样的。有对应linux内核开发工程师和linux驱动开发工程师职位。
内核开发指的是形成linux操作系统的过程,也就是内核开发者向内核中添加信息使得linux从无到有的过程或者添加新的功能,比如Linux内核在2.6版本内核之前是没有设备驱动模型,内核开发者在2.6版本中增加了设备驱动模型,这属于linux内核开发。而linux驱动开发指的是使用Linux内核提供的接口,驱动开发者根据实际情况按照内核提供驱动的框架写相应的驱动并注册到相应的总线上,进而驱使硬件设备工作,比如I2C设备就注册到i2c总线上,这个i2c总线是内核开发者为我们提供的接口,我们只需要调用相应接口即可。整个工作流程是:linux应用程序-内核-驱动-硬件。如下图所示:
多说一点,要进行linux驱动开发,必须学好C语言、能够看懂电路图(因为驱动开发需要根据相应的引脚来编写驱动)需要模电和数电知识,linux操作系统知识,linux应用编程知识(多进程、多线程、文件io操作)因为我们要测试写的驱动能否正常工作需要linux应用编程知识。最好对数据结构中的链表有所了解,内核中有大量的内核链表。
感谢评论,交流,转发。更多精彩内容可关注本头条号:嵌入式软硬件开发。感谢大家。相互交流,共同进步。