- 示例工程
【免费下载链接】Windows-driver-samples
This repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.
本篇技术指南以 Windows-driver-samples 仓库中的 usb/kmdf_fx2 示例为核心,系统讲解如何用 Kernel-Mode Driver Framework(KMDF)为 OSR USB-FX2 学习套件编写一个完整的 USB 函数驱动程序(Function Driver),涵盖设备架构、驱动源码逐模块解析、IOCTL 接口设计、批量/中断传输实现、电源管理(选择性挂起与远程唤醒)、ETW 统一跟踪,以及配套测试工具 osrusbfx2.exe 的完整命令行用法。读完本文,你将掌握基于 KMDF 的 USB 驱动从"枚举设备 → 选择配置 → 创建队列 → 收发数据 → 事件跟踪"的完整开发与调试路径,并可直接照搬示例的命令在真实 FX2 板卡上做读写与回环验证。
示例概览:KMDF 如何驱动一块 USB 学习板
osrusbfx2 是 WDK 中经典的 KMDF USB 驱动示例,面向 OSR USB-FX2 Learning Kit 编写,目标是演示如何对 USB 设备执行批量(Bulk)传输与中断(Interrupt)传输。示例工程包含三部分,源码位置分别为:
| 目录 | 内容 | 说明 |
|---|---|---|
| usb/kmdf_fx2/driver | 内核驱动源码 | KMDF 函数驱动本体 |
| usb/kmdf_fx2/exe | 控制台测试程序 | 驱动与设备测试应用 osrusbfx2.exe |
| usb/kmdf_fx2/deviceMetadata | 设备元数据包 | 安装设备前需先部署到系统 |
此外 usb/kmdf_fx2/inc 存放内核态与用户态共享的头文件(public.h定义设备接口 GUID 与 IOCTL,prototypes.h提供原型),usb/kmdf_fx2/kmdf_fx2.sln 是 Visual Studio 解决方案入口。
设备硬件架构
从 README 的 Overview 可知,该设备基于 Cypress EZ-USB FX2 开发套件(CY3681)的开发板:
- 1 个接口(Interface)、3 个端点(Endpoint):Interrupt IN、Bulk OUT、Bulk IN,与源码 osrusbfx2.h 中的端点索引宏一一对应:
INTERRUPT_IN_ENDPOINT_INDEX = 0(中断输入端点,位于描述符第 0 位)BULK_OUT_ENDPOINT_INDEX = 1(批量输出端点)BULK_IN_ENDPOINT_INDEX = 2(批量输入端点)
- 固件支持厂商命令:查询/设置 LED 条形图(Bar Graph)显示、查询/设置 7 段数码管(7-Segment)显示、查询拨码开关(Toggle Switch)状态。这些命令对应的厂商请求码定义在 osrusbfx2.h:
| 宏 | 请求码 | 含义 |
|---|---|---|
USBFX2LK_READ_7SEGMENT_DISPLAY | 0xD4 | 读取 7 段显示状态 |
USBFX2LK_READ_SWITCHES | 0xD6 | 读取拨码开关状态 |
USBFX2LK_READ_BARGRAPH_DISPLAY | 0xD7 | 读取条形图状态 |
USBFX2LK_SET_BARGRAPH_DISPLAY | 0xD8 | 设置条形图状态 |
USBFX2LK_IS_HIGH_SPEED | 0xD9 | 查询是否高速模式 |
USBFX2LK_REENUMERATE | 0xDA | 触发设备重新枚举 |
USBFX2LK_SET_7SEGMENT_DISPLAY | 0xDB | 设置 7 段显示状态 |
中断端点特性(README 明确说明,实现于 interrupt.c):
- 发送一个 8 位值表示拨码开关组合状态;
- 在启动、从挂起(suspend)恢复以及开关组合变化时发送;
- 固件不做防抖(de-bounce),一次开关变化可能产生多个字节;
- 位序与面板标签相反,例如
0x80对应面板上标记为 1 的开关。
批量端点特性:
- 配置为回环(loopback)模式:设备把数据从 IN 端点搬移到 OUT 端点,不修改数据内容、也不自行产生数据;
- 端点始终双缓冲(double buffered);
- 最大包大小取决于速度:全速(Full Speed)64 字节,高速(High Speed)512 字节。
Universal Windows Driver 合规性
README 强调该示例构建的是Universal Windows Driver(UWD):驱动只使用 OneCoreUAP 中包含的 API 与 DDI,因此可以面向 Windows 10/11 全系列设备(含 IoT Core)进行部署。这意味着驱动中不会引用桌面版 Windows 专有的内核 API,所有 WDF 调用(WdfUsbTargetDevice*、WdfIoQueue*等)都属于可通用化接口。驱动与设备元数据还可以配合微软官方的Custom driver access示例工作——通过设备元数据将设备接口标记为 Restricted,并声明自定义能力(Custom Capability),使 UWP 设备应用可通过Windows.Devices.Custom命名空间访问该接口。
驱动源码巡览:从 DriverEntry 到数据通路
driver 目录职责总览
README 的 Code tour 将驱动功能归纳如下,每一项都能在源码中找到对应实现:
- 加载驱动并响应 PnP/Power 事件:可安装、卸载、禁用、启用、挂起、恢复系统;
- 创建设备上下文:通过
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext)(osrusbfx2.h)把上下文类型绑定到 WDFDEVICE 对象; - 注册
EvtPrepareHardware回调初始化 USB 设备; - 将接口标记为 Restricted,仅允许有特权的 UWP 设备应用访问;
- 创建默认并行队列接收设置条形图显示的 IOCTL;
- 从请求中取内存句柄并发送厂商命令到 USB 设备;
- 在默认队列上注册读/写事件,取内存、格式化请求并发送到 USB 目标;
- 创建两个独立顺序队列分别直接分发读、写请求(
*kmdf_fx2 only); - 启用 wait-wake 与选择性挂起(
*kmdf_fx2 only); - 配置 USB 目标连续读取器,异步读取中断端点上的拨码开关状态(
*kmdf_fx2 only); - 支持额外的 IOCTL:读写 7 段显示、拨码开关、重置与重新枚举设备(
*kmdf_fx2 only); - 创建 ETW Provider记录两个事件日志事件及读/写开始-停止事件(
*kmdf_fx2 only); - WPP 跟踪。
*kmdf_fx2 only标注的能力(顺序队列、电源管理、连续读取器、额外 IOCTL、ETW Provider)说明这是 osrusbfx2 示例区别于其他基础 USB 示例的进阶内容。
设备上下文结构:驱动状态的中枢
驱动把所有设备实例相关信息集中存放在DEVICE_CONTEXT结构(osrusbfx2.h)中:
typedef struct _DEVICE_CONTEXT { WDFUSBDEVICE UsbDevice; // USB 设备句柄 WDFUSBINTERFACE UsbInterface; // 已选配置的接口句柄 WDFUSBPIPE BulkReadPipe; // 批量输入管道 WDFUSBPIPE BulkWritePipe; // 批量输出管道 WDFUSBPIPE InterruptPipe; // 中断管道 WDFWAITLOCK ResetDeviceWaitLock;// 串行化 ResetDevice 的等待锁 UCHAR CurrentSwitchState; // 连续读取器保存的最新开关状态 WDFQUEUE InterruptMsgQueue; // 手动分发的中断消息队列 ULONG UsbDeviceTraits; // 设备特征(速度/自供电/远程唤醒) WDFMEMORY DeviceNameMemory; // 设备友好名内存(事件日志用) PCWSTR DeviceName; WDFMEMORY LocationMemory; // 设备位置字符串内存 PCWSTR Location; } DEVICE_CONTEXT, *PDEVICE_CONTEXT;注意WDFWAITLOCK的使用:README 与源码注释均指出,除了用等待锁串行化ResetDevice()调用,另一种更"WDF 风格"的做法是创建顺序队列并把 Reset IOCTL 转发过去(Device.c)。
EvtDeviceAdd:创建设备对象与四类 I/O 队列
OsrFxEvtDeviceAdd(Device.c)是驱动的核心装配函数,从源码结构看其执行序列如下:
- 用
WDF_PNPPOWER_EVENT_CALLBACKS_INIT注册EvtDevicePrepareHardware、EvtDeviceD0Entry、EvtDeviceD0Exit、EvtDeviceSelfManagedIoFlush; WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered)设置缓冲型 I/O;WdfDeviceCreate创建设备对象;WdfDeviceSetPnpCapabilities设置SurpriseRemovalOK = WdfTrue,避免用户态弹窗;- 创建默认并行队列(
WdfIoQueueDispatchParallel),注册EvtIoDeviceControl处理 IOCTL; - 创建读顺序队列(
WdfIoQueueDispatchSequential)并注册EvtIoRead、EvtIoStop,然后WdfDeviceConfigureRequestDispatching(device, queue, WdfRequestTypeRead)把 Read 请求直接分派到该队列; - 创建写顺序队列并注册
EvtIoWrite、EvtIoStop,用WdfDeviceConfigureRequestDispatching(..., WdfRequestTypeWrite)分派 Write 请求; - 创建手动分发队列(
WdfIoQueueDispatchManual)作为InterruptMsgQueue,专门停放等待中断发生的 IOCTL_GET_INTERRUPT_MESSAGE 请求,并显式设置PowerManaged = WdfFalse(该队列不直接访问设备,可在设备空闲时继续停放请求); WdfDeviceCreateDeviceInterface注册GUID_DEVINTERFACE_OSRUSBFX2设备接口;WdfWaitLockCreate创建 Reset 等待锁。
关于 EvtIoStop:源码注释解释了 SDV(Static Driver Verifier)的__analysis_assume使用原因——该驱动不长期持有请求、也不转发给其他驱动,因此框架等待所有驱动持有的请求完成后再进入低功耗/移除是正确的默认行为(Device.c)。
设备接口 GUID 与 Restricted 属性
设备接口 GUID 定义在 public.h:
// {573E8C73-0CB4-4471-A1BF-FAB26C31D384} DEFINE_GUID(GUID_DEVINTERFACE_OSRUSBFX2, 0x573e8c73, 0xcb4, 0x4471, 0xa1, 0xbf, 0xfa, 0xb2, 0x6c, 0x31, 0xd3, 0x84);在OsrFxEvtDeviceAdd中,驱动通过g_pIoSetDeviceInterfacePropertyData(运行时解析的IoSetDeviceInterfacePropertyData函数指针,避免直接链接引入版本依赖)执行两件关键事:
- 设置
DEVPKEY_DeviceInterface_Restricted = DEVPROP_TRUE,把接口标记为受限,使普通应用无法打开; - 在 RS2+(
NTDDI_WIN10_RS2)条件下设置DEVPKEY_DeviceInterface_UnrestrictedAppCapabilities为字符串microsoft.hsaTestCustomCapability_q536wpkpf5cy2,向设备接口实例添加自定义能力,允许 Windows 商店设备应用通过Windows.Devices.Custom访问(Device.c)。
这两处属性也可以改由 INF 声明(README/源码注释提到 INF 中 "OsrUsb Interface installation" 节),驱动内设置与 INF 声明二选一。
EvtDevicePrepareHardware:选择配置与管道初始化
OsrFxEvtDevicePrepareHardware(Device.c)是 USB 驱动初始化的标准场所,调用链为:
WdfUsbTargetDeviceCreateWithParameters(使用USBD_CLIENT_CONTRACT_VERSION_602)创建 USB 设备句柄,仅在第一次 PrepareHardware 时创建,资源重平衡重启时复用句柄重新选择接口;WdfUsbTargetDeviceRetrieveInformation读取 USBD 版本、端口与设备能力(高速/自供电/远程唤醒),存入UsbDeviceTraits;SelectInterfaces(Device.c):WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE选择单接口配置;WdfUsbTargetDeviceSelectConfig执行配置选择;- 遍历
NumberConfiguredPipes个管道,用WdfUsbInterfaceGetConfiguredPipe取回管道句柄,对每个管道调用WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(允许读取少于最大包大小的数据),并按类型填入InterruptPipe/BulkReadPipe/BulkWritePipe; - 三个管道必须全部找到,否则返回
STATUS_INVALID_DEVICE_STATE; - 关键失败分支:若配置选择失败且设备非高速(即插在 USB 1.1 端口),会写 ETW 事件
EventWriteSelectConfigFailure——OSR FX2 板的 Interrupt 端点描述符不符合 USB 规范,Windows 检测后会返回错误(这正对应 README 中"Failure to start the OSR device on a USB 1.1 controller"事件);
- 若设备支持远程唤醒(
WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE),调用OsrFxSetPowerPolicy设置电源策略; OsrFxConfigContReaderForInterruptEndPoint配置中断管道连续读取器。
电源策略:选择性挂起与 wait-wake
OsrFxSetPowerPolicy(Device.c)同时设置两类电源策略:
- S0 空闲策略:
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleUsbSelectiveSuspend),IdleTimeout = 10000(10 秒),调用WdfDeviceAssignS0IdleSettings——即启用 USB 选择性挂起,设备空闲 10 秒后进入低功耗; - Sx 唤醒策略:
WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings)后调用WdfDeviceAssignSxWakeSettings——启用 wait-wake,系统睡眠时设备可远程唤醒系统。
配合电源管理,EvtDeviceD0Entry中通过WdfIoTargetStart显式启动中断管道 I/O 目标(连续读取器不会自动投递请求,必须由驱动启动),EvtDeviceD0Exit中用WdfIoTargetStop(..., WdfIoTargetCancelSentIo)停止并取消在途请求;EvtDeviceSelfManagedIoFlush则在设备移除前冲刷中断消息队列中的挂起请求(Device.c)。
连续读取器:异步读取拨码开关
interrupt.c 演示了 KMDF 的continuous reader(连续读取器)用法:
WDF_USB_CONTINUOUS_READER_CONFIG_INIT指定完成回调OsrFxEvtUsbInterruptPipeReadComplete、上下文和传输长度sizeof(UCHAR)(每次读 1 字节,对应 8 位开关状态);WdfUsbTargetPipeConfigContinuousReader完成配置;框架默认向目标端点排队 2 个请求(WDF_USB_CONTINUOUS_READER_CONFIG_INIT允许配置最多 10 个,见源码注释);- 完成回调中保存
CurrentSwitchState并调用OsrUsbIoctlGetInterruptMessage完成挂起的中断消息 IOCTL; - 读取失败回调
OsrFxEvtUsbInterruptReadersFailed清空状态并完成挂起请求,返回TRUE表示重试读取。
源码注释特别提醒:OSR USB 设备在从低功耗恢复时也会产生一个中断消息,因此若中断消息 IOCTL 在设备进入低功耗后才发出,挂起的 IOCTL 可能在用户拨动开关前就被完成——若这是不希望的行为,应在 D0Entry 维护状态变量区分"上电引起的中断"。
IOCTL 接口设计
public.h 完整定义了驱动暴露的 IOCTL 集,全部基于CTL_CODE(FILE_DEVICE_OSRUSBFX2, IOCTL_INDEX + n, ...)(IOCTL_INDEX = 0x800,FILE_DEVICE_OSRUSBFX2 = 65500):
| IOCTL | 方法 | 访问 | 作用 |
|---|---|---|---|
IOCTL_OSRUSBFX2_GET_CONFIG_DESCRIPTOR(+0) | METHOD_BUFFERED | FILE_READ | 获取配置描述符 |
IOCTL_OSRUSBFX2_RESET_DEVICE(+1) | METHOD_BUFFERED | FILE_WRITE | 复位设备端口 |
IOCTL_OSRUSBFX2_REENUMERATE_DEVICE(+3) | METHOD_BUFFERED | FILE_WRITE | 触发设备重新枚举 |
IOCTL_OSRUSBFX2_GET_BAR_GRAPH_DISPLAY(+4) | METHOD_BUFFERED | FILE_READ | 读取条形图状态(BAR_GRAPH_STATE) |
IOCTL_OSRUSBFX2_SET_BAR_GRAPH_DISPLAY(+5) | METHOD_BUFFERED | FILE_WRITE | 设置条形图状态 |
IOCTL_OSRUSBFX2_READ_SWITCHES(+6) | METHOD_BUFFERED | FILE_READ | 读取拨码开关状态(SWITCH_STATE) |
IOCTL_OSRUSBFX2_GET_7_SEGMENT_DISPLAY(+7) | METHOD_BUFFERED | FILE_READ | 读取 7 段显示(UCHAR) |
IOCTL_OSRUSBFX2_SET_7_SEGMENT_DISPLAY(+8) | METHOD_BUFFERED | FILE_WRITE | 设置 7 段显示(UCHAR) |
IOCTL_OSRUSBFX2_GET_INTERRUPT_MESSAGE(+9) | METHOD_OUT_DIRECT | FILE_READ | 挂起等待下一次中断消息(SWITCH_STATE) |
BAR_GRAPH_STATE与SWITCH_STATE都是位域联合体(public.h),每个位对应一个条形/开关,可整体作为 UCHAR 使用(BarsAsUChar/SwitchesAsUChar),并以#pragma pack(1)紧凑打包。
在 ioctl.c 中,OsrFxEvtIoDeviceControl按 IOCTL 分发:
- GET_CONFIG_DESCRIPTOR:先以
NULL缓冲区调用WdfUsbTargetDeviceRetrieveConfigDescriptor获取所需大小(预期返回STATUS_BUFFER_TOO_SMALL),再用WdfRequestRetrieveOutputBuffer取输出缓冲完成填充; - GET/SET_BAR_GRAPH_DISPLAY、GET/SET_7_SEGMENT:通过
WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR构造厂商控制传输(方向BmRequestDeviceToHost/BmRequestHostToDevice,目标BmRequestToDevice,请求码对应上表),经WdfUsbTargetDeviceSendControlTransferSynchronously同步发送,控制传输超时由DEFAULT_CONTROL_TRANSFER_TIMEOUT = 5 * -1 * WDF_TIMEOUT_TO_SEC(5 秒)决定(osrusbfx2.h); - GET_SWITCH_STATE:同步厂商控制传输读取开关;
- GET_INTERRUPT_MESSAGE:
WdfRequestForwardToIoQueue转发到InterruptMsgQueue挂起,等待连续读取器回调OsrUsbIoctlGetInterruptMessage时完成(ioctl.c); - RESET_DEVICE:
ResetDevice先WdfWaitLockAcquire加锁,StopAllPipes停止三个管道 I/O 目标,WdfUsbTargetDeviceResetPortSynchronously复位端口,再StartAllPipes恢复、释放锁(ioctl.c); - REENUMERATE_DEVICE:构造
USBFX2LK_REENUMERATE厂商请求同步发送,成功后写 ETW 事件EventWriteDeviceReenumerated(ioctl.c)。
批量读/写:请求格式化与异步完成
bulkrwr.c 实现读写通路:
OsrFxEvtIoRead(bulkrwr.c):校验Length <= TEST_BOARD_TRANSFER_BUFFER_SIZE(64 KB,定义于 osrusbfx2.h)→WdfRequestRetrieveOutputMemory取内存 →WdfUsbTargetPipeFormatRequestForRead格式化请求(该调用会校验管道类型、设置传输标志、创建 URB)→ 设置完成例程 →WdfRequestSend异步发送到BulkReadPipe的 I/O 目标;OsrFxEvtIoWrite:对称流程,目标为BulkWritePipe;- 完成例程从
PWDF_USB_REQUEST_COMPLETION_PARAMS提取实际传输字节数并WdfRequestCompleteWithInformation完成请求; - 每个读/写开始、停止与失败都调用
EventWriteReadStart/ReadStop/ReadFail、EventWriteWriteStart/WriteStop/WriteFail,用于 ETW 计时分析; OsrFxEvtIoStop处理挂起/清除语义:挂起时WdfRequestStopAcknowledge,清除时WdfRequestCancelSentRequest(bulkrwr.c)。
ETW 事件与 WPP 跟踪
驱动同时使用两套跟踪机制:
- WPP 软件跟踪:
trace.h声明跟踪宏,各 .c 文件通过#include "*.tmh"接入,可用 traceview 等工具查看驱动内部调试输出; - ETW 事件日志:清单文件 osrusbfx2.man 描述事件(由 MC.EXE 生成
fx2Events.h,见 osrusbfx2.h)。README 明确三个事件写入事件日志:添加设备例程失败(EventWriteFailAddDevice)、OSR 设备在 USB 1.1 控制器上启动失败(EventWriteSelectConfigFailure)、"重新枚举设备" IOCTL 被调用(EventWriteDeviceReenumerated);另加读/写开始-停止事件用于测量耗时。
事件在源码中的触发位置均可查证:OsrFxEvtDeviceAdd失败分支(Device.c)、SelectInterfaces失败分支(Device.c)、ReenumerateDevice(ioctl.c)。
测试应用 osrusbfx2.exe:命令行全集
usb/kmdf_fx2/exe 目录下的控制台测试程序 osrusbfx2.exe 用CM_Get_Device_Interface_List*系列 API 枚举GUID_DEVINTERFACE_OSRUSBFX2接口并打开设备,然后按命令行选项发起读、写或 IOCTL 请求(源码见 testapp.c)。README 给出的完整命令行选项如下:
| 选项 | 含义 |
|---|---|
-r [n] | 读取 n 字节 |
-w [n] | 写入 n 字节 |
-c [n] | 迭代次数(默认 1) |
-v | 显示详细读取数据 |
-p | 操作条形图、拨码开关、7 段显示 |
-a | 执行异步 I/O 操作 |
-u | 转储 USB 配置与管道信息 |
从 testapp.c 源码可补充若干默认值:测试缓冲BUFFER_SIZE = 1024字节,NUM_ASYNCH_IO = 100,G_ReadLen/G_WriteLen默认 512,迭代计数G_IterationCount默认 1。
操作 7 段显示、拨码开关与条形图
运行osrusbfx2.exe -p会进入交互菜单,选项 1–11 分别对应设置/清除条形图、读写 7 段、读取开关、复位与重新枚举设备。README 给出的完整菜单如下:
1. Light bar 2. Clear bar 3. Light entire bar graph 4. Clear entire bar graph 5. Get bar graph state 6. Get switch state 7. Get switch interrupt message 8. Get 7 segment state 9. Set 7 segment state 10. Reset the device 11. Re-enumerate the device 12. Exit Selection:该菜单与 testapp.c 中的INPUT_FUNCTION枚举一一对应(LIGHT_ONE_BAR = 1…REENUMERATE_DEVICE = 11)。选项 7(Get switch interrupt message)比较特殊:它发出IOCTL_OSRUSBFX2_GET_INTERRUPT_MESSAGE,请求被驱动挂起到InterruptMsgQueue,直到中断端点传来开关状态变化消息才完成返回。
复位与重新枚举设备
在-p菜单中选10复位设备(走IOCTL_OSRUSBFX2_RESET_DEVICE→ 停止管道 →WdfUsbTargetDeviceResetPortSynchronously→ 重启管道),选11重新枚举设备(走IOCTL_OSRUSBFX2_REENUMERATE_DEVICE→ 发送USBFX2LK_REENUMERATE厂商命令让设备重新接入总线)。
批量端点读写:典型命令与双缓冲原理
README 给出以下可直接复制的命令:
osrusbfx2.exe -r 64:从批量 IN 端点读 64 字节;osrusbfx2.exe -w 64:向批量 OUT 端点写 64 字节;osrusbfx2.exe -r 64 -w 64 -c 100 -v:先写 64 字节到 OUT 端点(Pipe 1),再从 IN 端点(Pipe 2)读 64 字节,比较读写缓冲是否一致;一致则重复 100 次;osrusbfx2.exe -a:以异步 I/O 方式无限循环读写设备。
双缓冲的行为约束(README 原文要点,务必理解):批量端点始终双缓冲,按工作速度(全速/高速)缓冲区为 64/512 字节。读请求在缓冲区为空时不会完成;写请求在缓冲区满时不会完成。做同步读时务必确保端点缓冲中有数据——例如对运行在全速模式的设备发送 512 字节写请求:由于端点双缓冲,总容量为 256 字节,前 256 字节填满缓冲后写请求会在 USB 栈中等待缓冲被清空;此时再开一个应用实例读 512 字节,两个请求才会都成功完成。换句话说,测试中"先写后读"并用 -c 循环比对,正是利用回环设备把写入的数据原样搬回,从而验证批量通路完整性的标准手法。
显示描述符:-u 输出解读
运行osrusbfx2.exe -u会转储配置、接口与端点描述符。设备运行在高速模式时的输出(README 原样):
=================== USB_CONFIGURATION_DESCRIPTOR bLength = 0x9, decimal 9 bDescriptorType = 0x2 ( USB_CONFIGURATION_DESCRIPTOR_TYPE ) wTotalLength = 0x27, decimal 39 bNumInterfaces = 0x1, decimal 1 bConfigurationValue = 0x1, decimal 1 iConfiguration = 0x4, decimal 4 bmAttributes = 0xa0 ( USB_CONFIG_BUS_POWERED ) MaxPower = 0x32, decimal 50 ----------------------------- USB_INTERFACE_DESCRIPTOR #0 bLength = 0x9 bDescriptorType = 0x4 ( USB_INTERFACE_DESCRIPTOR_TYPE ) bInterfaceNumber = 0x0 bAlternateSetting = 0x0 bNumEndpoints = 0x3 bInterfaceClass = 0xff bInterfaceSubClass = 0x0 bInterfaceProtocol = 0x0 bInterface = 0x0 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe00 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x81 ( INPUT ) bmAttributes= 0x3 ( USB_ENDPOINT_TYPE_INTERRUPT ) wMaxPacketSize= 0x49, decimal 73 bInterval = 0x1, decimal 1 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe01 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x6 ( OUTPUT ) bmAttributes= 0x2 ( USB_ENDPOINT_TYPE_BULK ) wMaxPacketSize= 0x200, decimal 512 bInterval = 0x0, decimal 0 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe02 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x88 ( INPUT ) bmAttributes= 0x2 ( USB_ENDPOINT_TYPE_BULK ) wMaxPacketSize= 0x200, decimal 512 bInterval = 0x0, decimal 0设备运行在全速模式时的输出,仅两处不同(README 原样):
=================== USB_CONFIGURATION_DESCRIPTOR bLength = 0x9, decimal 9 bDescriptorType = 0x2 ( USB_CONFIGURATION_DESCRIPTOR_TYPE ) wTotalLength = 0x27, decimal 39 bNumInterfaces = 0x1, decimal 1 bConfigurationValue = 0x1, decimal 1 iConfiguration = 0x3, decimal 3 bmAttributes = 0xa0 ( USB_CONFIG_BUS_POWERED ) MaxPower = 0x32, decimal 50 ----------------------------- USB_INTERFACE_DESCRIPTOR #0 bLength = 0x9 bDescriptorType = 0x4 ( USB_INTERFACE_DESCRIPTOR_TYPE ) bInterfaceNumber = 0x0 bAlternateSetting = 0x0 bNumEndpoints = 0x3 bInterfaceClass = 0xff bInterfaceSubClass = 0x0 bInterfaceProtocol = 0x0 bInterface = 0x0 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe00 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x81 ( INPUT ) bmAttributes= 0x3 ( USB_ENDPOINT_TYPE_INTERRUPT ) wMaxPacketSize= 0x49, decimal 73 bInterval = 0x1, decimal 1 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe01 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x6 ( OUTPUT ) bmAttributes= 0x2 ( USB_ENDPOINT_TYPE_BULK ) wMaxPacketSize= 0x40, decimal 64 bInterval = 0x0, decimal 0 ------------------------------ USB_ENDPOINT_DESCRIPTOR for Pipe02 bLength = 0x7 bDescriptorType = 0x5 ( USB_ENDPOINT_DESCRIPTOR_TYPE ) bEndpointAddress= 0x88 ( INPUT ) bmAttributes= 0x2 ( USB_ENDPOINT_TYPE_BULK ) wMaxPacketSize= 0x40, decimal 64 bInterval = 0x0, decimal 0对比两组输出可验证 README 中的设备规格:高速模式批量端点wMaxPacketSize = 0x200(512 字节),全速模式为0x40(64 字节);中断端点 Pipe00 在两种模式下都是0x49(73 字节)、bInterval = 1;接口包含 3 个端点、厂商类(bInterfaceClass = 0xff)、总线供电(bmAttributes = 0xa0)。注意高速中断端点的wMaxPacketSize(73 字节)并不完全符合 USB 规范——这正是 README 提到的"OSR FX2 板在 USB 1.1 端口上启动失败"事件的根源,也解释了SelectInterfaces中的特殊错误分支。
设备元数据部署与驱动测试
usb/kmdf_fx2/deviceMetadata 目录包含设备元数据包(B4D697F5-1C56-4807-ACCD-B28C09D37FF0.devicemetadata-ms)。README 明确指出:安装设备前必须先把设备元数据复制到系统。元数据包的作用是建立设备与"受限设备接口 + 自定义能力"的关联,从而允许 Custom driver access 示例(或你基于Windows.Devices.Custom编写的 UWP 应用)访问该接口;有关如何更新与部署设备元数据,可参考 Custom driver access 示例中的说明。
驱动测试有两种途径:
- Custom driver access 示例:作为正式的端到端测试方法,验证受限接口的授权访问链路;
- osrusbfx2.exe 测试应用:本文上一节详述的命令行工具,直接枚举驱动注册的接口并发起读写/IOCTL。
统一跟踪(Unified Tracing):查看驱动事件
驱动使用Event Tracing for Windows(ETW)记录事件。要查看事件,必须先安装 Provider 清单。在提升权限的命令提示符中运行:
wevtutil im osrusbfx2.man注册清单后系统即可获得事件解码所需路径信息。OSR 事件日志位于事件查看器的Event Viewer\Applications and Services Logs\OSRUSBFx2\Operational channel通道;通过 osrusbfx2.exe 触发一次设备重新枚举,就会向该日志写入一个事件。
使用系统自带工具 logman / tracerpt
启动跟踪:
logman start sample -o osrusbfx2.etl -ets -p OSRUSBFX2产生活动:运行测试应用,例如
osrusbfx2.exe -a;停止跟踪:
Logman stop sample查看跟踪文件:
tracerpt -of csv OSRUSBFX2.etl
-p OSRUSBFX2指定 Provider 名(与清单中的 Provider 名称对应),-o指定输出 ETL 文件,-ets表示直接使用事件跟踪会话。
使用 Xperf(Windows Performance Toolkit)
启动跟踪:
xperf -start sample -f osrusbfx2.etl -on OSRUSBFX2产生活动:运行
osrusbfx2.exe -a;停止跟踪:
xperf -stop sample查看跟踪文件:
xperfview OSRUSBFX2.etl
借助驱动写入的EventWriteReadStart/ReadStop、EventWriteWriteStart/WriteStop事件,可以精确测量每次批量读/写的时间开销——这是 README 推荐的性能观测方法。
工程构建与安装要点
- 用 Visual Studio 打开 kmdf_fx2.sln(需安装 WDK),驱动工程 osrusbfx2.vcxproj 会生成驱动二进制,测试工程 osrusbfx2.vcxproj 生成 osrusbfx2.exe;
- 驱动 INF 模板为 osrusbfx2.inx,构建时由 Stampinf 处理生成 .inf;仓库还提供 exe/test.cmd 辅助测试脚本(具体用法可查看脚本内容);
- 安装驱动前先部署 deviceMetadata 元数据包,否则受限接口无法被授权应用访问;
- 由于接口被标记为 Restricted,普通控制台程序直接打开设备会失败;osrusbfx2.exe 之所以可用,是因为示例的受限设置面向 Custom driver access 链路设计——若要在本地直接跑通测试应用,需结合实际部署的元数据与能力声明情况配置环境。
小结:一份可以"照着抄"的 KMDF USB 驱动范本
osrusbfx2 示例的工程价值在于它把 KMDF USB 驱动的所有关键知识点压缩在一个可编译、可测试的小工程里:
- 设备初始化:
EvtDevicePrepareHardware+SelectInterfaces的标准姿势; - I/O 模型:默认并行队列(IOCTL)+ 两个顺序队列(读/写)+ 手动队列(挂起等待中断)的分工;
- 异步传输:
WdfUsbTargetPipeFormatRequestForRead/Write+WdfRequestSend+ 完成例程; - 中断数据:continuous reader 免去驱动自行管理重读的繁琐;
- 电源管理:选择性挂起(10 秒空闲超时)+ wait-wake + D0Entry/D0Exit 启停管道;
- 控制传输:厂商请求封装为
WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR同步发送; - 可观测性:ETW 事件日志(.man 清单)+ WPP 跟踪双轨并行;
- 用户态配合:附送完整命令行测试工具与设备元数据包,开箱即可验证。
无论你是要写自己的第一个 USB 函数驱动,还是想研究 KMDF 批量/中断传输与电源管理的成熟写法,都可以把 usb/kmdf_fx2 作为起点:先按 README 的命令在 FX2 学习板上跑通回环读写,再对照本文的源码定位表逐文件深入,最后用wevtutil im+logman/xperf观察驱动行为,即可完成从"能跑"到"看懂"再到"能改"的完整闭环。
- 示例工程
【免费下载链接】Windows-driver-samples
This repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.
相关推荐
Windows-driver-samples USB驱动:UMDF2与KMDF USB设备开发对比
Windows driver samples USB驱动:UMDF2与KMDF USB设备开发对比 你是否在开发USB设备驱动时纠结选择用户模式还是内核模式?本
示例工程使用 OSR FX2 学习套件掌握 DCHU 通用驱动设计:Windows-driver-samples 中的三套 INF 打包方案详解
使用 OSR FX2 学习套件掌握 DCHU 通用驱动设计:Windows driver samples 中的三套 INF 打包方案详解 导读 本篇文章以 ge
示例工程Windows 驱动示例:Toaster 系列 KMDF 驱动开发实战(部署、构建与源码解析)
Windows 驱动示例:Toaster 系列 KMDF 驱动开发实战(部署、构建与源码解析) 本篇文章基于 Windows driver samples 仓库
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考