ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA QDMA Windows驱动编译与数字签名问题排查实践

FPGA QDMA Windows驱动编译与数字签名问题排查实践 上周帮客户调一块FPGA加速卡设计文档里的QDMA链路写得很顺利Linux侧一天就把吞吐跑到接近理论峰值结果一进入Windows驱动编译环节连续卡了三天VS和WDK版本不匹配、INF里的硬件ID对不上、安装时设备管理器直接报代码52“Windows无法验证此设备的驱动程序的数字签名”。最后把QDMA工程搭建、Windows驱动编译、性能测试三个环节重新串了一遍才把问题彻底理清。这篇文章就把这条完整链路拆开讲一遍适合正在做FPGA高速数据采集卡、智能网卡或者存储加速卡的上位机工程师和驱动开发人员参考。1. 开工前先理清QDMA工程搭建的完整依赖链很多人一上来就开Vivado拉一个QDMA IP核生成bit流就以为完事了。实际上QDMA工程搭建牵扯到硬件配置、软件驱动、运行时描述符管理三层任何一层和另外两层对不上后面调试都痛苦。1.1 为什么选QDMA而不是XDMA先说说选型。XDMA在Xilinx生态里用得最早它把DMA通道做成了相对固定的Block模式一个通道对应一大块连续传输驱动模型简单Linux下甚至可以直接用现成的dma-engine框架接。对于图像采集、普通数据回传这种单一、大块的搬运场景XDMA足够。但QDMA的思路完全不一样。QDMA内部维护了一个描述符队列引擎每个队列可以独立配置方向、深度、中断方式和调度权重硬件会根据描述符表自动搬运数据。这意味着你可以把它当成一个支持多队列、多通道、可精细控制调度的高性能DMA引擎。典型应用场景是NVMe存储加速、多路网络数据面、虚拟化场景里多个虚拟机共享同一片PCIe带宽。我个人的建议是如果只是板卡到上位机之间做简单的批量传输选XDMA开发成本低一个量级如果要做多队列并发、每个队列独立优先级或者后续要对接存储/网络协议栈直接上QDMA后面少改一版硬件。1.2 FPGA侧必须交付的几样东西QDMA工程搭建不只是给Vivado一个IP核你从FPGA侧拿到的产物至少要包含以下几类少了任何一个Windows驱动编译和调试都没法继续烧录文件bit/mcs包含QDMA IP核、AXI互联、用户逻辑的完整固件。板卡上电后PCIe才能枚举到这个设备。配置参数清单链路宽度和速率比如PCIe Gen3 x8、BAR空间分配大小、队列数量、AXI数据位宽和时钟频率、描述符地址宽度。这些参数直接决定驱动里怎么配置DMA引擎。寄存器映射表QDMA配置寄存器配置基址CBAR和队列寄存器队列基址QBAR的偏移和语义以IP手册为准。调试的时候你得知道往哪个偏移写什么。用户逻辑侧的队列行为说明哪些队列是H2C方向哪些是C2H方向队列是否支持按描述符长度自动聚散以及用户逻辑默认描述符格式是否被改动过。这些信息最好形成一份文档固定下来。我踩过最坑的一次是FPGA工程师改了队列深度但没同步给软件侧驱动还在按旧深度初始化环形缓冲结果描述符队列指针错位C2H方向数据全部错乱查了整整一个下午才发现是配置不同步。1.3 Windows下驱动路线的两条分支Windows下的QDMA驱动不像Linux那么透明基本有两条路可选。路线A是直接用官方提供的WDF驱动源码编译。这条路性能和可控性最好描述符管理、中断处理、DMA映射都在内核态完成用户态通过一个DLL调用API。但问题是官方对Windows的支持力度明显低于Linux有些QDMA版本不一定有公开的Windows驱动源码可能需要申请获取而且拿到的源码要自己处理WDK环境、INF文件、签名这些环节。路线B是基于WinDriver/Jungo这类用户态驱动框架。WinDriver会把PCIe设备的BAR空间直接映射到用户态你可以不写内核驱动直接在应用层读写寄存器、管理描述符、处理中断。优点是很适合做快速验证和原型开发缺点是多了一层框架的上下文切换开销高吞吐场景下性能比原生内核驱动低一些而且QDMA描述符引擎需要你自己完整实现一套队列管理逻辑。如果项目只是方案验证、或者板卡数量不多路线B是性价比最高的选择。正式产品化、追求性能和稳定性还是得走路线A把官方驱动编译这条路趟平。这篇文章后面主要展开路线A因为这条路的坑更多也更值得记录。2. Windows驱动编译环境的版本配对决定了成功率的80%QDMA的Windows驱动是基于WDFWindows Driver Framework写的编译它需要Visual Studio、Windows SDK、Windows Driver Kit三件套。这三者的版本如果不匹配最常见的表现是编译报一堆莫名其妙的头文件错误比如ntddk.h找不到、wdf.h版本冲突甚至项目模板直接加载不出来。2.1 推荐的环境组合我这次用的是Windows 10 x64Visual Studio 2019 WDK 10.0.19041的组合整个流程稳定。新的环境也可以选VS2022 WDK 10.0.22621但要注意安装顺序和版本对齐。开发主机系统Visual StudioWindows SDKWDKWindows 10 x64VS201910.0.1904110.0.19041Windows 11 x64VS202210.0.2262110.0.22621Windows 11 x64VS201910.0.1836210.0.18362安装顺序必须是先装Visual Studio再装Windows SDK最后装WDK。WDK安装程序会在VS里注册驱动开发相关的项目模板如果先装WDK再装SDK会导致VS里看不到“驱动程序”模板还要重新修复安装一遍。装完之后验证一下环境是否正常打开VS2019新建项目左侧模板列表里能找到“驱动程序”分类下的“WDF 驱动程序”模板。如果没有说明WDK的VSIX扩展没有正确注册去控制面板找到WDK安装项做修复。2.2 源码结构里必须先看懂的三个部分官方QDMA Windows驱动源码一般由三部分组成内核驱动工程生成.sys、用户态API封装工程生成DLL和静态库、示例应用工程。内核驱动工程里最关键的文件是INF文件它告诉Windows这个设备长什么样、驱动怎么安装其次是一个包含硬件ID的头文件驱动加载时靠这个匹配设备实例。拿到源码后不要急着按F5编译先做三件事。第一件事是确认平台是x64。现在几乎不需要x86内核驱动把解决方案平台的x86配置删除只保留x64避免误编译。第二件事是打开INF文件找到[Manufacturer]节和[Models]节。这里定义了设备硬件ID比如PCI\VEN_10EEDEV_9038。这个VID/VEN是提供商IDXilinx的设备一般是VEN_10EE开头DEV编号由IP核配置参数决定。第三件事是确认GUID没有冲突。驱动工程和DLL工程各自有自己的设备接口GUIDINF里会注册一个设备接口GUID用户态API通过这个GUID来打开设备。如果多个驱动或者多个版本之间GUID重复会出现打开设备失败或者串设备的问题。2.3 编译前先在Windows设备管理器里拿到真实硬件ID这一步很多人容易忽略导致编出来的驱动在目标机器上装不上。正确做法是先把FPGA板卡插到PCIe插槽上上电启动Windows此时设备管理器里会出现一个带黄色感叹号的“未知设备”或者“PCI设备”。右键属性切到“详细信息”页签在下拉框里选择“硬件ID”记录下类似PCI\VEN_10EEDEV_9038SUBSYS_...这样的字符串。注意INF文件里写的硬件ID要能匹配上这个真实枚举出来的ID。通常INF里会写一个可通配的版本比如PCI\VEN_10EEDEV_9038这样SUBSYS那一段就不影响匹配。但如果你拿到的驱动源码里DEV编号和你的硬件实际编号不一致必须改INF文件改成你设备管理器里看到的那一串。编译的时候我习惯用VS自带的“开发人员命令提示符”手动执行msbuild而不是在IDE里点编译按钮因为能看到完整的错误输出。命令大概是这样的msbuild QDMA_Driver.sln /t:build /p:ConfigurationRelease /p:Platformx64编译成功后在输出目录里能看到.sys、.inf、.cat三类文件。.sys是内核驱动本体.inf是安装描述文件.cat是安全编录文件里面放着驱动文件的哈希信息。2.4 INF里的关键字段不要凭感觉改INF文件里面有几个字段特别容易踩坑。一个是DriverVer这个字段的日期和版本号必须正确。Windows的即插即用管理器会根据DriverVer判断驱动优先级如果比系统自带驱动版本低设备管理器会拒绝安装。另一个是CatalogFile字段它指向.cat文件。如果你没有做签名证书这一步可以暂时不编cat文件INF里去掉CatalogFile这一行也能安装只是会弹签名警告。还有[DDInstall.NT]节下的CopyFiles字段它决定驱动文件被复制到哪个系统目录一般是12表示%SystemRoot%\system32\drivers。不要改成别的位置否则系统启动加载驱动时找不到文件。3. 签名这道坎设备管理器报无法验证数字签名时的完整应对64位Windows强制内核模式驱动签名未经签名的驱动程序默认会被拒绝加载。QDMA驱动是标准的内核驱动绕不开这一步。很多人编译驱动只用了半天装驱动却折腾了两天主要就是卡在签名和测试模式上。3.1 代码52的完整含义安装驱动时如果提示“Windows无法验证此设备的驱动程序的数字签名。最近的硬件或软件更改安装的文件可能未正确签名或已损坏代码 52”或者设备状态显示“Windows 无法验证此设备所需的驱动程序的数字签名”说明驱动没有被系统信任。这个机制有两个层面。第一个层面是文件级签名要求驱动文件的数字签名链能追溯到受信任根证书内核在加载驱动前会校验完整性和签名链。第二个层面是平台级策略普通Windows强制要求所有内核驱动有签名只有开启测试签名模式或者关闭强制签名未签名驱动才能加载。3.2 最稳定的做法开启测试签名模式测试签名模式是最推荐的办法因为它是系统级别的开关不用每次开机都操作。管理员权限打开CMD执行bcdedit /set testsigning on重启系统。重启后桌面右下角会出现“测试模式”水印说明已经生效。此时再去安装未签名或自签名驱动系统会放行。要关闭测试模式就执行bcdedit /set testsigning off后面再污染签名需要特别注意的是Windows更新和某些安全软件会重置这个开关。如果是Windows Update做过大版本更新建议检查水印还在不在。3.3 用自签名证书给QDMA驱动签名测试模式打开后为了让驱动文件带上签名信息这样可以避免一部分额外警告可以用自签名证书给它签一下。先在PowerShell里生成自签名代码签名证书New-SelfSignedCertificate -Type CodeSigningCert -Subject CNQDMA Test Cert -CertStoreLocation Cert:\CurrentUser\My然后导出为PFX文件再用WDK自带的signtool签名signtool sign /f qdma_test.pfx /p 你的密码 /fd sha256 /v qdma_driver.sys如果签名后还要做cat文件用WDK的inf2cat工具inf2cat /driver:C:\qdma_driver_folder /os:10_x64生成cat文件后把cat文件和sys文件放到同一个目录INF里的CatalogFile字段要指向这个cat文件。接着可以验证一下签名链是否生效signtool verify /pa /c qdma_driver.cat注意自签名证书在测试模式下能用但正式发布产品不能靠它。正式商用得买EV代码签名证书做内核模式交叉签名这个证书每年的成本不低而且申请流程比较严格需要公司主体资质。3.4 手动安装驱动的完整步骤驱动文件和INF准备好之后安装步骤其实很机械打开设备管理器找到带感叹号的未知设备右键“更新驱动程序”。选择“浏览我的电脑以查找驱动程序”。指向INF所在目录。如果弹出安全警告在测试签名模式下一般可以直接选“安装”。装完之后设备管理器里设备名称会从“未知设备”变成INF文件里定义的设备名。右键属性切到“详细信息”下拉框里选“设备实例路径”确认设备正在运行。如果状态显示错误记下状态码常见的有代码52、代码10、代码28。代码10出现时通常是驱动和硬件之间匹配不成功优先回查INF硬件ID或者驱动版本和FPGA侧固件不匹配。代码28是驱动未安装说明INF匹配规则没命中检查VEN_10EE那个硬件ID字符串是不是写错了。3.5 临时禁用强制签名只能应急还有一个办法是开机时禁用驱动程序强制签名流程是WinI打开设置进入“更新和安全”-“恢复”-“高级启动”点“立即重新启动”然后依次点击“疑难解答”-“高级选项”-“启动设置”-“重启”开机菜单里按数字键选择“禁用驱动程序强制签名”。这个方法的问题在于它只对当前这一次启动有效每次开机都得重复操作极其影响调试效率而且有些机器UEFI模式下这个选项表现不稳定。所以我还是建议直接开测试签名模式一劳永逸。4. 驱动安装后的性能测试从设备枚举到带宽实测驱动正常加载只是第一步性能能不能跑起来才是硬道理。QDMA性能测试这部分既有硬件的限制也有驱动配置和测试程序的细节任何一个环节不严谨测试数据都没有参考价值。4.1 测试环境先排除干扰因素性能测试之前建议做三件事。第一把Windows电源计划切到“高性能”避免CPU变频影响结果第二把Windows Defender实时保护临时关掉不然文件系统扫描会占用大量CPU缓存和中断资源第三测试时不要开浏览器等会产生周期性网络或磁盘活动的程序。测试拓扑方面最简单的是FPGA内部做回环QDMA写入的数据走到用户逻辑里直接转成读出方向回传给上位机。这种方式先验证PCIe链路和驱动正确性再测DDR读写验证真实用户逻辑场景。4.2 队列、描述符和缓冲区影响带宽的三个基础要素QDMA的性能模型离不开三个维度队列深度、描述符大小、缓冲区对齐。通俗点说队列深度决定了DMA引擎的“流水线”有多长描述符大小决定了一次搬运的粒度缓冲区对齐决定了PCIe事务是否能满负载传输。队列是QDMA最核心的概念。每个队列就是一个独立的描述符环形缓冲CPU在环里写入描述符硬件从环里取出描述符并执行搬运完成后填写完成状态。排队论里这个机制相当于快餐店取餐柜店方把做好的餐放进对应格子顾客按号取走。格子越多越能扛住突发客流但同时找格子也要多花时间。实际测试中我一般从队列深度512起步大块数据传输用1024。队列太浅CPU提交描述符的速度跟不上硬件消费速度链路会周期性空转队列太深描述符缓存缺失会变高单次访问描述符的延迟反而上升。描述符中的地址字段必须物理对齐。PCIe事务层要求地址至少256字节对齐实际测试中建议4KB对齐内存分配时优先申请2MB大页对齐的缓冲区。描述符地址低位不对齐会导致硬件做地址解析时把低位的地址当作标志位解析表现为传输偶发失败或者带宽断崖下跌。4.3 一个最小性能测试程序的核心逻辑用官方驱动自带的示例程序能很快验证链路但做带宽测试我建议自己写一个小的perf工具逻辑其实很简单核心步骤就是打开设备、配置队列、提交缓冲区、发起传输、等待完成、统计吞吐。示意代码如下基于WDF驱动自带的用户态APIHANDLE hDev QDMA_Open(0); // 配置Queue 0为H2C方向描述符深度1024 QDMA_SetupQueue(hDev, 0, QDMA_H2C, 1024); // 分配4KB对齐的缓冲区长度为128MB void *buf QDMA_AllocBuffer(hDev, 128 * 1024 * 1024, 4096); struct qdma_request req {0}; req.queue_id 0; req.buffer buf; req.length 128 * 1024 * 1024; // 发起一次搬运等待完成事件 QDMA_AsyncWrite(hDev, req); QDMA_WaitCompletion(hDev, req); // 计算带宽 double seconds (double)elapsed_ns / 1e9; double bandwidth (double)req.length / seconds / 1024 / 1024; printf(H2C bandwidth: %.2f MB/s\n, bandwidth);注意QDMA_AllocBuffer这个接口在Windows驱动里通常不是简单malloc它需要把用户态虚拟地址锁定并做DMA映射得到物理地址列表。这也是Windows下驱动开发比Linux繁琐的地方。Linux下有现成的VFIO或UIO框架帮你处理Windows下你必须通过WDF的DMA映射机制或者MDL来锁定内存。4.4 实测数据怎么算才合理先给出理论带宽做对照。PCIe Gen3每个lane是8GT/s采用128b/130b编码后有效带宽约7.877Gbps约等于0.985GB/s。x8链路就是7.88GB/sx16就是15.75GB/s。QDMA这类高性能DMA在理想大块传输下H2C方向做到链路峰值的80%左右是合理的C2H方向略低一点。我在这块板卡上的实测结果做个参考PCIe Gen3 x8AXI数据位宽512bit用户时钟250MHz队列数队列深度块大小H2C带宽C2H带宽12564KB4.2 GB/s3.9 GB/s110244KB5.6 GB/s5.1 GB/s410244KB6.7 GB/s6.2 GB/s420484KB6.8 GB/s6.3 GB/s可以看到从单队列到多队列、从浅队列到深队列吞吐都会涨。但涨幅不是线性的到队列深度1024以上之后主要瓶颈已经从描述符提交变成了PCIe链路本身。C2H方向比H2C方向低5%到8%是常见现象因为读方向需要额外的完成报文开销。5. 性能瓶颈排查带宽上不去、CPU飙升和蓝屏的完整链路性能测试往往不是一次就跑到位的。我在调这块板卡时遇到的问题很有代表性这里把排查链路完整写出来每一步都对应一个常见的低效假象。5.1 第一步永远先查PCIe链路协商状态带宽远低于预期时先别怀疑驱动和算法先确认PCIe链路到底协商到了哪个状态。很多“QDMA带宽只有1.2GB/s”的问题最终发现链路协商在了PCIe Gen2 x4甚至Gen1 x1上。在Windows下查看链路协商状态设备管理器里的“详细信息”页签能看到部分信息但更完整的方式是用PCIE工具或者板卡自带诊断程序。需要关注两个关键参数LinkCap表示设备支持的最大能力和LinkSta表示当前实际协商到的状态。正常情况下应该是Gen3 x8。链路降级的常见原因包括PCIe插槽物理宽度不够插在了x4槽上、金手指接触不良、参考时钟质量差导致的信号完整性问题、BIOS里PCIe链路速度被限制成Gen2。如果是长时间运行后降级还要怀疑PCB散热和电源纹波问题。我当时遇到的问题就是板卡在上电瞬间参考时钟不稳定设备协商到了Gen2 x4驱动正常但带宽只有1.2GB/s。把PCIe时钟芯片的配置寄存器调整了一下重新上下电后恢复到Gen3 x8带宽立刻跳到6.5GB/s以上。这个排查过程告诉我一个道理QDMA性能问题先看链路层再看事务层最后才看驱动层。5.2 中断频率和CPU核心分配是第二大瓶颈链路正常但带宽上不去第二步就是看中断。QDMA支持MSI-X多队列中断每个队列可以有自己的中断向量。如果驱动回退到了传统INTx共享中断所有队列的完成中断都会叠加在同一个CPU核上中断风暴会直接打满单个核导致CPU根本没时间提交下一批描述符。检查方法是用Windows Performance Recorder抓一次perf trace或者更简单地在任务管理器里看CPU占用分布。如果单个核心跑到100%其他核心空转大概率就是中断没做分散。调整手段有两个一是在驱动里开启MSI-X并给不同队列分配不同的中断向量二是在用户态测试程序里用SetThreadAffinityMask把处理不同队列完成事件的线程绑到不同核心上。另外QDMA驱动一般都有完成轮询模式。如果业务允许可以在高吞吐场景下关掉完成中断让CPU主动轮询描述符完成状态吞吐可能反而更高。代价是CPU占用一直保持在高位这个在低延迟和高吞吐之间需要做一个平衡。5.3 缓冲区分配和DMA映射相关的蓝屏问题Windows下做DMA还有一类高频坑是缓冲区问题。QDMA驱动在用户态申请内存时如果分配的是普通虚拟内存对应的物理页不一定连续。DMA引擎每次搬运需要一个物理连续的地址集合所以驱动必须把用户态缓冲区锁定并通过DMA映射函数将分散的物理页转换成硬件可以识别的描述符列表。如果你的驱动或者测试程序在分配DMA缓冲区时跳过了这个映射步骤直接把用户态虚拟地址当成物理地址填进描述符结果就是一运行就蓝屏常见错误是PAGE_FAULT_IN_NONPAGED_AREA或者IRQL_NOT_LESS_OR_EQUAL。排查这类蓝屏问题先用WinDbg打开内核dump看栈回溯是否能定位到QDMA驱动里的DMA映射函数。如果栈顶在DmaMapTransfer之类的函数说明是映射参数有问题。一个很实际的排查技巧是先用驱动自带的内存分配接口申请DMA缓冲区不要自己malloc跑通之后再去考虑优化自定义分配。驱动接口分配出来的缓冲区默认就是物理连续的虽然申请时间慢一点但至少不会蓝屏。还有一类蓝屏和中断有关表现为中断风暴把系统拖死但一般不会直接报特定错误码而是系统卡死几秒后自动重启。这种先检查是不是打开了测试签名模式但安装了冲突的杀毒驱动再检查是否关闭了传统INTx共享中断。5.4 针对QDMA特有机制的调优经验链路、中断、内存都正常之后还可以按QDMA的特有机制做微调。第一是队列方向分离。H2C和C2H最好不要混在同一个队列里应该按方向建立独立队列这样可以针对不同方向配置不同深度的描述符环避免一个方向的深队列拖慢另一个方向的消费速度。第二是描述符粒度。小包传输比如64字节在QDMA上效率极低因为每个描述符的提交和完成处理开销是固定的包越小固定开销占比越高。高带宽场景尽量用2KB到4KB的数据粒度如果业务上层是512字节扇区可以在驱动层做聚合凑够4KB再提交。第三是队列调度。QDMA的队列调度支持优先级配置如果你有低延迟控制面和高吞吐数据面两类流量把控制面队列优先级调高数据面队列优先级调低避免数据面突发把控制面的时延顶上去。这个和网卡上的多队列流量整形是同一个思路。第四要特别提醒的是驱动固件配套问题。QDMA的驱动版本和FPGA侧IP核版本必须匹配。更新IP核版本后描述符格式可能有细微变化比如标志位位置上多了一个字段如果还用老驱动描述符会被硬件解释成错误格式表现为偶发性传输超时、特定地址越界。遇到这种问题先查版本配套表再查代码。写在最后的一点经验这套流程走完一遍之后我对Windows下跑QDMA的整体心得可以浓缩成三句话版本对齐是前提签名问题要前置处理性能瓶颈从PCIe链路开始查。不要默认驱动编译完就能跑也不要默认带宽上不去就是驱动问题。先把测试签名模式打开、硬件ID改对、链路协商状态确认清楚后面的排查都会顺很多。我自己在刚接触QDMA的时候最想吃到的就是一条“照着做就能跑通”的完整路径希望这篇能帮你省下那几天的白折腾。
返回列表