基于Linux 的PXIe 可重构仪器设备驱动程序开发
2021-04-29王法臻崔少辉
王法臻,崔少辉,王 成
(陆军工程大学石家庄校区,石家庄 050003)
0 概述
PXIe 总线是PCIe 总线在仪器领域的拓展,通过在PCIe 总线基础上附加必要的时钟信号、触发总线、星形总线和本地总线等,可形成PXIe 扩展信号[1]。PXIe 总线采用点对点的通信方式并利用差分信号进行数据传输,可使数据的传输速率和品质得到大幅提升。
设备驱动程序作为操作系统的重要组成部分,是应用程序与硬件完成数据交互所必需的媒介[2-3]。为满足自主设计的PXIe 可重构仪器与上位机之间的通信需求,在完成PXIe 接口硬件部分设计的基础上,必须开发相应的PXIe 设备驱动程序才能实现功能控制指令和测试数据经PXIe 总线的传输。因此,设计稳定可靠的驱动程序十分关键。
本文在研究Linux 字符设备驱动结构的基础上开发可重构仪器的PXIe 设备驱动程序。设计直接存储器存取(Direct Memory Acess,DMA)传输操作流程,并对应用程序与驱动程序数据的交互方式进行优化,从而提高数据传输效率,使PXIe 可重构仪器能够在国产操作系统下稳定运行。
1 PXIe 可重构仪器
通用自动测试系统(Automatic Test System,ATS)采用共享资源架构,其通过开关系统分配测试通道和资源,易产生开关延迟、测试资源竞争和死锁等问题[4]。多通道可重构仪器具备良好的通用性和灵活性[5-6],可根据用户需求进行重构从而获得不同的测试能力,且每路测试通道均具有独立完成测试的能力,因此为传统通用ATS 存在的问题提供了良好的解决方案。
多通道PXIe可重构仪器以Altera公司的Cyclone IV系列FPGA 为核心,配置测量功能电路模块,其32 路测试通道均具备数模转换、模数转换、波形产生以及电压比较等功能,各通道可根据测试需求通过重构实现一定的功能扩展。上位机与仪器设备经PXIe总线接口完成功能控制指令及测试数据的传输。由于PXIe 与PCIe 遵循相同的协议,因此PXIe 接口的开发通过FPGA 中的PCIe IP 硬核实现,配置选择为PCIe 1.1 x4 链路,理论上最大传输速率可达1 GB/s。该实现方式相比采用专用芯片进行接口开发的优势在于可简化电路设计同时降低开发成本。
2 Linux 设备驱动程序
Linux 贯彻“一切皆文件”的思想,因此各类外部设备也都被看作是文件,一般称作设备文件(或设备节点)[7]。用户层应用程序并不能直接访问操作外部硬件设备,需要先通过如open、close、read、write 和unlocked_ioctl 等系统调用访问设备文件,然后借助Linux 中虚拟文件系统(Virtual File System,VFS)所提供的访问接口寻找各个系统调用的响应函数,从而进一步操作外部硬件设备[8]。
设备文件所对应的设备驱动程序负责为操作外部硬件设备提供与系统调用相关联的响应函数,这些响应函数可直接访问外部硬件设备。因此,用户层应用程序必须通过设备驱动程序来实现对外部硬件设备的访问操作。Linux 系统下设备驱动程序、应用程序及硬件设备三者之间的关系如图1 所示。
图1 设备驱动、应用程序与硬件设备间的关系Fig.1 Relationship of device driver,application and hardware device
Linux 设备驱动程序按设备类型一般划分为字符设备驱动、块设备驱动和网络接口驱动3 类[9]。字符设备以字节为单位传输数据,块设备以块为单位(每块至少512 Byte)传输数据,网络设备以用户数据包形式在网络媒介上传输数据。
PXIe 设备驱动程序以字符设备驱动结构为主体框架进行设计开发,因此,需要先对字符设备驱动的相关情况有所掌握。字符设备、字符设备驱动和应用程序之间的结构关系如图2 所示,具体描述如下:
1)应用程序通过系统调用经VFS 提供的接口访问驱动程序,驱动程序根据应用程序要求进一步操作访问设备。
2)结构体cdev 负责描述字符设备驱动,其主要由设备号和文件操作结构体两部分组成。当系统调用宏module_init 加载驱动时,即实现该结构体的实例化并完成字符设备的注册。
3)设备号dev_t 负责存放驱动程序给设备分配的主设备号和次设备号,主设备号负责标识设备类型,次设备号负责标识同类设备中的特定设备。由于设备号是设备文件在系统中的标志且唯一确定,因此新设备号的申请不能与已有设备号发生冲突。
4)文件操作结构体file_operations 作为用户层与内核层交互的接口,是建立起应用程序与驱动程序交互的关键所在。当用户层应用程序使用open、release、unlocked_ioctl 等Linux 系统调用时,系统经由VFS 间接寻找并执行file_operations 结构体中所对应的xxx_open、xxx_release、xxx_unlocked_ioctl 等操作函数,完成对设备的指定操作。
5)驱动卸载在系统调用宏module_exit 时完成,负责设备号的释放和所申请的相关资源并注销cdev结构体。
图2 字符设备驱动程序结构Fig.2 Structure of character device driver
为使仪器能够在国产操作系统下工作运行,驱动程序的开发环境选择国产Deepin 15.11 操作系统,Linux 内核版本号4.15.0-30-generic,使用系统自带vim 编辑器和GCC 编译器完成驱动程序代码的编写及编译。
3 PXIe 设备驱动设计
PXIe 设备驱动程序基于字符设备驱动结构的开发设计流程如图3 所示。
图3 PXIe 设备驱动程序设计流程Fig.3 Procedure of PXIe device driver design
3.1 驱动初始化
驱动程序的初始化主要完成对结构体cdev 和结构体pci_driver 的初始化,目的在于完成PXIe 设备在系统中的注册、硬件资源的获取以及建立与应用程序的交互关系。
cdev 结构体已在上文进行相关介绍,主要实现设备在系统中的注册并建立与应用程序的交互关系。设备号dev_t 申请成功后,通过调用函数cdev_init 初始化cdev 中的file_operations 结构体,建立其与应用程序的交互关系,通过调用函数cdev_add 向系统添加字符设备从而完成设备注册。Linux 系统调用所对应的file_operations结构体成员函数设置如下:函数pxie_open和pxie_release 负责连接和断开应用程序与驱动程序的连接;函数dma_mmap 负责实现内存共享映射以提高应用层与内核层间数据交互速率;函数pxie_read 和pxie_write 负责对BAR 空间上寄存器的访问,函数dma_unlocked_ioctl 用于DMA 传输控制指令的发送。
pci_driver 结构体实现驱动程序与特定设备的匹配以及硬件资源信息的获取。驱动初始化的实现代码如下:
3.1.1 设备匹配
操作系统启动后会自动检测总线上的所有设备,并将厂商号(VendorID)、设备号(DeviceID)等硬件信息记录在pci_dev 结构体中。驱动程序即通过这些硬件信息实现与设备的匹配连接。
驱动程序的pci_device_id 结构体中包含设备的ID 信息,通过宏MODULE_DEVICE_TABLE 将其导出至用户空间。在驱动初始化过程中,当调用函数pci_register_driver 初始化pci_driver 结构体时,系统会自动匹配pci_dev 结构体与驱动程序pci_device_id结构体中的设备ID 信息。若匹配成功,则激发驱动程序调用pxie_probe 函数去探测设备以获取硬件资源信息[10-11]。设备匹配的实现代码如下:
3.1.2 设备探测
设备硬件资源的探测通过pxie_probe 函数进行,主要完成I/O 资源的申请、基址寄存器(Base Address Register,BAR)空间基地址的获取、设备中断号的申请以及中断处理函数的注册,同时将相关资源信息保存至私有数据结构体private_data 中供驱动程序使用。设备探测的实现代码如下:
PXIe 设备共有6 个BAR 可使用,均可配置为I/O空间或存储器空间,两者在访问方式上有所不同,设备仅使用BAR0、BAR1 且配置为存储器空间。在完成BAR 物理地址到内核空间虚拟地址的映射后,便可通过函数ioread32/iowrite32 访问BAR 中相应地址上的配置寄存器实现DMA 传输。
3.1.3 设备删除
通过调用函数cdev_del 和pci_unregister_ driver分别注销cdev结构体和pci_driver结构体,以实现设备的移除并调用pxie_remove 函数删除所申请的硬件资源。设备删除的实现代码如下:
3.2 共享内存映射
以DMA 方式向设备发送数据,用户层应用程序需要先将数据传输至内核层驱动程序的DMA 缓冲区中,再通过对该缓冲区的操作写入设备。以DMA方式从设备中读取数据沿反方向执行。在频繁进行数据传输时,大量数据若通过函数copy_from_user和copy_to_user 以拷贝的方式实现用户层和内核层之间的数据交互,会影响整体传输效率[12],而通过建立共享内存映射可有效解决这一问题[13-14]。
共享内存映射是指将一段物理内存同时映射出用户空间虚拟地址和内核空间虚拟地址,这样应用程序和驱动程序均可对该段物理内存进行访问,从而减少数据拷贝带来的时间开销,提高数据的传输效率。共享内存映射的具体实现由系统调用mmap,通过其在file_operations 结构体中的响应函数dma_mmap 完成。利用虚拟内存区(Virtual Memory Areas,VMA)结构将驱动程序创建的DMA 缓冲区的虚拟地址转换到该段内存的物理地址,然后通过此物理地址为该段内存建立新的页表并映射出起始的用户空间虚拟地址供应用程序使用[15-16]。共享内存映射的实现代码如下:
用户层应用程序通过mmap 系统调用所得到的返回地址就是该段共享内存在用户层的虚拟地址,应用程序通过该地址可直接对DMA 缓冲区进行访问。
unsigned int *user_dmaaddress=(unsigned int *)mmap(NULL,DMA_LENGTH,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
当设备停止使用时,应解除共享内存映射关系并释放所申请的DMA 缓冲区。应用程序在release系统调用的响应函数pxie_release 中,通过调用函数munmap 和pci_free_consistent 实现。
3.3 DMA 及中断处理
上位机同设备之间的数据传输采用DMA 方式实现。由于在FPGA 内部已设计实现DMA 控制器,因此CPU 只需要完成对DMA 传输相关寄存器的配置以及中断信号的处理即可,而不必直接参与数据传输,从而有效降低CPU 负荷并提高数据的传输速率。DMA 传输操作流程如图4 所示。
图4 DMA 传输操作流程Fig.4 Procedure of DMA transmission operating
在启动DMA 传输前,通过函数iowrite32 设置BAR 空间上DMA 传输首地址、传输长度以及中断服务寄存器,然后设置读写控制寄存器以启动DMA传输。同时,通过等待队列方式阻塞当前进程直至设备向上位机发出中断信号,该信号是数据传输完毕的标志。
由于接口设计采用INTx 中断方式,设备可能与其他设备共享同一中断号,系统在实际运行过程所接收到的中断信号可能源自其他设备,因此还需要对中断信号的来源进行判断,即通过函数ioread32检查BAR空间上中断服务寄存器的数值变化。若中断信号确由设备产生,则清除中断信号并唤醒等待队列所阻塞的进程以通知应用程序传输结束。
4 驱动程序的测试
Linux 驱动程序以内核模块形式通过静态或动态的方式加载至内核中[17]。由于静态加载方式在内核编译以及重启系统过程中会带来大量的时间开销,因此在驱动程序开发调试阶段采用动态加载方式添加和移除驱动模块。驱动模块的加载与卸载通过命令insmod 和命令rmmod 实现[18-19]。
4.1 加载与卸载
驱动代码文件pxie_device.c 的编译通过vim 编辑器编写Makefile 文件实现。由于Makefile 文件可通过make 工具自动完成编译工作,因此只需要通过系统终端在源代码文件所在目录下执行make 命令,即可通过GCC 编译器生成驱动模块pxie_device.ko。pxie_device.ko 通过动态方式加载至内核后需创建相应的设备节点(设备文件),该节点的创建为应用程序提供了访问设备的接口。加载与卸载的实现代码如下:
系统驱动模块显示列表如图5 所示。
图5 系统驱动模块显示列表Fig.5 Display list of system driver module
4.2 测试结果
Qt 是一个跨平台的C++应用程序开发框架[20],本文通过其集成开发环境Qt Creator 以图形化界面的方式开发实现测试程序,如图6 所示。测试程序通过对FPGA 中FIFO(First Input First Output)存储器进行数据读写传输以验证驱动程序是否符合设计需求,数据校验结果如图7 所示。
图6 测试程序界面Fig.6 Test program interface
图7 数据校验结果Fig.7 Data verification result
在测试过程中,数据实际传输速率较理论峰值存在一定差距,这主要是由于软件时间花销和协议规范消耗带宽(8b/10b 编码以及各协议层数据包封装)所导致。当数据量较小时,这部分花销占用比较大,导致实际测得的传输速率较低;当数据量较大时,这部分花销占用比减小,从而使得传输速率显著提高。
DMA 写(设备接收上位机发送的数据)数据传速率可达350 MB/s,DMA 读(上位机接收设备发送的数据)数据传输速率可达420 MB/s。在传输相同数据量的情况下,DMA 读速率大于DMA 写速率,这主要是因为在软件从设备读取数据时,硬件端只需发送存储器写请求TLP 即可;而向设备写入数据时,硬件端在发送存储器读请求TLP 后,还需要有存储器读完成报文进行应答,从而产生了一些时间花销。
5 结束语
目前,Windows 操作系统仍占据国内操作系统绝大部分市场份额,但微软已停止对Win7 的技术支持,而Win8/Win10 安全性较差,因此,实现面向国产操作系统的软件开发具有重大意义。本文介绍国产Deepin操作系统下PXIe 驱动程序的开发方法,对应用程序与驱动程序间的数据交互过程进行优化,同时结合阻塞机制和中断机制设计DMA 传输操作流程。测试结果表明,该方法数据传输准确稳定,可满足PXIe 可重构仪器的数据通信需求,并且其通用性较好,在工程实践应用中具有一定参考价值。在实际测试系统中往往需要使用多个同类型仪器,下一步将实现应用程序通过单个驱动同时对多个可重构仪器的操作控制及数据传输。