WinUSB 或 HID 驱动程序的 USB 设备原型超时,适用于 libusb

时间:2021-06-21 11:18:45

标签: windows usb winusb libusb-win32

我正在尝试为 USB (1.1) HID 设备 using an Arudino board and a CH376 module 创建原型。我设法编写了将 CH376 设置为设备模式并处理所有 USB 事务的 Arduino 代码。

只要我声明该设备属于“供应商特定”类,Windows 10 就会将该设备识别为“未知设备”,并在我在控制面板(设备属性 - 详细信息 - 硬件 ID)。到现在为止还挺好。然后我可以使用 Zadig 为设备分配一个 libusb 驱动程序,然后它会显示在控制面板的“libusb 设备”类别下;一切都还好,我可以使用 libusb 函数与设备通信。

现在问题来了。如果我尝试使用 WinUSB 而不是 libusb 来管理设备,当我插入或重置设备时会发生以下情况:

  1. Windows 做了 the usual device initialization sequence:它请求设备描述符(64 个字节),然后给设备分配一个地址,然后再次请求设备描述符(这次只有 18 个字节),然后请求配置描述符。

  2. 五秒后,Windows 再次请求设备描述符。这就是设备出现在设备管理器中的位置,状态为“此设备工作正常”。

  3. 再过 5 秒后,Windows 再次请求配置描述符,但在仅检索前 8 个字节(即控制端点的大小)后,它断开设备连接并在设备管理器中用感叹号显示它。

如果我转到设备属性,“常规”选项卡,它会说:

This device cannot start. (Code 10)

{Device Timeout}
The specified I/O operation on %hs was not completed before the time-out period expired.

如果我转到“事件”选项卡,最后一个是“设备未启动 (WinUSB)”,其中包含以下详细信息:

Device USB\VID_1209&PID_0002\8&339ad878&0&2 had a problem starting.

Driver Name: oem168.inf
Class Guid: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
Service: WinUSB
Lower Filters: 
Upper Filters: 
Problem: 0xA
Problem Status: 0xC00000B5

现在,如果不是将设备声明为属于“供应商特定”类,而是将其声明为 HID 设备并让 Windows 选择正确的驱动程序,则行为是相同的,但“常规”中的错误消息标签是:

This device cannot start. (Code 10)

The I/O request was canceled.

...以及最后一个事件:

Device USB\VID_1209&PID_0002\8&339ad878&0&2 had a problem starting.

Driver Name: input.inf
Class Guid: {745a17a0-74d3-11d0-b6fe-00a0c90f57da}
Service: HidUsb
Lower Filters: 
Upper Filters: 
Problem: 0xA
Problem Status: 0xC0000120

我怀疑原型对于 Windows 内置的 USB 设备管理库来说可能太慢了,而 libusb 更宽松,但我觉得这很奇怪(Arduino 板上的代码只是检查来自 CH376 的事件并处理它们立即循环),我想知道我是否做错了什么。

这是 Arduino 代码生成的跟踪的转储。 SETUP/IN/OUT 事件表示令牌处理已经完成;对于 IN 令牌,我之前写入的任何数据(“写入 X 字节”日志)都已发送。通过使用 libusb,我可以确认记录的数据确实是我发送的数据。

Int: USB_SUSPEND
Int: WAKE_UP
Int: WAKE_UP
Int: EP0_SETUP
  0x80 0x06 0x00 0x01 0x00 0x00 0x40 0x00 
  GET_DESCRIPTOR: DEVICE
  Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08 
Int: EP0_IN
  Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02 
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_SETUP
  0x00 0x05 0x18 0x00 0x00 0x00 0x00 0x00 
  SET_ADDRESS: 24
Int: EP0_IN
  Setting address: 24
Int: EP0_SETUP
  0x80 0x06 0x00 0x01 0x00 0x00 0x12 0x00 
  GET_DESCRIPTOR: DEVICE
  Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08 
Int: EP0_IN
  Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02 
Int: EP0_IN
  Writing 2 bytes: 0x00 0x01 
Int: EP0_IN
Int: EP0_OUT
Int: EP0_SETUP
  0x80 0x06 0x00 0x02 0x00 0x00 0xFF 0x00 
  GET_DESCRIPTOR: CONFIGURATION
  Writing 8 bytes: 0x09 0x02 0x22 0x00 0x01 0x01 0x00 0x80 
Int: EP0_IN
  Writing 8 bytes: 0x32 0x09 0x04 0x00 0x00 0x01 0x03 0x00 
Int: EP0_IN
  Writing 8 bytes: 0x00 0x00 0x09 0x21 0x10 0x01 0x00 0x01 
Int: EP0_IN
  Writing 8 bytes: 0x22 0x29 0x00 0x07 0x05 0x81 0x03 0x03 
Int: EP0_IN
  Writing 2 bytes: 0x00 0x0A 
Int: EP0_IN
Int: EP0_OUT

(暂停 5 秒,设备管理器中还没有条目)

Int: EP0_SETUP
  0x80 0x06 0x00 0x01 0x00 0x00 0x12 0x00 
  GET_DESCRIPTOR: DEVICE
  Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08 
Int: EP0_IN
  Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02 
Int: EP0_IN
  Writing 2 bytes: 0x00 0x01 
Int: EP0_IN
Int: EP0_OUT

(再次暂停 5 秒,设备在设备管理器中显示工作正常)

Int: EP0_SETUP
  0x80 0x06 0x00 0x02 0x00 0x00 0x09 0x00 
  GET_DESCRIPTOR: CONFIGURATION
  Writing 8 bytes: 0x09 0x02 0x22 0x00 0x01 0x01 0x00 0x80 
Int: USB_SUSPEND

(设备现在显示为感叹号)

顺便说一下,我通过写入串行端口来获取这些痕迹,然后由计算机读取。考虑到这可能会减慢整个处理速度,我尝试禁用任何串行端口通信,结果是一样的。

所以我的问题是:

  1. 我做错了什么吗?还是 Windows 有问题?
  2. 如果是后者,Windows 10 是否提供任何方法来增加 WinUSB/HID 设备处理的超时时间(如果这确实是问题所在)?

编辑

如果我使用 Microsoft's USB hardware verifier,我会得到以下输出:

Event Message: USBXHCI Device Update
VendorID/ProductID: 0x1209/0x2
PortPath:  0x4, 0x4, 0x4, 0x1, 0x0, 0x0
HWVerify Errors Encountered so far:
    #1: (UsbHub3/171): Request for Language ID String Descriptor Failed
    #2: (UsbHub3/132): Device Control Transfer Error

...这很奇怪,因为设备似乎根本没有收到任何对字符串描述符的请求!

编辑 2

根据评论中的建议,我将 Wireshark 与 USBPcap 一起使用,我看到的是:

  • 对 wLength = 18 的设备描述符的一个请求
  • 对 wLength = 9 的配置描述符的一个请求

这与我在自己的跟踪中看到的最后两个请求一致。

编辑 3

为了不让问题文本增长太多I have created a gist包含:

  • 我正在使用的 Arduino 代码。
  • 详细的 Wireshark 捕获。

这是我正在使用的 CH376 模块的原理图(这是我找到的有关该模块的所有技术信息):

enter image description here

编辑 4

正如评论中所建议的,我修改了我的代码,以便设备现在将自己报告为 USB 2.0 设备并提供序列号字符串。现在从设备的角度来看事件的顺序是:

  • 获取设备描述符请求(wLength = 64)
  • 设置地址请求
  • 获取设备描述符请求(wLength = 18)
  • 获取配置描述符请求 (wLength = 255)
  • 暂停 5 秒
  • 获取可用语言请求(字符串描述符 0)
  • 暂停 5 秒
  • 获取 Microsoft 特定的描述符请求 (bRequest = 6, wValue = 0x0600) - 不受支持,因此我将其搁置
  • 获取设备描述符请求(wLength = 18)
  • 暂停 5 秒
  • 获取配置描述符请求 (wLength = 9) - 在 Wireshark 中显示为 USBD_STATUS_CANCELED 的响应

有趣的是,Wireshark 跟踪完全相同:语言请求和 Microsoft 特定的描述符请求没有显示!

这种模式似乎是 Wireshark 看不到 5 秒暂停后的请求,所以也许它们没有被我的代码正确处理,也没有被主机认为是完成的,但我不明白为什么(因为我正在执行与第二个获取设备描述符请求相同的处理,该请求成功)。

另外值得注意的是,当第一个设备描述符请求完成时,现在我得到的不是一个而是六个 OUT 令牌中断。这可能是时间问题的征兆吗?

编辑 5

显然是时间问题。

我使用 Zadig 将设备的驱动程序设置为 libusb,然后我尝试在循环中发送一些描述符请求;结果是,有二分之一的请求始终失败。但是,如果我在请求之间放置 1 毫秒的延迟,那么它们都可以正常工作!就好像芯片在成功请求后需要一个“冷却期”。失败的请求不会出现在 Wireshark 或我自己的日志中,因此似乎 SETUP 令牌本身丢失了。

有什么方法可以指示 Windows 在对给定设备的请求之间稍等片刻?

2 个答案:

答案 0 :(得分:3)

我检查了您的代码并发现:

#define USB_REQ_SET_CONFIGURATION 是 9 而不是 8

在通过 SPI 驱动 CH376s 的 Teensy2 上,它在我的 Mac 上正确显示如下:

         NestorDevice:

              Product ID: 0x0002
              Vendor ID: 0x1209
              Version: 1.00
              Speed: Up to 12 Mb/s
              Location ID: 0x14223000 / 48
              Current Available (mA): 500
              Current Required (mA): 100
              Extra Operating Current (mA): 0

我还没有尝试与它沟通。

还要知道时机就是一切。当我在 Mac 上运行您的代码时,通过串行将所有命令中继到 Teensy,我错过了某些事件。 Mac 发出所有命令的速度非常快,串行延迟太多。

编辑

有两种类型的读取:

CMD01_RD_USB_DATA0  EQU         027H             ; / * Read the data block from the current USB interrupt endpoint buffer or the host endpoint receive buffer * /
; / * Output: length, data stream * /

CMD01_RD_USB_DATA   EQU         028H             ; / * Device mode: Read the data block from the current USB interrupt endpoint buffer and release the buffer, equivalent to CMD01_RD_USB_DATA0 + CMD00_UNLOCK_USB * /
; / * Output: length, data stream * /

如果我们使用后一个函数呢?它会更早地释放缓冲区并为下一个事件做好准备吗?我会在这里尝试一下,看看它是否有意义。

答案 1 :(得分:1)

我一直在对此进行进一步调查,并在 Github 上找到了更多中文示例。令我震惊的是,他们在 EP0_IN 处添加了数量不等的 UNLOCK_USB 命令。

一旦我这样做了,我就更幸运地注册了串行设备。否则它会处于循环中,每 5 秒重试一次。

所以在 USB_INT_EP0_IN 处添加一个额外的 CMD_UNLOCK_USB 在处理任何需要的处理之后。查看更多here