Windows USB驱动开发实战教程(基于WDF框架)
简介:Windows USB驱动开发是实现设备与操作系统高效交互的关键环节。本教程围绕Windows Driver Frameworks(WDF)展开,深入讲解KMDF与UMDF两种开发模型的应用方法。涵盖USB驱动架构设计、设备枚举、接口配置、数据传输、错误调试等核心技术,并提供完整示例代码与开发文档,帮助开发者构建稳定高效的USB设备驱动程序,适用于各类Windows平台驱动开发实践。
1. Windows USB驱动开发概述
USB驱动在现代计算机系统中扮演着至关重要的角色,它不仅实现了硬件设备与操作系统之间的高效通信,还直接影响设备的功能完整性与性能表现。Windows平台通过一套完整的驱动模型与框架,为USB设备提供了强大的支持。
从系统架构来看,Windows驱动模型经历了从早期的WDM(Windows Driver Model)到WDF(Windows Driver Frameworks)的演进,极大地简化了开发流程并提升了稳定性和可维护性。USB驱动作为其中的重要组成部分,广泛应用于存储、通信、输入设备等多个领域。
本章将从USB协议基础入手,逐步引导读者理解Windows平台下USB驱动开发的基本原理与结构,为后续章节中使用KMDF和UMDF进行实际开发奠定理论基础。
2. Windows Driver Frameworks(WDF)框架介绍
WDF(Windows Driver Frameworks)是微软为了简化Windows驱动程序开发而推出的一套现代驱动开发框架。它分为两个主要部分: KMDF(Kernel-Mode Driver Framework) 和 UMDF(User-Mode Driver Framework) 。这两种框架分别适用于不同的应用场景,开发者可以根据设备特性和性能需求选择合适的模型进行开发。
WDF通过对象模型、事件驱动机制和统一的API接口,降低了传统驱动开发的复杂性。它不仅简化了设备管理、电源管理、即插即用(PnP)处理等核心功能的实现,还增强了驱动程序的稳定性和可维护性。
本章将围绕WDF框架的基本架构、核心对象模型以及开发环境搭建等内容展开,为后续深入学习KMDF与UMDF驱动开发打下坚实基础。
2.1 WDF框架的基本架构
2.1.1 驱动模型的演变与WDF的引入
Windows驱动开发经历了多个阶段的演变,从最早的 Windows NT驱动模型(NTDDK) 到后来的 Windows Driver Model(WDM) ,再到如今的 Windows Driver Frameworks(WDF) ,每一代模型都在尝试解决前一代的问题。
- NTDDK时代 :早期的驱动开发完全基于内核模式,开发者需要手动管理资源、同步、电源和PnP状态机,代码复杂度高,容易出错。
- WDM模型 :引入了更通用的设备驱动模型,支持PnP和电源管理,但开发者仍然需要大量底层代码,且调试困难。
- WDF框架 :微软在Windows Vista之后推出了WDF,将面向对象的思想引入驱动开发,通过封装底层复杂逻辑,让开发者更专注于业务逻辑实现。
WDF框架的出现,标志着Windows驱动开发进入了一个更高效、更安全、更易维护的新阶段。
WDF的两个子框架
| 框架类型 | 全称 | 模式 | 特点 | 适用场景 |
|---|---|---|---|---|
| KMDF | Kernel-Mode Driver Framework | 内核模式 | 高性能、直接访问硬件 | 需要低延迟和高吞吐量的设备,如存储、网卡、USB核心设备 |
| UMDF | User-Mode Driver Framework | 用户模式 | 安全性高、稳定性好 | 对性能要求不高但稳定性要求高的设备,如打印机、HID设备 |
注 :KMDF运行在内核空间,拥有更高的执行权限,适用于需要高性能和硬件直接访问的场景;而UMDF运行在用户空间,隔离性强,适用于对系统稳定性要求较高的设备。
2.1.2 KMDF与UMDF的区别与适用场景
虽然KMDF和UMDF都属于WDF家族,但它们在执行环境、性能、调试方式等方面存在显著差异。
执行环境与安全性对比
| 特性 | KMDF | UMDF |
|---|---|---|
| 执行模式 | 内核模式 | 用户模式 |
| 权限 | 高(可访问所有系统资源) | 低(受限于用户权限) |
| 安全性 | 相对较低(崩溃可能导致系统崩溃) | 高(崩溃仅影响自身进程) |
| 调试方式 | 内核调试器(WinDbg) | 用户调试器(Visual Studio) |
性能与适用场景
- KMDF :适用于需要高性能、实时响应和直接访问硬件资源的设备,如高速USB设备、PCIe设备、网络设备等。
- UMDF :适用于对性能要求不极端、但需要更高稳定性和安全性的设备,如HID设备、打印机、扫描仪等。
开发模型对比
| 特性 | KMDF | UMDF |
|---|---|---|
| 对象模型 | 支持完整的WDF对象模型 | 支持有限的WDF对象模型 |
| I/O请求处理 | 异步、可中断 | 异步、基于线程池 |
| 通信机制 | 通过IRP | 通过WDF和COM接口 |
| 调试便捷性 | 复杂 | 简单 |
总结 :如果设备需要高性能、直接访问硬件资源,则选择KMDF;如果设备对安全性、稳定性要求高,且不需要高性能,则选择UMDF。
2.2 WDF的核心对象与事件模型
WDF框架采用面向对象的设计思想,通过一系列核心对象来封装驱动程序的运行机制和设备行为。这些对象包括设备对象(WDFDEVICE)、队列对象(WDFQUEUE)、I/O请求对象(WDFREQUEST)等。
2.2.1 设备对象(WDFDEVICE)与队列对象(WDFQUEUE)
WDFDEVICE(设备对象)
WDFDEVICE 是WDF中最核心的对象之一,表示一个设备实例。每个驱动程序通常会创建一个或多个设备对象来管理与其关联的硬件设备。
WDFDEVICE device;
WDFDEVICE_INIT* deviceInit;
deviceInit = WdfControlDeviceInitAllocate(driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL);
WdfDeviceCreate(&deviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
代码解析 :
- WdfControlDeviceInitAllocate :为控制设备分配初始化结构体。
- WdfDeviceCreate :创建一个设备对象。
- device :用于后续操作该设备的句柄。
WDFQUEUE(队列对象)
WDFQUEUE 用于管理I/O请求队列,驱动通过队列接收来自应用程序的读写请求。每个设备对象可以有多个队列,用于处理不同类型的I/O请求。
WDFQUEUE queue;
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
queueConfig.EvtIoRead = MyReadHandler;
queueConfig.EvtIoWrite = MyWriteHandler;
WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
代码解析 :
- WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE :初始化队列配置结构。
- WdfIoQueueCreate :创建队列对象。
- EvtIoRead 和 EvtIoWrite :分别注册读写请求的处理回调函数。
对象之间的关系图(Mermaid流程图)
graph TD
A[WDFDRIVER] --> B[WDFDEVICE]
B --> C[WDFQUEUE]
C --> D[WDFREQUEST]
D --> E[User Application]
说明 :驱动对象(WDFDRIVER)创建设备对象(WDFDEVICE),设备对象创建队列对象(WDFQUEUE),队列接收I/O请求对象(WDFREQUEST),最终由用户应用程序发起请求。
2.2.2 事件回调机制与驱动生命周期管理
WDF通过 事件回调机制 来管理设备的生命周期和I/O请求处理。每个对象都可以注册事件处理函数,用于响应特定的操作。
常见的事件回调函数
| 事件类型 | 回调函数名 | 描述 |
|---|---|---|
| EvtDeviceAdd | EvtDriverDeviceAdd | 当设备被添加到系统时调用 |
| EvtDeviceRemove | EvtDeviceRemove | 设备被移除时调用 |
| EvtIoRead | EvtIoRead | 应用程序发起读请求时调用 |
| EvtIoWrite | EvtIoWrite | 应用程序发起写请求时调用 |
| EvtDeviceD0Entry | EvtDeviceD0Entry | 设备从休眠状态唤醒时调用 |
| EvtDeviceD0Exit | EvtDeviceD0Exit | 设备进入休眠状态时调用 |
示例:注册设备添加事件
WDF_DRIVER_CONFIG config;
WDF_DRIVER_CONFIG_INIT(&config, MyDeviceAdd);
config.EvtDriverUnload = MyDriverUnload;
status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, &driver);
代码解析 :
- WDF_DRIVER_CONFIG_INIT :初始化驱动配置结构。
- MyDeviceAdd :设备添加事件的回调函数。
- WdfDriverCreate :创建驱动对象并注册事件。
驱动生命周期管理流程图
graph TD
A[Driver Load] --> B[DriverEntry]
B --> C[EvtDriverDeviceAdd]
C --> D[Device Created]
D --> E[Process I/O Requests]
E --> F[EvtDeviceRemove]
F --> G[Driver Unload]
说明 :驱动加载时会调用
DriverEntry,之后在设备插入时触发EvtDriverDeviceAdd创建设备对象,设备在运行期间处理I/O请求,设备拔出时调用EvtDeviceRemove,最后驱动卸载时调用EvtDriverUnload。
2.3 WDF开发环境搭建与驱动模板使用
在开始WDF驱动开发之前,开发者需要配置合适的开发环境。Windows驱动开发通常依赖于 Visual Studio 和 Windows Driver Kit(WDK) 的集成环境。
2.3.1 Visual Studio与WDK的集成配置
安装步骤:
- 安装 Visual Studio 2022 或更高版本(建议社区版即可)。
- 安装 Windows Driver Kit(WDK) ,与VS版本匹配。
- 在VS中启用 Windows Driver Development 工作负载。
- 配置目标平台工具集为 WDK 构建工具。
配置调试环境:
- 启用 Windows调试工具(WinDbg) 进行内核调试。
- 配置 调试符号路径 ,如 Microsoft Symbol Server。
- 设置 虚拟机或物理机调试环境 ,如使用 WinDbg + KDNet。
示例:配置符号路径
.sympath SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols
.reload
说明 :
- .sympath :设置符号路径。
- .reload :重新加载符号表。
2.3.2 使用模板创建WDF驱动项目
WDK提供了多个驱动项目模板,方便开发者快速启动项目。
创建步骤:
- 打开 Visual Studio。
- 选择 Create a new project 。
- 搜索 WDF ,选择 KMDF Driver 或 UMDF Driver 模板。
- 输入项目名称,点击 Create 。
- 模板将自动生成基本的驱动框架代码。
示例:KMDF模板生成的代码结构
#include <ntddk.h>
#include <wdf.h>
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
WDF_OBJECT_ATTRIBUTES attributes;
WDFDRIVER driver;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
WDF_DRIVER_CONFIG_INIT(&config, MyDeviceAdd);
return WdfDriverCreate(DriverObject, RegistryPath, &attributes, &config, &driver);
}
NTSTATUS
MyDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
WDFDEVICE device;
return WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
}
代码说明 :
- DriverEntry 是驱动的入口函数。
- WdfDriverCreate 创建驱动对象并注册设备添加事件。
- MyDeviceAdd 是设备添加时的回调函数,负责创建设备对象。
模板项目结构图(Mermaid流程图)
graph TD
A[DriverEntry] --> B[注册设备添加事件]
B --> C[调用MyDeviceAdd]
C --> D[创建设备对象]
D --> E[注册I/O队列]
E --> F[处理I/O请求]
说明 :从驱动入口开始,逐步构建设备和队列对象,最终形成完整的I/O处理流程。
本章通过介绍WDF框架的基本架构、核心对象与事件机制,以及开发环境的搭建流程,帮助读者建立了对WDF驱动开发的整体认知。下一章将深入讲解KMDF驱动开发的实践技巧,包括设备初始化、USB通信等具体实现。
3. Kernel-Mode Driver Framework(KMDF)开发实践
KMDF(Kernel-Mode Driver Framework)是Windows驱动开发中用于实现高性能、低延迟、直接访问硬件的首选框架。尤其在USB设备驱动开发中,KMDF因其对硬件的直接访问能力以及对异步I/O和中断处理的良好支持,成为开发核心USB驱动的重要工具。本章将通过实际开发案例,系统性地讲解KMDF驱动的基本结构、USB通信实现方式以及调试部署方法,帮助读者掌握从开发到测试的完整流程。
3.1 KMDF驱动的基本结构与入口函数
KMDF驱动程序本质上是一个内核模式驱动,其结构遵循Windows驱动模型的基本规范,但通过WDF框架封装了许多底层细节,使开发者能够专注于设备功能的实现。KMDF驱动的核心结构由入口函数、设备添加回调函数、设备对象与队列对象等组成。
3.1.1 EvtDriverDeviceAdd事件处理函数
当系统检测到一个与驱动匹配的设备插入时,WDF框架会调用驱动的 EvtDriverDeviceAdd 回调函数。该函数负责初始化设备对象、配置硬件资源、注册事件处理程序等。
以下是一个典型的 EvtDriverDeviceAdd 函数示例:
NTSTATUS
EvtDriverDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
WDFDEVICE device;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
MY_DEVICE_CONTEXT* deviceContext;
UNREFERENCED_PARAMETER(Driver);
// 设置设备上下文
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, MY_DEVICE_CONTEXT);
deviceAttributes.EvtCleanupCallback = MyDeviceCleanup;
// 创建设备对象
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (!NT_SUCCESS(status)) {
return status;
}
// 获取设备上下文
deviceContext = GetMyDeviceContext(device);
// 初始化设备资源
status = InitializeDeviceResources(device);
if (!NT_SUCCESS(status)) {
return status;
}
// 注册设备I/O队列
status = ConfigureIoQueues(device);
if (!NT_SUCCESS(status)) {
return status;
}
return status;
}
代码逻辑分析与参数说明:
-
WDFDRIVER Driver:当前驱动对象的句柄。 -
PWDFDEVICE_INIT DeviceInit:用于初始化设备的结构体,包含设备的基本属性、硬件资源等信息。 -
WDF_OBJECT_ATTRIBUTES:定义设备对象的属性,包括上下文结构、清理回调函数等。 -
WdfDeviceCreate:创建设备对象,并关联上下文。 -
InitializeDeviceResources:自定义函数,用于分配和初始化设备所需的内存、中断等资源。 -
ConfigureIoQueues:设置I/O队列,用于处理来自用户模式的I/O请求。
该函数是KMDF驱动的起点,决定了设备的创建和资源初始化流程,是整个驱动运行的基础。
3.1.2 驱动初始化与设备对象创建
KMDF驱动的入口函数为 DriverEntry ,它负责注册驱动对象并设置 EvtDriverDeviceAdd 回调函数。以下是典型代码:
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
WDF_DRIVER_CONFIG config;
WDF_DRIVER_CONFIG_INIT(&config, EvtDriverDeviceAdd);
status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
return status;
}
代码逻辑分析与参数说明:
-
PDRIVER_OBJECT DriverObject:操作系统传入的驱动对象指针。 -
PUNICODE_STRING RegistryPath:注册表路径,通常用于加载配置信息。 -
WDF_DRIVER_CONFIG_INIT:初始化驱动配置结构,并绑定设备添加回调函数。 -
WdfDriverCreate:创建WDF驱动对象,注册回调函数。
该函数是KMDF驱动的启动入口,执行后系统将开始监听设备插入事件,并在设备插入时调用 EvtDriverDeviceAdd 完成设备初始化。
3.2 USB设备通信基础
KMDF框架为USB设备提供了丰富的接口函数,简化了设备通信流程。本节将重点介绍如何通过WDFUSB接口与USB设备进行控制传输。
3.2.1 使用WDFUSB接口与设备交互
WDFUSB接口是KMDF为USB设备通信封装的一组函数集,开发者可以通过 WdfUsbTargetDeviceCreate 创建USB设备对象,并通过该对象进行数据传输。
以下是创建USB设备对象的示例代码:
NTSTATUS
InitializeUsbDevice(
_In_ WDFDEVICE device
)
{
NTSTATUS status;
WDF_USB_DEVICE_CONFIG config;
WDFUSBDEVICE usbDevice;
WDF_USB_DEVICE_CONFIG_INIT(&config);
config.UsbDevice = WdfUsbTargetDeviceCreate(device, WDF_NO_OBJECT_ATTRIBUTES, &usbDevice);
if (!NT_SUCCESS(status)) {
return status;
}
// 存储USB设备句柄到设备上下文中
MY_DEVICE_CONTEXT* devContext = GetMyDeviceContext(device);
devContext->UsbDevice = usbDevice;
return status;
}
代码逻辑分析与参数说明:
-
WDF_USB_DEVICE_CONFIG_INIT:初始化USB设备配置结构。 -
WdfUsbTargetDeviceCreate:创建USB设备对象,用于后续通信。 -
devContext->UsbDevice:将设备句柄保存到上下文中,便于后续操作使用。
3.2.2 发送控制传输请求(WdfUsbTargetDeviceSendControlTransfer)
控制传输是USB协议中最基本的数据传输方式,常用于设备配置、状态查询等操作。KMDF通过 WdfUsbTargetDeviceSendControlTransfer 函数实现控制传输。
以下是一个发送控制传输请求的示例:
NTSTATUS
SendGetDeviceDescriptor(
_In_ WDFUSBDEVICE usbDevice
)
{
NTSTATUS status;
WDFMEMORY memory;
USB_DEVICE_DESCRIPTOR* devDesc;
size_t length = sizeof(USB_DEVICE_DESCRIPTOR);
// 分配内存用于存储设备描述符
status = WdfMemoryCreate(WDF_NO_OBJECT_ATTRIBUTES, NonPagedPoolNx, 0, length, &memory, (PVOID*)&devDesc);
if (!NT_SUCCESS(status)) {
return status;
}
// 发送控制传输请求获取设备描述符
status = WdfUsbTargetDeviceSendControlTransfer(usbDevice,
NULL,
WDF_DEFAULT_QUEUE,
&TransferParams,
memory,
&length,
NULL);
if (NT_SUCCESS(status)) {
// 处理获取到的设备描述符
DbgPrint("Device Vendor ID: %x\n", devDesc->idVendor);
DbgPrint("Device Product ID: %x\n", devDesc->idProduct);
}
WdfMemoryFree(memory);
return status;
}
代码逻辑分析与参数说明:
-
WdfMemoryCreate:分配非分页内存用于存储设备描述符。 -
WdfUsbTargetDeviceSendControlTransfer:发送控制传输请求,参数包括设备句柄、队列、传输参数、内存缓冲区等。 -
TransferParams:控制传输的参数结构体,包含请求类型、值、索引等字段。 -
DbgPrint:用于调试输出设备信息。
该函数演示了如何通过控制传输获取设备描述符,是设备初始化阶段的典型操作。
3.3 KMDF驱动的调试与部署
开发完成后,KMDF驱动需要经过严格的调试与部署流程,确保其在真实环境中稳定运行。
3.3.1 使用WinDbg进行内核调试
WinDbg 是微软提供的内核级调试工具,支持实时调试、断点设置、内存查看等功能。以下是使用WinDbg调试KMDF驱动的基本步骤:
- 配置调试环境 :使用两台PC,一台作为目标机(运行驱动),一台作为主机(运行WinDbg)。
- 启用调试模式 :
- 在目标机上运行命令bcdedit /debug on。
- 设置调试端口(如串口或网络):bcdedit /dbgsettings serial debugport=1 baudrate=115200。 - 启动WinDbg :
- 连接目标机:File > Kernel Debug > COM。
- 设置波特率和COM端口号。 - 加载符号文件 :
- 设置符号路径:.sympath SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols
- 加载驱动符号:.reload /f MyDriver.sys - 设置断点与调试 :
- 查看驱动加载地址:lm
- 设置断点:bp MyDriver!MyDeviceAdd
- 启动目标机,触发设备插入事件,进入断点调试。
调试常用命令:
| 命令 | 说明 |
|---|---|
.sympath | 设置符号路径 |
.reload | 重新加载符号 |
lm | 查看加载模块 |
bp | 设置断点 |
g | 继续执行 |
k | 查看调用栈 |
3.3.2 驱动签名与安装测试
KMDF驱动必须经过签名才能在64位Windows系统上加载运行。以下是签名与安装流程:
-
生成驱动签名证书 :
- 使用makecert创建测试证书:
makecert -r -pe -n "CN=MyTestCert" -ss My -sr LocalMachine -sky signature -cy authority -a sha256 -len 2048 -sv MyTestCert.pvk MyTestCert.cer
- 导出证书并安装到受信任的根证书颁发机构。 -
使用signtool签名驱动 :
signtool sign /f MyTestCert.pfx /p password /t http://timestamp.verisign.com/scripts/timstamp.dll MyDriver.sys -
测试驱动安装 :
- 使用devcon工具安装驱动:
devcon install MyDriver.inf USB\VID_1234&PID_5678
- 或使用设备管理器手动安装。 -
启用测试签名模式 :
- 在目标机上运行:
bcdedit /set testsigning on
- 重启系统后即可加载未正式签名的驱动。
驱动安装常见问题与解决办法:
| 问题 | 描述 | 解决方案 |
|---|---|---|
| Error 0x0000007E | 驱动崩溃 | 检查内存访问、指针有效性 |
| Error 0xC0000034 | 文件路径错误 | 检查INF文件路径与注册表项 |
| Error 0x0000000D | 参数错误 | 检查API参数是否正确传递 |
| Error 0x0000001A | 内存不足 | 优化内存分配逻辑,避免过度分配 |
3.4 小结与扩展思考
本章详细介绍了KMDF驱动的基本结构、USB通信机制以及调试与部署流程。KMDF框架通过对象模型和事件回调机制,极大地简化了驱动开发的复杂性,使得开发者可以更专注于设备功能的实现。结合WDFUSB接口,开发者能够快速实现对USB设备的控制与数据传输。
在实际开发中,建议开发者结合USB协议规范,深入理解设备描述符、端点配置、传输类型等内容,以便更好地适配不同类型的USB设备。此外,调试与签名流程虽然繁琐,但却是确保驱动稳定性与安全性的关键环节,必须严格遵循流程执行。
在下一章中,我们将介绍UMDF框架的开发实践,并与KMDF进行对比,帮助开发者根据实际需求选择合适的开发模式。
4. User-Mode Driver Framework(UMDF)开发实践
UMDF(User-Mode Driver Framework)是Windows驱动框架WDF的一部分,它允许开发者在用户模式下编写驱动程序。与KMDF不同,UMDF驱动运行在用户空间,具备更高的安全性与稳定性,尤其适合那些不需要直接访问硬件资源、但需要与USB设备进行交互的场景。本章将围绕UMDF驱动的开发流程、与用户程序的交互方式、调试与性能优化策略进行深入讲解,并通过代码示例和流程图展示其核心实现逻辑。
4.1 UMDF驱动的开发流程
UMDF驱动的开发流程相较于KMDF更加简洁,主要得益于其运行在用户模式下,避免了内核调试的复杂性。开发者可以使用Visual Studio与Windows Driver Kit(WDK)集成的模板来快速创建项目,并通过标准C++接口与USB设备进行通信。
4.1.1 UMDF项目创建与编译配置
UMDF项目可以通过Visual Studio结合WDK模板创建。在安装好Visual Studio和WDK后,开发者可以选择“User Mode Driver”模板来创建UMDF项目。
创建步骤如下:
- 打开 Visual Studio。
- 选择“文件” > “新建” > “项目”。
- 在“新建项目”对话框中,选择“WDF User Mode Driver”模板。
- 输入项目名称并选择保存路径。
- 在“驱动配置向导”中选择“UMDF 2.x”版本,并确认目标平台。
创建完成后,项目结构如下:
MyUMDFDriver/
├── driver/
│ ├── MyDriver.cpp // 驱动入口与设备创建
│ ├── MyDevice.cpp // 设备对象实现
│ └── MyQueue.cpp // I/O队列处理
├── package/
│ └── MyDriver.inf // 安装信息文件
└── build/
编译配置注意事项:
- 需要确保WDK环境变量已正确配置。
- 在项目属性中选择“UMDF Version 2”。
- 启用符号调试支持(用于后续调试)。
代码示例:驱动入口函数
// MyDriver.cpp
#include "Driver.h"
extern "C" NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK);
return WdfDriverCreate(&DriverObject, RegistryPath, &attributes, &config, WDF_NO_HANDLE);
}
逐行解释:
-
WDF_DRIVER_CONFIG:用于配置驱动的基本行为。 -
WDF_OBJECT_ATTRIBUTES_INIT:初始化对象属性。 -
WDF_DRIVER_CONFIG_INIT:设置驱动的事件回调函数,这里设置为无回调。 -
WdfDriverCreate:创建驱动对象,这是UMDF驱动的入口点。
4.1.2 驱动与设备通信的实现方式
UMDF驱动通过I/O队列处理来自用户程序的请求,并通过WDFUSB接口与USB设备进行通信。下面是一个基本的设备通信实现流程。
UMDF与USB通信的流程图:
graph TD
A[用户程序发送IOCTL] --> B[UMDF驱动接收请求]
B --> C{请求类型判断}
C -->|控制传输| D[WdfUsbTargetDeviceSendControlTransfer]
C -->|批量传输| E[WdfUsbTargetPipeRead/WdfUsbTargetPipeWrite]
D --> F[返回结果给用户程序]
E --> F
代码示例:控制传输实现
// MyDevice.cpp
NTSTATUS
MyDevice::OnControlTransfer(
WDFUSBDEVICE usbDevice,
PUSB_DEFAULT_PIPE_SETUP_PACKET setupPacket,
WDFMEMORY outputBuffer
)
{
NTSTATUS status;
ULONG bytesTransferred = 0;
status = WdfUsbTargetDeviceSendControlTransfer(usbDevice,
WDF_NO_CONTEXT,
setupPacket,
outputBuffer,
&bytesTransferred);
if (!NT_SUCCESS(status)) {
KdPrint(("Control transfer failed: %x\n", status));
return status;
}
KdPrint(("Transferred %d bytes\n", bytesTransferred));
return status;
}
参数说明:
-
usbDevice:USB设备对象。 -
setupPacket:控制传输的设置包。 -
outputBuffer:用于接收数据的缓冲区。 -
bytesTransferred:实际传输的字节数。
4.2 UMDF与用户程序的交互
UMDF驱动通过I/O控制(IOCTL)机制与用户程序进行通信。用户程序通过调用Win32 API(如 DeviceIoControl )向驱动发送请求,驱动处理后再返回结果。
4.2.1 IOCTL接口设计与实现
IOCTL是用户模式与驱动交互的核心机制。开发者需要定义一组自定义IOCTL代码,并在驱动中实现相应的处理函数。
定义IOCTL代码(在头文件中)
// MyIoctl.h
#define FILE_DEVICE_MYDRIVER 0x8000
#define IOCTL_MYDRIVER_READ \
CTL_CODE(FILE_DEVICE_MYDRIVER, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYDRIVER_WRITE \
CTL_CODE(FILE_DEVICE_MYDRIVER, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
驱动中实现IOCTL处理函数
// MyQueue.cpp
VOID
MyQueue::OnIoDeviceControl(
WDFQUEUE queue,
WDFREQUEST request,
size_t outputBufferLength,
size_t inputBufferLength,
ULONG ioControlCode
)
{
UNREFERENCED_PARAMETER(queue);
UNREFERENCED_PARAMETER(outputBufferLength);
UNREFERENCED_PARAMETER(inputBufferLength);
switch (ioControlCode) {
case IOCTL_MYDRIVER_READ:
OnIoRead(request);
break;
case IOCTL_MYDRIVER_WRITE:
OnIoWrite(request);
break;
default:
WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
break;
}
}
逻辑分析:
-
WDFQUEUE:处理I/O请求的队列对象。 -
WDFREQUEST:代表一个具体的I/O请求。 -
switch:根据IOCTL代码调用不同的处理函数。 -
WdfRequestComplete:完成请求并返回状态码。
4.2.2 使用Win32 API与驱动通信
用户程序通过Win32 API与UMDF驱动通信,最常用的是 DeviceIoControl 函数。
代码示例:用户程序调用IOCTL
#include <windows.h>
#include "MyIoctl.h"
int main()
{
HANDLE hDevice = CreateFile(L"\\\\.\\MyUMDFDevice",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device\n");
return 1;
}
DWORD bytesReturned;
char buffer[64] = {0};
if (!DeviceIoControl(hDevice,
IOCTL_MYDRIVER_READ,
NULL, 0,
buffer, sizeof(buffer),
&bytesReturned, NULL)) {
printf("IOCTL failed: %d\n", GetLastError());
} else {
printf("Received %d bytes: %s\n", bytesReturned, buffer);
}
CloseHandle(hDevice);
return 0;
}
参数说明:
-
hDevice:通过CreateFile打开的设备句柄。 -
IOCTL_MYDRIVER_READ:要发送的IOCTL代码。 -
buffer:用于接收驱动返回的数据。 -
bytesReturned:实际接收的字节数。
4.3 UMDF驱动的调试与性能分析
UMDF驱动的调试相较于KMDF更加安全和便捷,因为它运行在用户模式下。开发者可以使用UMDF调试器、日志系统以及性能分析工具来优化驱动性能。
4.3.1 使用UMDF调试器和日志系统
UMDF提供了强大的调试支持,开发者可以使用 WdfDeviceWdmGetDeviceObject 获取设备对象,并通过 KdPrint 输出调试信息。
示例代码:日志输出
// MyDevice.cpp
void MyDevice::LogDeviceInfo()
{
UNICODE_STRING deviceName;
WdfDeviceGetDeviceName(m_Device, &deviceName);
KdPrint(("Device Name: %wZ\n", &deviceName));
}
使用WinDbg调试UMDF驱动:
- 启动目标机器并启用调试模式。
- 在主机端使用WinDbg连接目标机。
- 加载驱动后,使用
!wdfkd.wdfdriverinfo查看驱动信息。 - 使用
!wdfkd.wdfdeviceinfo查看设备状态。
4.3.2 性能瓶颈识别与优化策略
在UMDF驱动中,常见的性能瓶颈包括:
- I/O请求处理延迟
- 内存拷贝效率低
- USB传输带宽未充分利用
优化策略:
| 优化方向 | 具体措施 |
|---|---|
| I/O请求处理 | 使用异步I/O,避免阻塞主线程 |
| 数据传输 | 使用批量传输代替控制传输,提高吞吐量 |
| 内存管理 | 使用WDF内存对象避免频繁分配与释放 |
| 日志输出 | 仅在调试模式下启用日志输出,减少性能损耗 |
示例:异步I/O处理优化
// MyQueue.cpp
VOID MyQueue::OnIoRead(WDFREQUEST request)
{
// 使用异步方式提交请求
WdfRequestMarkCancelable(request, MyCancelCallback);
WdfRequestForwardToIoQueue(request, m_ReadQueue);
}
VOID MyCancelCallback(WDFREQUEST request)
{
KdPrint(("Request canceled\n"));
WdfRequestComplete(request, STATUS_CANCELLED);
}
逻辑分析:
-
WdfRequestMarkCancelable:标记请求为可取消,防止阻塞。 -
WdfRequestForwardToIoQueue:将请求转发到另一个队列异步处理。 -
WdfRequestComplete:在处理完成后完成请求并返回结果。
总结
UMDF驱动为开发者提供了一个安全、稳定的用户模式开发环境,特别适合与USB设备进行通信的场景。本章从项目创建、通信机制、用户交互到调试优化,全面讲解了UMDF驱动的开发流程,并通过代码示例和流程图展示了核心实现方式。UMDF虽然在性能上略逊于KMDF,但在易用性和安全性方面具有显著优势,适合大多数USB设备驱动开发的需求。在后续章节中,我们将进一步探讨KMDF与UMDF之间的差异,并结合实际案例进行对比分析。
5. USB设备枚举与描述符解析
USB设备枚举是操作系统识别和配置设备的关键过程,而描述符解析则是获取设备属性的基础。本章将从底层协议角度深入讲解USB枚举流程及其在驱动中的实现。通过本章内容,读者将掌握USB枚举的详细流程、描述符结构的解析方法,以及如何在WDF框架中实现设备描述符的获取与处理。
5.1 USB设备枚举流程详解
5.1.1 枚举阶段的控制传输过程
USB设备枚举是主机识别设备并分配地址的过程。当设备插入主机端口后,主机通过一系列标准控制请求与设备通信,获取设备的基本信息,并为其分配一个唯一的地址(Address),以便后续通信。
USB枚举的主要步骤如下:
| 步骤 | 描述 |
|---|---|
| 1 | 设备插入,主机检测到设备连接(通过D+/D-线状态变化) |
| 2 | 主机发送 Get_Descriptor 请求(使用默认地址0),获取设备描述符 |
| 3 | 主机分配一个新的唯一地址给设备 |
| 4 | 主机使用新地址再次获取设备描述符和配置描述符 |
| 5 | 主机选择配置,设备进入配置状态 |
| 6 | 设备准备就绪,可进行数据传输 |
控制传输的基本结构:
控制传输由三阶段组成:
- Setup阶段 :主机发送8字节的请求数据,包含请求类型、值、索引和长度。
- Data阶段 :根据请求,主机或设备发送或接收数据。
- Status阶段 :用于确认传输完成。
// 示例:使用WDF发送控制传输请求
NTSTATUS SendGetDeviceDescriptor(WDFUSBDEVICE hUsbDevice) {
WDF_USB_CONTROL_SETUP_PACKET setupPacket;
WDFMEMORY buffer;
ULONG bytesTransferred;
NTSTATUS status;
// 初始化控制请求
WDF_USB_CONTROL_SETUP_PACKET_INIT(
&setupPacket,
BmRequestDeviceToHost, // 设备到主机
BmRequestTypeStandard,
USB_REQUEST_GET_DESCRIPTOR,
0, // wValue: 0 for device descriptor
0, // wIndex: 0
0 // wLength: 0,表示使用默认长度
);
// 创建内存缓冲区
status = WdfMemoryCreate(
WDF_NO_OBJECT_ATTRIBUTES,
NonPagedPoolNx,
0,
sizeof(USB_DEVICE_DESCRIPTOR),
&buffer,
NULL
);
if (!NT_SUCCESS(status)) {
return status;
}
// 发送控制传输
status = WdfUsbTargetDeviceSendControlTransfer(hUsbDevice,
NULL, // WDFREQUEST 可为 NULL,由框架自动创建
&setupPacket,
buffer,
NULL,
&bytesTransferred
);
if (NT_SUCCESS(status)) {
// 获取描述符内容
PUSB_DEVICE_DESCRIPTOR pDesc = WdfMemoryGetBuffer(buffer, NULL);
KdPrint(("Device Descriptor: bLength=%d, bDescriptorType=%d\n", pDesc->bLength, pDesc->bDescriptorType));
}
WdfObjectDelete(buffer);
return status;
}
代码逻辑分析:
-
WDF_USB_CONTROL_SETUP_PACKET_INIT初始化控制请求包,设置方向为设备到主机,请求类型为标准请求,请求码为USB_REQUEST_GET_DESCRIPTOR。 -
WdfMemoryCreate创建一个内存对象,用于存储设备描述符数据。 -
WdfUsbTargetDeviceSendControlTransfer发送控制传输请求,并接收设备返回的描述符数据。 -
WdfMemoryGetBuffer获取内存缓冲区指针,从而访问设备描述符内容。
5.1.2 地址分配与设备描述符获取
在枚举过程中,主机为设备分配地址是一个关键步骤。设备默认地址为0,主机通过发送 SET_ADDRESS 请求,为设备分配一个唯一的地址(1~127),后续所有通信都基于该地址进行。
SET_ADDRESS请求结构:
| 字段 | 值 |
|---|---|
| bmRequestType | 0x00 (主机到设备,设备请求) |
| bRequest | 0x05 (SET_ADDRESS) |
| wValue | 设备地址 |
| wIndex | 0 |
| wLength | 0 |
代码示例:发送SET_ADDRESS请求
NTSTATUS SetDeviceAddress(WDFUSBDEVICE hUsbDevice, UCHAR address) {
WDF_USB_CONTROL_SETUP_PACKET setupPacket;
WDF_USB_CONTROL_SETUP_PACKET_INIT(
&setupPacket,
BmRequestHostToDevice, // 主机到设备
BmRequestTypeStandard,
USB_REQUEST_SET_ADDRESS,
address, // wValue: 设备地址
0, // wIndex: 0
0 // wLength: 0
);
return WdfUsbTargetDeviceSendControlTransfer(hUsbDevice,
NULL,
&setupPacket,
NULL,
NULL,
NULL);
}
代码逻辑分析:
- 使用
WDF_USB_CONTROL_SETUP_PACKET_INIT构造SET_ADDRESS请求。 -
WdfUsbTargetDeviceSendControlTransfer发送请求,无需数据传输,因此缓冲区为 NULL。 - 成功发送后,设备将使用新地址响应后续请求。
5.2 USB描述符结构与解析方法
5.2.1 设备描述符、配置描述符与接口描述符
USB描述符是设备向主机提供的关于其功能和特性的结构化数据。主要的描述符包括:
| 描述符类型 | 描述 |
|---|---|
| 设备描述符 | 描述设备的基本信息(如USB版本、设备类、厂商ID、产品ID等) |
| 配置描述符 | 描述设备的配置信息(如支持的配置数量、是否自供电、最大功耗等) |
| 接口描述符 | 描述接口的功能(如接口类、端点数量等) |
| 端点描述符 | 描述端点的特性(如地址、传输类型、最大包大小等) |
设备描述符结构(USB_DEVICE_DESCRIPTOR)
typedef struct _USB_DEVICE_DESCRIPTOR {
UCHAR bLength; // 描述符长度(18 bytes)
UCHAR bDescriptorType; // 描述符类型(DEVICE = 0x01)
USHORT bcdUSB; // USB版本号(例如0x0200表示USB 2.0)
UCHAR bDeviceClass; // 设备类
UCHAR bDeviceSubClass; // 子类
UCHAR bDeviceProtocol; // 协议
UCHAR bMaxPacketSize0; // 端点0的最大包大小
USHORT idVendor; // 厂商ID
USHORT idProduct; // 产品ID
USHORT bcdDevice; // 设备版本号
UCHAR iManufacturer; // 厂商字符串索引
UCHAR iProduct; // 产品字符串索引
UCHAR iSerialNumber; // 序列号字符串索引
UCHAR bNumConfigurations; // 支持的配置数量
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;
配置描述符结构(USB_CONFIGURATION_DESCRIPTOR)
typedef struct _USB_CONFIGURATION_DESCRIPTOR {
UCHAR bLength; // 描述符长度(9 bytes)
UCHAR bDescriptorType; // 描述符类型(CONFIGURATION = 0x02)
USHORT wTotalLength; // 整个配置的总长度
UCHAR bNumInterfaces; // 接口数量
UCHAR bConfigurationValue; // 配置值
UCHAR iConfiguration; // 配置字符串索引
UCHAR bmAttributes; // 配置特性(如是否自供电)
UCHAR MaxPower; // 最大功耗(单位为2mA)
} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR;
接口描述符结构(USB_INTERFACE_DESCRIPTOR)
typedef struct _USB_INTERFACE_DESCRIPTOR {
UCHAR bLength; // 描述符长度(9 bytes)
UCHAR bDescriptorType; // 描述符类型(INTERFACE = 0x04)
UCHAR bInterfaceNumber; // 接口编号
UCHAR bAlternateSetting; // 替代表设置
UCHAR bNumEndpoints; // 端点数量(不包括端点0)
UCHAR bInterfaceClass; // 接口类
UCHAR bInterfaceSubClass; // 子类
UCHAR bInterfaceProtocol; // 协议
UCHAR iInterface; // 接口字符串索引
} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;
5.2.2 使用WDF解析并处理描述符信息
在WDF驱动中,可以通过 WdfUsbTargetDeviceGetDeviceDescriptor 和 WdfUsbTargetDeviceSelectConfig 等函数获取并处理描述符信息。
示例:获取设备描述符
NTSTATUS GetAndPrintDeviceDescriptor(WDFUSBDEVICE hUsbDevice) {
USB_DEVICE_DESCRIPTOR deviceDesc;
NTSTATUS status;
status = WdfUsbTargetDeviceGetDeviceDescriptor(hUsbDevice, &deviceDesc);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to get device descriptor: 0x%x\n", status));
return status;
}
KdPrint(("Device Descriptor:\n"));
KdPrint(("\tbLength: %d\n", deviceDesc.bLength));
KdPrint(("\tbDescriptorType: %d\n", deviceDesc.bDescriptorType));
KdPrint(("\tbcdUSB: 0x%x\n", deviceDesc.bcdUSB));
KdPrint(("\tidVendor: 0x%x\n", deviceDesc.idVendor));
KdPrint(("\tidProduct: 0x%x\n", deviceDesc.idProduct));
KdPrint(("\tbNumConfigurations: %d\n", deviceDesc.bNumConfigurations));
return STATUS_SUCCESS;
}
代码逻辑分析:
-
WdfUsbTargetDeviceGetDeviceDescriptor获取设备描述符。 - 使用
KdPrint输出描述符关键字段,便于调试和日志记录。
示例:获取配置描述符并选择活动配置
NTSTATUS SelectFirstConfiguration(WDFUSBDEVICE hUsbDevice) {
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
WDF_USB_INTERFACE_SETTING_PAIR interfacePair;
NTSTATUS status;
// 初始化配置选择参数
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(
&configParams,
1, // 一个接口设置
&interfacePair
);
// 获取默认配置
status = WdfUsbTargetDeviceSelectConfig(hUsbDevice, &configParams);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to select configuration: 0x%x\n", status));
return status;
}
KdPrint(("Configuration selected successfully.\n"));
return STATUS_SUCCESS;
}
代码逻辑分析:
-
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES初始化配置参数,支持多个接口设置。 -
WdfUsbTargetDeviceSelectConfig选择配置,使设备进入工作状态。 - 成功选择后,驱动可以开始与设备通信。
枚举与描述符解析流程图(Mermaid格式)
graph TD
A[设备插入] --> B[主机检测到连接]
B --> C[发送GET_DESCRIPTOR请求]
C --> D[获取设备描述符]
D --> E[发送SET_ADDRESS请求]
E --> F[设备使用新地址响应]
F --> G[再次GET_DESCRIPTOR]
G --> H[获取配置描述符]
H --> I[发送SET_CONFIGURATION请求]
I --> J[设备进入配置状态]
J --> K[设备准备就绪]
通过本章的学习,读者应能够理解USB设备枚举的完整流程,掌握设备描述符、配置描述符等基本结构,并能在WDF驱动中实现描述符的获取与解析操作。这些知识为后续章节中接口选择、数据传输等高级功能打下了坚实的基础。
6. USB接口选择与配置管理
在USB设备驱动开发中,正确选择和配置接口是实现设备功能的关键步骤。USB设备通常包含多个接口(Interface),每个接口下又可能包含多个端点(Endpoint),而不同的接口代表了设备提供的不同功能。因此,驱动需要能够动态地识别这些接口、选择合适的接口,并配置其对应的端点,以确保设备的正常通信和功能实现。
本章将围绕WDF框架下的接口选择与配置管理展开,重点介绍USB接口与端点的基本概念、接口描述符与端点描述符之间的关系,以及如何在驱动中动态切换接口和配置端点。
6.1 USB接口与端点的概念
USB接口和端点是USB设备功能实现的基础单元。理解它们之间的关系和作用,是正确配置USB设备的关键。
6.1.1 接口描述符与端点描述符的关系
USB设备的接口描述符(Interface Descriptor)定义了该接口的功能、类别(Class)、子类(Subclass)以及支持的端点数量等信息。而端点描述符(Endpoint Descriptor)则描述了每个端点的数据传输方向、类型(如控制、批量、中断或同步传输)以及最大包大小等属性。
下表展示了接口描述符和端点描述符的主要字段对比:
| 字段名称 | 接口描述符字段 | 端点描述符字段 | 含义说明 |
|---|---|---|---|
| bLength | 接口描述符长度 | 端点描述符长度 | 描述符结构的字节长度 |
| bDescriptorType | 接口类型(0x04) | 端点类型(0x05) | 描述符类型标识 |
| bInterfaceNumber | 接口编号 | - | 接口在设备中的编号 |
| bAlternateSetting | 可选设置编号 | - | 同一接口的不同配置选项 |
| bNumEndpoints | 端点数量 | - | 接口下端点的数量 |
| bInterfaceClass | 接口类别 | - | 接口所属的类别(如HID、CDC等) |
| bEndpointAddress | - | 端点地址 | 端点地址,包含方向(IN/OUT)信息 |
| bmAttributes | - | 端点属性 | 端点传输类型(控制、批量等) |
| wMaxPacketSize | - | 最大包大小 | 端点单次传输的最大数据量 |
| bInterval | - | 轮询间隔 | 中断端点的轮询时间间隔(ms) |
示例:获取接口描述符与端点描述符
在WDF驱动中,可以通过以下代码获取接口描述符和端点描述符信息:
WDF_USB_INTERFACE_INFORMATION interfaceInfo;
WDF_USB_ENDPOINT_INFORMATION endpointInfo;
// 获取接口信息
WdfUsbInterfaceGetInformation(UsbInterface, 0, &interfaceInfo);
// 遍历接口下的端点
for (ULONG i = 0; i < interfaceInfo.NumEndpoints; i++) {
WdfUsbInterfaceGetEndpointInformation(UsbInterface, i, &endpointInfo);
// 打印端点地址和传输类型
DbgPrint("Endpoint Address: 0x%x, Type: %d", endpointInfo.EndpointAddress, endpointInfo.TransferType);
}
代码逻辑分析:
-
WdfUsbInterfaceGetInformation函数用于获取指定接口的描述信息。 -
WdfUsbInterfaceGetEndpointInformation用于获取接口下的端点信息。 -
endpointInfo.EndpointAddress包含端点地址和传输方向(高位表示方向)。 -
endpointInfo.TransferType表示该端点支持的传输类型(如WdfUsbTargetPipeTypeBulk)。
6.1.2 多接口设备的驱动适配策略
USB设备可以支持多个接口,例如一个设备可能包含一个HID接口和一个CDC接口。驱动需要根据设备的功能选择合适的接口进行通信。
在WDF中,可以通过如下流程选择接口:
- 枚举设备配置 :使用
WdfUsbDeviceRetrieveAllConfigurations获取所有配置。 - 选择配置 :通过
WdfUsbDeviceSelectConfig选择一个配置。 - 获取接口 :使用
WdfUsbConfigurationGetInterface获取指定接口。 - 设置接口 :调用
WdfUsbInterfaceSetCurrentInterfaceSetting设置接口。
WDF_USB_CONFIGURATION_INFORMATION configInfo;
WDF_USB_INTERFACE_INFORMATION interfaceInfo;
// 获取所有配置
status = WdfUsbDeviceRetrieveAllConfigurations(UsbDevice, &configInfo);
if (!NT_SUCCESS(status)) {
return status;
}
// 选择第一个配置
status = WdfUsbDeviceSelectConfig(UsbDevice, &configInfo.ConfigDescriptor, WDF_NO_HANDLE);
if (!NT_SUCCESS(status)) {
return status;
}
// 获取接口0
status = WdfUsbConfigurationGetInterface(configInfo.ConfigHandle, 0, &interfaceInfo.InterfaceHandle);
if (!NT_SUCCESS(status)) {
return status;
}
// 设置接口为默认设置
status = WdfUsbInterfaceSetCurrentInterfaceSetting(interfaceInfo.InterfaceHandle, 0);
if (!NT_SUCCESS(status)) {
return status;
}
代码逻辑分析:
-
WdfUsbDeviceRetrieveAllConfigurations:检索设备的所有配置信息。 -
WdfUsbDeviceSelectConfig:根据配置描述符选择一个配置。 -
WdfUsbConfigurationGetInterface:从配置中获取指定编号的接口句柄。 -
WdfUsbInterfaceSetCurrentInterfaceSetting:设置接口的当前配置项(Alternate Setting)。
6.2 配置管理与接口切换实现
在实际驱动开发中,设备可能会动态切换接口或更改配置。例如,设备在进入低功耗模式后可能需要切换到不同的接口,或者根据用户输入切换功能模式。此时,驱动必须具备动态管理接口和配置的能力。
6.2.1 选择活动配置与接口
USB设备通常支持多个配置(Configuration),每个配置下包含一组接口。选择正确的配置是驱动开发中的关键步骤。
接口选择流程图(mermaid)
graph TD
A[设备插入] --> B{是否支持多配置?}
B -->|是| C[获取所有配置]
C --> D[遍历配置]
D --> E[根据功能需求选择配置]
E --> F[调用WdfUsbDeviceSelectConfig设置配置]
F --> G[获取接口句柄]
G --> H[调用WdfUsbInterfaceSetCurrentInterfaceSetting设置接口]
H --> I[完成接口配置]
B -->|否| J[使用默认配置]
J --> K[直接获取接口并设置]
示例:动态选择接口
NTSTATUS SelectUsbInterface(WDFUSBDEVICE UsbDevice, UCHAR InterfaceNumber, UCHAR AlternateSetting)
{
NTSTATUS status;
WDF_USB_CONFIGURATION_INFORMATION configInfo;
// 获取当前配置
status = WdfUsbDeviceRetrieveAllConfigurations(UsbDevice, &configInfo);
if (!NT_SUCCESS(status)) {
return status;
}
// 获取指定接口
WDFUSBINTERFACE usbInterface;
status = WdfUsbConfigurationGetInterface(configInfo.ConfigHandle, InterfaceNumber, &usbInterface);
if (!NT_SUCCESS(status)) {
return status;
}
// 设置接口的Alternate Setting
status = WdfUsbInterfaceSetCurrentInterfaceSetting(usbInterface, AlternateSetting);
if (!NT_SUCCESS(status)) {
return status;
}
return STATUS_SUCCESS;
}
代码逻辑分析:
-
WdfUsbConfigurationGetInterface:获取指定编号的接口对象。 -
WdfUsbInterfaceSetCurrentInterfaceSetting:设置接口的可选设置值(Alternate Setting),实现接口切换。
6.2.2 动态切换接口与端点配置
在运行时切换接口是USB设备常见的操作,例如从HID模式切换到CDC模式。为了实现动态切换,驱动需要重新配置接口并重新初始化端点。
示例:接口切换并重新初始化端点
NTSTATUS SwitchUsbInterface(WDFUSBINTERFACE CurrentInterface, UCHAR NewAlternateSetting)
{
NTSTATUS status;
WDF_USB_INTERFACE_INFORMATION newInterfaceInfo;
// 设置新的Alternate Setting
status = WdfUsbInterfaceSetCurrentInterfaceSetting(CurrentInterface, NewAlternateSetting);
if (!NT_SUCCESS(status)) {
return status;
}
// 重新获取接口信息
WdfUsbInterfaceGetInformation(CurrentInterface, NewAlternateSetting, &newInterfaceInfo);
// 重新初始化端点
for (ULONG i = 0; i < newInterfaceInfo.NumEndpoints; i++) {
WDFUSBPIPE pipe;
WdfUsbInterfaceGetConfiguredPipe(CurrentInterface, i, NULL, &pipe);
// 初始化端点缓冲区、设置回调等
WdfUsbTargetPipeSetNoMaximumPacketSizeMpsShortCheck(pipe);
}
return STATUS_SUCCESS;
}
代码逻辑分析:
-
WdfUsbInterfaceSetCurrentInterfaceSetting:切换接口的Alternate Setting。 -
WdfUsbInterfaceGetInformation:获取新配置下的接口信息。 -
WdfUsbInterfaceGetConfiguredPipe:获取端点管道对象,用于后续数据传输。 -
WdfUsbTargetPipeSetNoMaximumPacketSizeMpsShortCheck:设置端点最大包大小策略。
动态切换接口的注意事项:
- 同步操作 :接口切换应确保当前传输已完成,避免出现数据混乱。
- 资源释放与重建 :切换接口后可能需要释放旧端点资源并重新创建。
- 错误处理 :切换失败时应恢复到原始状态,避免设备不可用。
总结
本章详细介绍了USB接口与端点的基本概念、接口描述符与端点描述符之间的关系,以及在WDF框架下如何实现接口的选择与配置管理。通过示例代码和流程图,展示了如何在驱动中动态切换接口并重新配置端点,从而实现设备功能的灵活适配。
下一章将继续深入USB数据传输机制,涵盖控制、批量、中断和同步传输的具体实现方式。
7. USB数据传输机制详解(控制、批量、中断、同步)
USB协议定义了四种基本的数据传输类型: 控制传输 (Control Transfer)、 批量传输 (Bulk Transfer)、 中断传输 (Interrupt Transfer)和 同步传输 (Isochronous Transfer)。每种传输机制具有不同的特性,适用于不同的应用场景。本章将深入分析每种传输机制的工作原理、适用场景,并结合WDF框架中的API实现具体的传输逻辑。
7.1 控制传输与标准请求处理
控制传输是USB协议中最基础的传输类型,主要用于设备的初始化、配置和控制命令的发送。它具有固定的格式,由 Setup 包 、 数据阶段 (可选)和 状态阶段 组成。
7.1.1 控制传输的结构与流程
控制传输的结构如下:
sequenceDiagram
participant Host
participant Device
Host->>Device: Setup Packet (8 bytes)
alt Data Phase
Host->>Device: Data Out
Device-->>Host: ACK
else
Device->>Host: Data In
Host-->>Device: ACK
end
Host->>Device: Status Phase
控制传输的典型应用包括获取设备描述符、设置设备地址、选择配置等。
在WDF中,控制传输的处理通常通过 WdfUsbTargetDeviceSendControlTransfer 函数实现:
NTSTATUS SendCustomControlRequest(WDFUSBDEVICE usbDevice) {
WDF_USB_CONTROL_SETUP_PACKET controlSetup;
NTSTATUS status;
ULONG_PTR bytesTransferred;
UCHAR buffer[64];
// 初始化控制请求包
WDF_USB_CONTROL_SETUP_PACKET_INIT(
&controlSetup,
BmRequestType, // 请求类型
bRequest, // 请求码
wValue, // 参数值
wIndex, // 接口索引
wLength // 数据长度
);
// 发送控制传输
status = WdfUsbTargetDeviceSendControlTransfer(
usbDevice,
NULL, // 默认配置
&controlSetup, // 控制请求包
buffer, // 数据缓冲区
sizeof(buffer), // 缓冲区大小
&bytesTransferred
);
if (!NT_SUCCESS(status)) {
KdPrint(("控制传输失败: 0x%x\n", status));
}
return status;
}
参数说明 :
-BmRequestType:请求方向和类型(如设备到主机、主机到设备)。
-bRequest:请求代码,如USB_REQUEST_GET_DESCRIPTOR。
-wValue:用于指定描述符类型和索引。
-wIndex:接口或端点索引。
-wLength:期望传输的数据长度。
7.1.2 实现自定义控制请求处理
在实际驱动开发中,可能需要处理自定义的控制请求。可以通过实现 EvtIoDeviceControl 回调函数来处理 IOCTL 请求,并在其中调用上述函数发送控制命令。
VOID MyEvtIoDeviceControl(
WDFQUEUE Queue,
WDFREQUEST Request,
size_t OutputBufferLength,
size_t InputBufferLength,
ULONG IoControlCode
) {
UNREFERENCED_PARAMETER(Queue);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
if (IoControlCode == IOCTL_MY_CUSTOM_COMMAND) {
SendCustomControlRequest(MyUsbDevice);
WdfRequestComplete(Request, STATUS_SUCCESS);
}
}
通过这种方式,用户程序可以通过 DeviceIoControl 接口与驱动交互,实现对USB设备的控制操作。
7.2 批量传输的实现与优化
批量传输适用于大数据量、无严格时序要求的数据传输,例如U盘、打印机等设备。它具有较高的带宽利用率,但不保证传输延迟。
7.2.1 批量端点的读写操作
在WDF中,使用 WdfUsbTargetPipeWriteSynchronously 和 WdfUsbTargetPipeReadSynchronously 函数实现同步读写操作。
NTSTATUS ReadFromBulkEndpoint(WDFUSBPIPE bulkPipe) {
UCHAR buffer[512];
size_t bytesRead;
NTSTATUS status;
status = WdfUsbTargetPipeReadSynchronously(
bulkPipe,
buffer,
sizeof(buffer),
NULL,
&bytesRead
);
if (!NT_SUCCESS(status)) {
KdPrint(("批量读取失败: 0x%x\n", status));
}
return status;
}
7.2.2 提高传输效率的策略
- 使用异步传输 :避免阻塞主线程,提高并发处理能力。
- 增大缓冲区大小 :合理设置缓冲区长度,减少传输次数。
- 优化端点配置 :根据设备能力选择合适的最大包长度(wMaxPacketSize)。
7.3 中断传输与实时数据处理
中断传输适用于需要周期性轮询的小数据量传输,例如键盘、鼠标等设备。其特点是 低延迟、小数据包 ,适合实时数据采集。
7.3.1 中断端点的轮询与回调机制
WDF中使用 WdfUsbTargetPipeReadSynchronously 或异步读取方式来处理中断端点数据。
VOID MyEvtUsbInterruptReadComplete(
WDFUSBPIPE Pipe,
WDFMEMORY Buffer,
size_t NumBytesRead
) {
PUCHAR data = WdfMemoryGetBuffer(Buffer, NULL);
KdPrint(("收到中断数据: %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3]));
// 重新提交读请求
WdfUsbTargetPipeReadSynchronously(Pipe, Buffer, NumBytesRead, NULL, NULL);
}
NTSTATUS StartInterruptRead(WDFUSBPIPE interruptPipe) {
WDFMEMORY buffer;
NTSTATUS status;
status = WdfMemoryCreate(
WDF_NO_OBJECT_ATTRIBUTES,
NonPagedPool,
0,
8, // 假设中断端点数据包大小为8字节
&buffer,
NULL
);
if (!NT_SUCCESS(status)) {
return status;
}
// 提交异步读请求
status = WdfUsbTargetPipeReadSynchronously(
interruptPipe,
buffer,
8,
NULL,
NULL
);
return status;
}
7.3.2 实时数据采集与处理案例
在工业控制、传感器采集等场景中,中断传输用于周期性采集设备状态。例如,每10ms读取一次传感器数据,并通过缓冲区传递给应用层处理。
7.4 同步传输与音频设备应用
同步传输适用于对时间敏感的数据流,如音频、视频。它保证数据的传输时序,但不保证可靠性(可能丢包)。
7.4.1 同步传输的时序要求
同步传输具有严格的时序要求,必须在规定的时间帧内完成传输。USB 2.0中,同步传输的数据包大小为1ms一帧,最大为1023字节。
7.4.2 在USB音频设备中的应用示例
对于USB音频设备,同步传输常用于音频流的发送与接收。WDF中可使用 WdfUsbTargetPipeWriteSynchronously 进行同步音频数据发送:
NTSTATUS SendAudioStream(WDFUSBPIPE isoPipe, PVOID audioData, size_t length) {
NTSTATUS status;
status = WdfUsbTargetPipeWriteSynchronously(
isoPipe,
audioData,
length,
NULL,
NULL
);
if (!NT_SUCCESS(status)) {
KdPrint(("同步传输失败: 0x%x\n", status));
}
return status;
}
在音频设备驱动中,还需结合USB描述符判断同步端点的帧间隔,并在每次帧到来时触发数据传输,以保证音频流的连续性。
(本章内容持续深入展开中)
简介:Windows USB驱动开发是实现设备与操作系统高效交互的关键环节。本教程围绕Windows Driver Frameworks(WDF)展开,深入讲解KMDF与UMDF两种开发模型的应用方法。涵盖USB驱动架构设计、设备枚举、接口配置、数据传输、错误调试等核心技术,并提供完整示例代码与开发文档,帮助开发者构建稳定高效的USB设备驱动程序,适用于各类Windows平台驱动开发实践。
更多推荐
所有评论(0)