1.Linux下PCI设备驱动开发详解(六)
2.Linux下PCI设备驱动开发详解(三)
3.Linux-块设备驱动详解
4.深入Linux内核-设备驱动驱动(ioctl的设设备实现)
5.Linux驱动开发头文件剖析(二十四):<linux/ktime.h>、<linux/timekeeping.h>、备驱<linux/timekeeping32.h>
6.一文搞懂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是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、xscript打包源码写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设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/...
Linux下PCI设备驱动开发详解(三)
在深入PCIe硬件和软件开发之前,理解几个关键概念至关重要,它们能帮助我们把握整个PCIe异构系统的听书阅读源码工作原理,以及驱动和设备在系统中的定位。PCIe软硬异构系统基础
应用程序通过文件系统访问硬件,通常通过读写文件操作抽象设备。非内核功能的库函数直接实现,涉及硬件操作的则通过系统调用,由内核处理。 Linux将硬件分为字符设备、块设备和网络设备,设备通过文件名(设备文件)和设备号(主、从设备号)区分。设备文件以文件形式在/dev目录下,通过文件操作如open、read、write、close进行硬件操作。 驱动程序作为内核模块,不包含main()函数,由初始化函数启动,应用程序与驱动的工作模式不同,前者在用户态,后者在内核态。 设备驱动由总线、设备和驱动组成,总线作为硬件连接的桥梁,设备通过总线注册自身,匹配合适的驱动进行通信。总线、设备和驱动的协作
系统启动时自动创建总线目录。总线驱动通过bus_register()进行注册,生成设备和驱动文件夹,再通过device_register()和driver_register()进行设备和驱动的具体注册。 总线初始化和注册是内核初始化的一部分,新总线的添加通常由系统自动处理,驱动的probe函数在匹配成功后执行初始化。 设备和驱动的实例展示了PCI设备和其对应的驱动注册过程。 Linux设备驱动模型的核心在于总线、设备和驱动的紧密配合,它们共同构建了系统与硬件的交互机制。后续文章将深入探讨实际PCI设备驱动的开发细节。参考资料:
Linux-块设备驱动详解
深入解析Linux块设备驱动的核心原理 在Linux内核的世界里,块设备驱动是系统与存储设备交互的关键桥梁。本文将带你探索gendisk和request结构,以及一系列关键函数在驱动程序中的运作,如自旋锁(spinlock)的定义与使用。 首先,让我们聚焦于几个核心数据结构:gendisk: 作为设备描述符,存储了设备的基本信息,如扇区数等。
request: 请求队列的数据结构,负责记录和管理设备上的读写请求。
在驱动程序的初始化流程中,重要函数包括:set_capacity: 设置gendisk的扇区数,反映设备的存储容量。
add_disk: 注册gendisk,微食品+源码使其成为系统可识别的设备。
put_disk: 当设备不再使用时,注销gendisk以释放资源。
elv_next_request: 通过电梯算法获取未完成的请求,实现高效数据传输。
end_request: 标记请求的读写结果,更新状态并清理资源。
kzalloc/kfree: 分配和释放内存用于静态缓存,保证请求处理的内存管理。
rq_data_dir: 确定请求的读写方向。
在实际操作中,驱动的生命周期如下:在驱动的入口函数中,创建设备,分配request队列,gendisk结构,并进行初始化,如注册block设备。
request队列处理函数中,不断获取请求,根据命令标志执行读写操作,然后调用end_request结束请求。
在驱动退出时,通过put_disk, del_gendisk, kfree, blk_cleanup_queue, unregister_blkdev等函数释放所有资源。
测试阶段,你可能进行以下操作:加载模块: insmod
创建文件系统: mkdosfs
挂载: mount
文件操作: vi, cat
卸载: umount
网络文件系统挂载: mount (nfs)
最后,通过fdisk工具进行分区操作。
以上是Linux块设备驱动的基础概述,每个步骤都至关重要,它们共同构建了设备驱动与用户空间之间的无缝交互。深入理解这些核心概念,将有助于你编写出高效稳定的驱动程序。希望本文能为你在内核驱动开发的道路上提供宝贵的指引。深入Linux内核-设备驱动驱动(ioctl的实现)
ioctl功能简介
ioctl功能是为了实现用户空间和内核空间的数据交换外,还提供了对设备的控制能力,如报告错误信息、弹出介质、设置波特率等。
用户空间和内核空间实现ioctl的方法
在用户空间中使用int ioctl(int fd, unsigned long cmd, ...);函数实现,其中fd对应内核空间中的inode和file参数,cmd表示ioctl指令,...表示指令所需参数。在内核空间中通过int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg)实现。
如何实现ioctl方法
首先定义cmd命令,然后实现底层驱动中的ioctl函数,以实现特定功能。
关于cmd
cmd是一个unsigned int变量,用于区分不同驱动和命令。例如,定义cmd为0x,代表特定命令。
内核中cmd的前世今生
cmd被分为四个部分:幻数(区分不同驱动,如设备号申请时用到)、序数(命令编号)、数据传输方向(描述数据传输方向,如输入或输出)、数据大小(与体系结构相关,如ARM下为bit)。这些信息用于定义命令,让内核识别和处理。
高级cmd的诞生
了解cmd的基础后,可以尝试使用预定义命令。这包括可用于任何文件、普通文件或特定文件系统类型的操作。
关于第三个参数arg
arg用于传递参数,确保从用户空间传入的地址指针在内核空间中进行严格检查,避免潜在问题。在实现过程中,需要在不同文件中声明结构体和函数,以正确处理用户和内核空间的数据传递。
Linux驱动开发头文件剖析(二十四):<linux/ktime.h>、<linux/timekeeping.h>、<linux/timekeeping.h>
ktime.h
定义了内核时间相关的数据结构和函数,核心是ktime_t,它表示纳秒级内核时间,不随系统时钟变化。ktime.h还包含用于内核时间操作的宏和函数。
ktime.h是timer.h的一部分,对驱动开发中使用定时器至关重要。
ktime.h内含ktime_get、ktime_set等函数,用于获取和设置时间值。
对比另一个用于一般时间操作的头文件,ktime.h更多关注内核时间。
ktime_t本质为s类型,ktime_set用于将秒数和纳秒数转换为ktime_t时间值。
ktime_add_ns和ktime_sub_ns用于对ktime_t时间变量进行加减操作。
timespec_to_ktime和ktime_to_timespec用于结构体转换。
ktime_compare用于比较ktime_t大小。
ktime_after和ktime_before用于比较时间点。
ktime_divns函数用于ktime_t时间除以纳秒值。
ktime_to_us和ktime_to_ms将时间值转换为微秒和毫秒。
ktime_us_delta和ktime_ms_delta用于时间差计算。
ktime_add_safe安全相加ktime_t值。
ktime_to_timespec_cond进行转换并返回成功标志。
ns_to_ktime和ms_to_ktime进行单位转换。
timekeeping.h
时间管理相关的头文件,涉及系统、真实、启动、TAI时间等操作,包括大量函数声明。
timekeeping_init初始化时间管理机制。
timekeeping_suspended标志表示时间管理状态。
update_process_times更新进程时间统计信息。
xtime_update更新全局系统时间。
do_settimeofday设置系统时间。
do_sys_settimeofday设置系统时间并处理时区。
ktime_get_resolution_ns获取系统时钟分辨率。
ktime_get_real获取真实时间。
ktime_get_coarse_real获取低精度真实时间。
do_settimeofday和do_sys_settimeofday用于时间设置。
系统时间相关函数如ktime_get_boottime和ktime_get_clocktai。
ktime_get_coarse用于低精度时间获取。
ktime_get_coarse_ns等函数用于纳秒级时间获取。
ktime_mono_to_real将单调时间转换为真实时间。
ktime_get_ns等函数用于系统时间、真实时间等纳秒级获取。
ktime_get_boottime_ts和ktime_get_coarse_boottime_ts用于获取启动时间。
ktime_get_boottime_seconds获取秒级启动时间。
ktime_get_clocktai_ts和ktime_get_coarse_clocktai_ts用于获取TAI时间。
ktime_get_clocktai_seconds获取秒级TAI时间。
ktime_timestamps定义时间戳字段。
system_time_snapshot和system_device_crosststamp定义系统时间快照和设备交叉时间戳。
system_counterval_t用于系统计数器值。
get_device_system_crosststamp获取设备系统交叉时间戳。
ktime_get_snapshot和ktime_get_fast_timestamps用于获取系统时间戳。
persistent_clock_is_local标识持久性时钟是否本地。
read_persistent_clock和read_persistent_wall_and_boot_offset用于读取持久性时钟。
update_persistent_clock用于更新持久性时钟。
timekeeping.h
过时文件,内容已迁移至timekeeping.h。仅包含一个函数。
用于返回当前时间。
时间管理头文件ktime.h和timekeeping.h提供了内核时间操作的全面支持,从时间结构定义到时间转换、时间比较直至时间设置和获取,满足驱动开发中对时间处理的需求。timekeeping.h作为过时文件,其内容已整合至timekeeping.h中,未来将被删除。
一文搞懂Linux内核网络设备驱动(白嫖小知识~)
介绍数据包收包过程,有助于我们了解Linux内核网络设备在数据收包过程中的位置。数据包从被网卡接收到进入socket接收队列的整个过程,首先涉及网络设备初始化。以Intel I网卡的驱动ibg为例,驱动会在加载时调用初始化函数。pci_register_driver函数用于将驱动的各种回调方法注册到一个struct pci_driver变量中。通过PCI ID识别设备后,内核为设备选择合适的驱动。许多驱动需要大量代码使得设备就绪,如设置net_device_ops变量,注册ethtool函数,以及配置软中断。
网络设备启动过程中,igb_probe函数完成设备初始化工作,包括PCI相关的操作和通用网络功能。结构net_device_ops变量包含了网络设备相关的操作函数,例如开启网络设备(ndo_open)时会调用对应的方法。在使用DMA将数据直接写入内存后,实现这一功能的数据结构为ring buffer。预留内存区域给网卡使用,实现数据包的接收。网卡支持接收侧扩展(RSS)或多队列技术,以利用多个CPU并行处理数据包。Intel I网卡支持多队列,其驱动在启用时调用igb_setup_all_rx_resources函数管理DMA内存。
启用NAPI(New API)接收数据包,通过调用napi_enable函数设置NAPI变量中的启用标志位。对于igb驱动,当网卡被启用或通过ethtool修改队列数量或大小时,会启用每个q_vector的NAPI变量。注册中断处理函数,不同驱动实现因硬件而异,一般优先考虑MSI-X中断方式,以实现更高效的数据处理。最后,打开硬中断,网卡便可以接收数据包。
监控网络设备有多种方式,从最粗粒度的ethtool -S查看网卡统计信息,到sysfs中获取接收端数据包的详细类型统计。sysfs提供统计信息,但驱动决定何时以及如何更新这些计数。/proc/net/dev提供了更高层级的网卡统计,适合作为常规参考。如果对数据的准确度有高要求,必须查看内核源码、驱动源码和驱动手册,以完全理解每个字段的实际含义。
本文仅介绍了Linux内核网络设备驱动的基本概述,未来将深入探讨更详细的内容,欢迎关注后续文章。
Linux驱动开发 - Linux 设备树学习 - DTS语法
设备树(Device Tree)是一种描述硬件设备的树形结构文件,主要用于Linux系统中描述板级设备信息,如CPU数量、内存基地址、IIC接口和SPI接口所连接的设备等。设备树的主干是系统总线,IIC控制器、GPIO控制器、SPI控制器等设备是系统总线上的分支。例如,IIC控制器分为IIC1和IIC2,其中IIC1连接了FT和ATC这两个IIC设备,IIC2仅连接了MPU一个设备。
在开发Linux设备驱动时,需要了解DTS(Device Tree Source)、DTB(Device Tree Binary)和DTC(Device Tree Compiler)之间的关系。DTC工具依赖于特定的源代码文件,最终生成主机文件DTC。要编译DTS文件,只需在Linux源码根目录下执行命令“make all”或“make dtbs”,后者仅编译设备树。
在开发板中,每个板子都对应一个DTS文件,以I.MX6ULL芯片为例,打开arch/arm/boot/dts/Makefile文件,可以找到特定编译配置。当选中I.MX6ULL芯片后,与该芯片相关的DTS文件会被编译成DTB文件。若要为新的板子编写DTS文件,只需新建此板子对应的DTS文件,并在dtb-$(CONFIG_SOC_IMX6ULL)下添加对应的DTB文件名,这样在编译设备树时会自动编译为二进制文件。
在Linux内核源码分析学习方面,可参考指定地址。此外,Linux内核源码分析交流群提供学习资源,包括书籍、视频等,通过加入该群可以获取这些资源。
在编写设备树文件时,需要了解DTS语法。DTS文件支持头文件,扩展名为.dtsi。设备树节点通过属性信息描述,属性是键值对形式。例如,在imx6ull.dtsi文件中,描述了CPU架构、频率、外设寄存器地址范围等信息。设备节点是树形结构中描述设备的节点,通过节点名字和地址来描述。
兼容性属性(compatible)是设备树中非常重要的属性,用于将设备与驱动绑定。属性值是一个字符串列表,格式为“厂商名称, 设备名称”。Linux下的外设驱动通常会使用这些兼容性属性来查找与设备匹配的驱动程序。
模型属性(model)描述设备模块信息,如设备名字。状态属性(status)记录设备状态,可选状态包括正在运行、已停止、错误等。地址属性(address-cells和size-cells)用于描述设备子节点的地址信息,reg属性用于描述设备地址空间资源信息。ranges属性用于描述设备子地址和父地址的映射关系。
在产品开发过程中,设备树文件需要随着硬件需求的变更而更新。例如,需要在I.MX6U-ALPHA开发板的I2C1接口上添加一个新设备时,需要在对应的DTS文件中向已有节点添加新子节点。
在Linux内核启动时,设备树信息会被解析并在根文件系统中以目录/proc/devicetree的形式体现。通过该目录可以查看根节点的属性和子节点,如模型、兼容性、地址等信息。这些信息与设备树文件中的描述相匹配。
linux设备驱动程序——i2c设备驱动源码实现
深入了解Linux内核中的i2c设备驱动程序详解 在Linux内核中,i2c设备驱动程序的实现是一个关键部分。本文将逐步剖析其形成、匹配及源码实现,以帮助理解i2c总线的工作原理。 首先,熟悉I2C的基本知识是必不可少的。作为主从结构,设备通过从机地址寻址,其工作流程涉及主器件对从机的通信。了解了基础后,我们接着来看Linux内核中的驱动程序框架。 Linux的i2c设备驱动程序框架由driver和device两部分构成。当driver和device加载到内存时,会自动调用match函数进行匹配,成功后执行probe()函数。driver中,probe()负责创建设备节点并实现特定功能;device则设置设备的I2C地址和选择适配器,如硬件I2C控制器。 示例代码中,i2c_bus_driver.c展示了driver部分的实现,而i2c_bus_device.ko和i2c_bus_device.ko的编译加载则验证了这一过程。加载device后,probe函数会被调用,确认设备注册成功。用户程序可测试驱动,通过读写传感器寄存器进行操作。 在设备创建方面,i2c_new_device接口允许在设备存在时加载驱动,但有时需要检测设备插入状态。这时,i2c_new_probed_device提供了检测功能,确保只有实际存在的设备才会被加载,有效管理资源。 深入源码分析,i2c_new_probed_device主要通过检测来实现设备存在性,最终调用i2c_new_device,但地址分配机制确保了board info中的地址与实际设备地址相符。 至此,关于Linux内核i2c驱动的讨论结束。希望这个深入解析对您理解i2c设备驱动有帮助。如果你对此话题有兴趣,可以加入作者牧野星辰的Linux内核技术交流群,获取更多学习资源。 学习资源Linux内核技术交流群:获取内核学习资料包,包括视频教程、电子书和实战项目代码
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直达:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈