如何发送 USB 控制传输
本文介绍控制传输的结构以及客户端驱动程序应如何向设备发送控制请求。
关于默认终结点
所有 USB 设备必须支持至少一个名为“默认终结点”的终结点。 任何以默认终结点为目标的传输都称为“控制传输”。 控制传输的目的是使主机能够获取设备信息、配置设备或执行特定于设备的控制操作。
让我们从研究默认终结点的以下特征着手。
- 默认终结点的地址为 0。
- 默认终结点是双向的,也就是说,在一次传输过程中,主机可以向终结点发送数据并从其接收数据。
- 默认终结点在设备级别可用,不在设备的任何接口中定义。
- 一旦在主机和设备之间建立连接,默认终结点就处于活动状态。 甚至在选择配置之前,它就已经处于活动状态。
- 默认终结点的数据包最大大小取决于设备的总线速度。 低速,8 字节;全速和高速,64 字节;超高速,512 字节。
控制传输的布局
由于控制传输是高优先级传输,因此会由主机在总线上保留一定量的带宽。 将为低速和全速设备保留 10% 的带宽;为高速和超高速传输设备保留 20% 的带宽。 现在,让我们看看控制传输的布局。
控制传输分为三个事务:设置事务、数据事务、状态事务。 每个事务包含三类数据包:令牌数据包、数据数据包、握手数据包。
某些字段通用于所有数据包。 这些字段是:
- “同步”字段,指示数据包的开始。
- 数据包标识符 (PID),指示数据包的类型、事务的方向、事务是成功还是失败(如果是握手数据包)。
- EOP 字段,指示数据包的结束。
其他字段取决于数据包的类型。
令牌数据包
每个设置事务都以令牌数据包开头。 下面是该数据包的结构。 主机始终发送令牌数据包。
PID 值指示令牌数据包的类型。 可能的值有:
- 安装程序:指示控制传输中设置事务的开始。
- IN:指示主机正在从设备请求数据(读取大小写)。
- OUT:指示主机正在向设备发送数据(写入大小写)。
- SOF:指示帧的开始。 此类型的令牌数据包包含一个 11 位的帧号。 主机发送 SOF 数据包。 发送此数据包的频率取决于总线速度。 对于全速总线,主机每隔 1 毫秒发送一次数据包;对于高速总线,则每隔 125 微秒发送一次。
数据数据包
紧跟着令牌数据包的是包含有效负载的数据数据包。 每个数据数据包能够包含的字节数取决于默认终结点的数据包最大大小。 数据数据包可以由主机或设备发送,具体取决于传输的方向。
握手数据包
紧跟着数据数据包的是握手数据包。 此数据包的 PID 指示是主机还是设备接收了数据包。 握手数据包可以由主机或设备发送,具体取决于传输的方向。
可以使用任何 USB 分析器(例如 Beagle、Ellisys、LeCroy USB 协议分析器)来查看事务和数据包的结构。 分析器设备显示如何通过线路将数据发送到 USB 设备或从其接收数据。 在此示例中,让我们检查由 LeCroy USB 分析器捕获的某些跟踪。 此示例仅供参考, 不表示 Microsoft 的认可。
设置事务
始终由主机启动控制传输。 为此,主机会发送设置事务。 此事务包含名为“设置令牌”的令牌数据包,后跟一个 8 字节的数据数据包。 以下屏幕截图显示了一个示例性的设置事务。
在前面的跟踪中,主机通过发送设置令牌数据包 #434 来启动控制传输(由 H\)控制传输。 请注意,PID 指定的 SETUP 表示一个设置令牌。 PID 后跟设备地址和终结点地址。 对于控制传输,该终结点地址始终为 0。
接下来,主机发送数据包 #435。 PID 为 DATA0,该值用于数据包排序(在后面讨论)。 PID 后跟 8 个字节,其中包含有关此请求的主要信息。 这 8 个字节指示请求的类型和缓冲区(设备将在其中写入响应)的大小。
所有字节以相反顺序接收。 如 9.3 节所述,我们会看到以下字段和值:
字段 | 大小 | 值 | 说明 |
---|---|---|---|
bmRequestType(参见 9.3.1 bmRequestType) | 1 | 0x80 | 数据传输方向为从设备到主机(D7 为 1) 请求为标准请求(D6…D5 为 0) 请求的接收者为 DEVICE(D4 为 0) |
bRequest(参见 9.3.2 节和表 9-4) | 1 | 0x06 | 请求类型为 GET_DESCRIPTOR。 |
wValue(参见表 9-5) | 2 | 0x0100 | 请求值指示描述符类型为 DEVICE。 |
wIndex(参见 9.3.4 节) | 2 | 0x0000 | 方向为从主机到设备(D7 为 1) 终结点编号为 0。 |
wLength(参见 9.3.5 节) | 2 | 0x0012 | 请求将检索 18 个字节。 |
因此,我们可以得出结论:在此控制(读取)传输中,主机发送请求来检索设备描述符,并指定 18 个字节作为保存该描述符所需的传输长度。 设备发送这 18 个字节的方式取决于默认终结点可以在一个事务中发送多少数据。 该信息包含在设备描述符中,由设备在数据事务中返回。
作为响应,设备发送握手数据包(#436 由 D=)。 请注意,PID 值为 ACK(ACK 数据包)。 这表示设备确认了此事务。
数据事务
现在,让我们看看设备在响应请求时返回的内容。 实际数据在数据事务中传输。
下面是数据事务的跟踪。
在接收到 ACK 数据包后,主机会启动数据事务。 若要启动事务,它会发送一个令牌数据包(#450),其方向为 IN(称为 IN 令牌)。
作为响应,设备发送一个遵循 IN 令牌的数据包(#451)。 此数据数据包包含实际的设备描述符。 第一个字节指示设备描述符的长度,即 18 个字节 (0x12)。 此数据数据包中的最后一个字节指示默认终结点支持的数据包最大大小。 在此示例中,我们看到设备可以通过其默认终结点一次发送 8 个字节。
注意
默认终结点的数据包最大大小取决于设备的速度。 高速设备的默认终结点为 64 个字节;低速设备为 8 个字节。
主机通过向设备发送 ACK 数据包(#452)来确认数据事务。
让我们计算返回的数据量。 在 设置事务中数据包 (#435) 的 wLength 字段中,主机请求了 18 个字节。 在数据事务中,我们看到从设备收到的只有设备描述符的前 8 个字节。 那么,主机如何接收存储在剩余的 10 个字节中的信息? 设备在两个事务中执行此操作:8 个字节,最后 2 个字节。
主机知道了默认终结点的数据包最大大小以后,就会启动新的数据事务,根据数据包大小请求下一部分。
下面是下一数据事务:
主机通过发送 IN 令牌(#463)并从设备请求接下来的 8 个字节来启动上述数据事务。 设备使用包含设备描述符的接下来 8 个字节的数据数据包(#464)进行响应。
收到 8 个字节后,主机会将 ACK 数据包 (#465) 发送到设备。
接下来,主机在另一数据事务中请求最后的 2 个字节,如下所示:
因此,我们看到,为了将 18 个字节从设备传输到主机,主机会跟踪传输的字节数并启动三个数据事务 (8+8+2)。
注意
请注意数据事务 19、23、26 中数据包的 PID。 PID 在 DATA0 和 DATA1 之间交替变换。 该顺序称为数据切换。 在有多个数据事务的情况下,数据切换用于验证数据包顺序。 此方法可确保数据数据包不重复或丢失。
将合并的数据数据包映射到设备描述符的结构(参见表 9-8)以后,我们看到以下字段和值:
字段 | 大小 | 值 | 说明 |
---|---|---|---|
bLength | 1 | 0x12 | 设备描述符的长度,即 18 个字节。 |
bDescriptorType | 1 | 0x01 | 描述符类型为设备。 |
bcdUSB | 2 | 0x0100 | 规范版本号为 1.00。 |
bDeviceClass | 1 | 0x00 | 设备类为 0。 配置中的每个接口都有类信息。 |
bDeviceSubClass | 1 | 0x00 | 子类为 0,因为设备类为 0。 |
bProtocol | 1 | 0x00 | 协议为 0。 此设备不使用任何特定于类的协议。 |
bMaxPacketSize0 | 1 | 0x08 | 终结点的数据包最大大小为 8 个字节。 |
idVendor | 2 | 0x0562 | 电传通信。 |
idProduct | 2 | 0x0002 | USB 麦克风。 |
bcdDevice | 2 | 0x0100 | 指示设备发行版号。 |
iManufacturer | 1 | 0x01 | 制造商字符串。 |
iProduct | 1 | 0x02 | 产品字符串。 |
iSerialNumber | 1 | 0x03 | 序列号。 |
bNumConfigurations | 1 | 0x01 | 配置数。 |
检查这些值即可获得设备的一些初步信息。 设备是低速 USB 麦克风。 默认终结点的数据包最大大小为 8 个字节。 设备支持一种配置。
状态事务
最后,主机会启动最后一个事务:状态事务,从而完成控制传输。
主机使用 OUT 令牌数据包(#481)启动事务。 此数据包的目的是验证设备是否已发送所有请求的数据。 在此状态事务中,不发送数据数据包。 设备使用 ACK 数据包进行响应。 如果发生错误,PID 可能为 NAK 或 STALL。
驱动程序模型
先决条件
在客户端驱动程序能够枚举管道之前,请确保满足以下要求:
客户端驱动程序必须已创建框架 USB 目标设备对象。
如果使用 Microsoft Visual Studio Professional 2012 随附的 USB 模板,则模板代码会执行这些任务。 模板代码会获取目标设备对象的句柄并将其存储在设备上下文中。
KMDF 客户端驱动程序
KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceCreateWithParameters 方法来获取 WDFUSBDEVICE 句柄。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (KMDF) 中的“设备源代码”。
UMDF 客户端驱动程序
UMDF 客户端驱动程序必须通过查询框架目标设备对象获取 IWDFUsbTargetDevice 指针。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (UMDF) 中的“IPnpCallbackHardware 实现和特定于 USB 的任务”。
控制传输最重要的方面是正确设置设置令牌的格式。 在发送请求之前,请收集以下信息集:
- 请求的方向:从主机到设备,或者从设备到主机。
- 请求的接收者:设备、接口、终结点或其他。
- 请求的类别:标准、类或供应商。
- 请求的类型,例如 GET_DESCRIPTPOR 请求。 有关详细信息,请参阅 USB 规范中的 9.5 节。
- wValue 和 wIndex 值。 这些值取决于请求的类型。
可以从官方的 USB 规范中获取所有此类信息。
如果编写 UMDF 驱动程序,请从 OSR USB Fx2 学习工具包的 UMDF 示例驱动程序中获取头文件 Usb_hw.h。 此头文件包含的宏和结构用于设置控制传输的设置数据包的格式。
所有 UMDF 驱动程序必须与内核模式驱动程序通信才能通过设备发送和接收数据。 对于 USB UMDF 驱动程序,内核模式驱动程序始终是 Microsoft 提供的驱动程序 WinUSB (Winusb.sys)。
每当 UMDF 启动程序针对 USB 驱动程序堆栈发出请求时,Windows I/O 管理器就会将该请求发送给 WinUSB。 收到请求后,WinUSB 会处理请求,或者将其转发给 USB 驱动程序堆栈。
Microsoft 定义的用于发送控制传输请求的方法
主机上的 USB 客户端驱动程序启动的大多数控制请求是用于获取有关设备的信息、配置设备或发送供应商控制命令。 所有这些请求可以分为以下类别:
标准请求 在 USB 规范中定义。 发送标准请求的目的是获取有关设备、其配置、接口和终结点的信息。 每个请求的接收者取决于请求的类型。 接收方可以是设备、接口或终结点。
注意
任何控制传输的目标都始终是默认终结点。 接收者是设备的实体,其信息(描述符、状态等)是主机感兴趣的。
可将请求进一步分类为:配置请求、功能请求和状态请求。
- 配置请求 将发送到设备以获取信息,以便主机可以对其进行配置,例如GET_DESCRIPTOR请求。 这些请求也可能是主机发送的写入请求,目的是在设备中设置特定的配置或备用设置。
- 客户端驱动程序发送功能请求 ,以启用或禁用设备、接口或终结点支持的某些布尔设备设置。
- 状态请求 允许主机获取或设置设备、终结点或接口的 USB 定义状态位。
有关详细信息,请参阅 USB 规范版本 2.0 中的 9.4 节。 标准请求类型在头文件 Usbspec.h 中定义。
类请求 由特定的设备类规范定义。
供应商请求 由供应商提供,具体取决于设备支持的请求。
Microsoft 提供的 USB 堆栈处理与设备进行的所有协议通信,如前面的跟踪所示。 此驱动程序会公开设备驱动程序接口 (DDI),后者允许客户端驱动程序以多种方式发送控制传输。 如果客户端驱动程序是 Windows Driver Foundation (WDF) 驱动程序,则它可以直接调用例程来发送常见类型的控制请求。 WDF 本质上支持 KMDF 和 UMDF 的控制传输。
某些类型的控制请求不通过 WDF 公开。 对于这些请求,客户端驱动程序可以使用 WDF 混合模型。 此模型允许客户端驱动程序构建 WDM URB 样式的请求并设置其格式,然后使用 WDF 框架对象发送这些请求。 混合模型仅适用于内核模式驱动程序。
对于 UMDF 驱动程序:
请使用在 usb_hw.h 中定义的帮助器宏和结构。 此头文件随附在 OSR USB Fx2 学习工具包的 UMDF 示例驱动程序中。
请使用下表来确定向 USB 驱动程序堆栈发送控制请求的最佳方式。 如果无法查看此表,请参阅本文中的表。
如果要发送控制请求... | 对于 KMDF 驱动程序... | 对于 UMDF 驱动程序... | 对于 WDM 驱动程序,请生成 URB 结构(帮助程序例程) |
---|---|---|---|
CLEAR_FEATURE:在设备、其配置、接口和终结点中禁用某些功能设置。 请参阅 USB 规范中的 9.4.1 节。 |
|
|
_URB_CONTROL_FEATURE_REQUEST (UsbBuildFeatureRequest) URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT URB_FUNCTION_CLEAR_FEATURE_TO_OTHER |
GET_CONFIGURATION:获取当前的 USB 配置。 请参阅 USB 规范中的 9.4.2 节。 | KMDF 默认选择第一个配置。 若要检索设备定义的配置编号,请执行以下操作:
|
UMDF 默认选择第一个配置。 若要检索设备定义的配置编号,请执行以下操作:
|
_URB_CONTROL_GET_CONFIGURATION_REQUEST URB_FUNCTION_GET_CONFIGURATION |
GET_DESCRIPTOR:获取设备、配置、接口和终结点描述符。 请参阅 USB 规范中的 9.4.3 节。 有关详细信息,请参阅 USB 描述符。 |
调用以下方法: |
调用以下方法: |
_URB_CONTROL_DESCRIPTOR_REQUEST (UsbBuildGetDescriptorRequest) URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE |
GET_INTERFACE:获取接口的当前备用设置。 请参阅 USB 规范中的 9.4.4 节。 |
|
|
_URB_CONTROL_GET_INTERFACE_REQUEST URB_FUNCTION_GET_INTERFACE |
GET_STATUS:从设备、终结点或接口获取状态位。 请参阅 USB 规范中 的 9.4.5 节。 |
|
|
_URB_CONTROL_GET_STATUS_REQUEST (UsbBuildGetStatusRequest) URB_FUNCTION_GET_STATUS_FROM_DEVICE URB_FUNCTION_GET_STATUS_FROM_INTERFACE URB_FUNCTION_GET_STATUS_FROM_ENDPOINT URB_FUNCTION_GET_STATUS_FROM_OTHER。 |
SET_ADDRESS:请参阅 USB 规范中的 9.4.6 部分。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 |
SET_CONFIGURATION:设置配置。 请参阅 USB 规范中的 9.4.7 节。 有关详细信息,请参阅如何选择 USB 设备的配置。 |
默认情况下,KMDF 选择默认配置以及每个接口中的第一个备用设置。 客户端驱动程序可以调用 WdfUsbTargetDeviceSelectConfigType 方法并将 WdfUsbTargetDeviceSelectConfigTypeUrb 指定为请求选项,以这种方式更改默认配置。 然后,你必须格式化此请求的 URB 并将其提交到 USB 驱动程序堆栈。 | 默认情况下,UMDF 选择默认配置以及每个接口中的第一个备用设置。 客户端驱动程序无法更改此配置。 | _URB_SELECT_CONFIGURATION (USBD_SelectConfigUrbAllocateAndBuild) URB_FUNCTION_SELECT_CONFIGURATION |
SET_DESCRIPTOR:更新现有设备、配置或字符串描述符。 请参阅 USB 规范中的 9.4.8 节。 此请求不常用。 但是,USB 驱动程序堆栈会接受来自客户端驱动程序的此类请求。 |
|
|
_URB_CONTROL_DESCRIPTOR_REQUEST URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE |
SET_FEATURE:在设备、其配置、接口和终结点中启用某些功能设置。 请参阅 USB 规范中的 9.4.9 节。 |
|
|
_URB_CONTROL_FEATURE_REQUEST (UsbBuildFeatureRequest) URB_FUNCTION_SET_FEATURE_TO_DEVICE URB_FUNCTION_SET_FEATURE_TO_INTERFACE URB_FUNCTION_SET_FEATURE_TO_ENDPOINT URB_FUNCTION_SET_FEATURE_TO_OTHER |
SET_INTERFACE:更改接口中的备用设置。 请参阅 USB 规范中的 9.4.9 节。 有关详细信息,请参阅如何在 USB 接口中选择备用设置。 |
WdfUsbTargetDeviceSelectConfig
|
|
_URB_SELECT_INTERFACE (USBD_SelectInterfaceUrbAllocateAndBuild) URB_FUNCTION_SELECT_INTERFACE |
SYNC_FRAME:设置和获取终结点的同步帧编号。 请参阅 USB 规范中的 9.4.10 节。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 | 此请求由 USB 驱动程序堆栈处理;客户端驱动程序不能执行此操作。 |
针对特定于设备类的请求和供应商命令。 |
|
|
_URB_CONTROL_VENDOR_OR_CLASS_REQUEST (UsbBuildVendorRequest) URB_FUNCTION_VENDOR_DEVICE URB_FUNCTION_VENDOR_INTERFACE URB_FUNCTION_VENDOR_ENDPOINT URB_FUNCTION_VENDOR_OTHER URB_FUNCTION_CLASS_DEVICE URB_FUNCTION_CLASS_INTERFACE URB_FUNCTION_CLASS_ENDPOINT URB_FUNCTION_CLASS_OTHER |
如何发送供应商命令的控制传输 - KMDF
以下过程演示了客户端驱动程序如何发送控制传输。 在此示例中,客户端驱动程序发送一个从设备检索固件版本的供应商命令。
声明一个用于供应商命令的常量。 研究硬件规范,确定要使用的供应商命令。
通过调用 WDF_MEMORY_DESCRIPTOR_INIT_BUFFER 宏来声明 WDF_MEMORY_DESCRIPTOR 结构并将其初始化。 此结构会在 USB 驱动程序完成请求后从设备接收响应。
指定发送选项,具体取决于你是以同步方式还是异步方式发送请求:
如果通过调用 WdfUsbTargetDeviceSendControlTransferSynchronously 以同步方式发送请求,请指定超时值。 该值很重要,因为如果没有超时值,则可能无限期阻止线程。
为此,请通过调用 WDF_REQUEST_SEND_OPTIONS_INIT 宏声明 WDF_REQUEST_SEND_OPTIONS 结构并将其初始化。 将该选项指定为 WDF_REQUEST_SEND_OPTION_TIMEOUT。
接下来,通过调用 WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT 宏设置超时值。
如果以异步方式发送请求,请实施一个完成例程。 释放完成例程中所有分配的资源。
声明一个 WDF_USB_CONTROL_SETUP_PACKET 结构,使之包含设置令牌,然后将结构格式化。 为此,请调用 WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR 宏来格式化设置数据包。 在调用中指定请求的方向、接收者、发送-请求选项(已在步骤 3 中初始化)以及供应商命令的常量。
通过调用 WdfUsbTargetDeviceSendControlTransferSynchronously 或 WdfUsbTargetDeviceFormatRequestForControlTransfer 来发送请求。
检查框架返回的 NTSTATUS 值,并检查接收的值。
以下代码示例将控制传输请求发送到 USB 设备,以便检索其固件版本。 请求以异步方式发送,客户端驱动程序指定一个 5 秒的相对超时值(以 100 纳秒为单位)。 驱动程序将接收的响应存储在驱动程序定义的设备上下文中。
enum {
USBFX2_GET_FIRMWARE_VERSION = 0x1,
....
} USBFX2_VENDOR_COMMANDS;
#define WDF_TIMEOUT_TO_SEC ((LONGLONG) 1 * 10 * 1000 * 1000) // defined in wdfcore.h
const __declspec(selectany) LONGLONG
DEFAULT_CONTROL_TRANSFER_TIMEOUT = 5 * -1 * WDF_TIMEOUT_TO_SEC;
typedef struct _DEVICE_CONTEXT
{
...
union {
USHORT VersionAsUshort;
struct {
BYTE Minor;
BYTE Major;
} Version;
} Firmware; // Firmware version.
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
__drv_requiresIRQL(PASSIVE_LEVEL)
VOID GetFirmwareVersion(
__in PDEVICE_CONTEXT DeviceContext
)
{
NTSTATUS status;
WDF_USB_CONTROL_SETUP_PACKET controlSetupPacket;
WDF_REQUEST_SEND_OPTIONS sendOptions;
USHORT firmwareVersion;
WDF_MEMORY_DESCRIPTOR memoryDescriptor;
PAGED_CODE();
firmwareVersion = 0;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, (PVOID) &firmwareVersion, sizeof(firmwareVersion));
WDF_REQUEST_SEND_OPTIONS_INIT(
&sendOptions,
WDF_REQUEST_SEND_OPTION_TIMEOUT
);
WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(
&sendOptions,
DEFAULT_CONTROL_TRANSFER_TIMEOUT
);
WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(&controlSetupPacket,
BmRequestDeviceToHost, // Direction of the request
BmRequestToDevice, // Recipient
USBFX2_GET_FIRMWARE_VERSION, // Vendor command
0, // Value
0); // Index
status = WdfUsbTargetDeviceSendControlTransferSynchronously(
DeviceContext->UsbDevice,
WDF_NO_HANDLE, // Optional WDFREQUEST
&sendOptions,
&controlSetupPacket,
&memoryDescriptor, // MemoryDescriptor
NULL); // BytesTransferred
if (!NT_SUCCESS(status))
{
KdPrint(("Device %d: Failed to get device firmware version 0x%x\n", DeviceContext->DeviceNumber, status));
TraceEvents(DeviceContext->DebugLog,
TRACE_LEVEL_ERROR,
DBG_RUN,
"Device %d: Failed to get device firmware version 0x%x\n",
DeviceContext->DeviceNumber,
status);
}
else
{
DeviceContext->Firmware.VersionAsUshort = firmwareVersion;
TraceEvents(DeviceContext->DebugLog,
TRACE_LEVEL_INFORMATION,
DBG_RUN,
"Device %d: Get device firmware version : 0x%x\n",
DeviceContext->DeviceNumber,
firmwareVersion);
}
return;
}
如何发送 GET_STATUS 的控制传输 - UMDF
以下过程演示了客户端驱动程序如何发送 GET_STATUS 命令的控制传输。 请求的接收者为设备,请求获取 D1-D0 位中的信息。 有关详细信息,请参阅 USB 规范中的图 9-4。
包括随附在 OSR USB Fx2 学习工具包的 UMDF 示例驱动程序中的头文件 Usb_hw.h。
声明 WINUSB_CONTROL_SETUP_PACKET 结构。
通过调用帮助器宏 WINUSB_CONTROL_SETUP_PACKET_INIT_GET_STATUS 来初始化设置数据包。
指定 BmRequestToDevice 作为接收者。
在 Index 值中指定 0。
调用帮助器方法 SendControlTransferSynchronously,以同步方式发送请求。
此帮助器方法通过调用 IWDFUsbTargetDevice::FormatRequestForControlTransfer 方法将初始化的设置数据包与框架请求对象和传输缓冲区相关联,以这种方式构建请求。 然后,此帮助器方法通过调用 IWDFIoRequest::Send 方法来发送请求。 在此方法返回后,检查返回的值。
若要确定状态指示自驱动还是远程唤醒,请使用 WINUSB_DEVICE_TRAITS 枚举中定义的以下值:
以下代码示例通过发送控制传输请求来获取设备的状态。 此示例通过调用名为 SendControlTransferSynchronously 的帮助器方法以异步方式发送请求。
HRESULT
CDevice::GetDeviceStatus ()
{
HRESULT hr = S_OK;
USHORT deviceStatus;
ULONG bytesTransferred;
TraceEvents(TRACE_LEVEL_INFORMATION,
DRIVER_ALL_INFO,
"%!FUNC!: entry");
// Setup the control packet.
WINUSB_CONTROL_SETUP_PACKET setupPacket;
WINUSB_CONTROL_SETUP_PACKET_INIT_GET_STATUS(
&setupPacket,
BmRequestToDevice,
0);
hr = SendControlTransferSynchronously(
&(setupPacket.WinUsb),
& deviceStatus,
sizeof(USHORT),
&bytesReturned
);
if (SUCCEEDED(hr))
{
if (deviceStatus & USB_GETSTATUS_SELF_POWERED)
{
m_Self_Powered = true;
}
if (deviceStatus & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED)
{
m_remote_wake-enabled = true;
}
}
return hr;
}
以下代码示例演示了如何实现名为 SendControlTransferSynchronously 的帮助器方法。 此方法以异步方式发送请求。
HRESULT
CDevice::SendControlTransferSynchronously(
_In_ PWINUSB_SETUP_PACKET SetupPacket,
_Inout_ PBYTE Buffer,
_In_ ULONG BufferLength,
_Out_ PULONG LengthTransferred
)
{
HRESULT hr = S_OK;
IWDFIoRequest *pWdfRequest = NULL;
IWDFDriver * FxDriver = NULL;
IWDFMemory * FxMemory = NULL;
IWDFRequestCompletionParams * FxComplParams = NULL;
IWDFUsbRequestCompletionParams * FxUsbComplParams = NULL;
*LengthTransferred = 0;
hr = m_FxDevice->CreateRequest( NULL, //pCallbackInterface
NULL, //pParentObject
&pWdfRequest);
if (SUCCEEDED(hr))
{
m_FxDevice->GetDriver(&FxDriver);
hr = FxDriver->CreatePreallocatedWdfMemory( Buffer,
BufferLength,
NULL, //pCallbackInterface
pWdfRequest, //pParetObject
&FxMemory );
}
if (SUCCEEDED(hr))
{
hr = m_pIUsbTargetDevice->FormatRequestForControlTransfer( pWdfRequest,
SetupPacket,
FxMemory,
NULL); //TransferOffset
}
if (SUCCEEDED(hr))
{
hr = pWdfRequest->Send( m_pIUsbTargetDevice,
WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
0); //Timeout
}
if (SUCCEEDED(hr))
{
pWdfRequest->GetCompletionParams(&FxComplParams);
hr = FxComplParams->GetCompletionStatus();
}
if (SUCCEEDED(hr))
{
HRESULT hrQI = FxComplParams->QueryInterface(IID_PPV_ARGS(&FxUsbComplParams));
WUDF_TEST_DRIVER_ASSERT(SUCCEEDED(hrQI));
WUDF_TEST_DRIVER_ASSERT( WdfUsbRequestTypeDeviceControlTransfer ==
FxUsbComplParams->GetCompletedUsbRequestType() );
FxUsbComplParams->GetDeviceControlTransferParameters( NULL,
LengthTransferred,
NULL,
NULL );
}
SAFE_RELEASE(FxUsbComplParams);
SAFE_RELEASE(FxComplParams);
SAFE_RELEASE(FxMemory);
pWdfRequest->DeleteWdfObject();
SAFE_RELEASE(pWdfRequest);
SAFE_RELEASE(FxDriver);
return hr;
}
如果使用 Winusb.sys 作为设备的功能驱动程序,则可从应用程序发送控制传输。 若要设置 WinUSB 中的设置数据包的格式,请使用本文表格中所述的 UMDF 帮助程序宏和结构。 若要发送请求,请调用 WinUsb_ControlTransfer 函数。