CH552 官方 USB HID 示例程序中的一处 bug
最近在开发 BeatShow 设备的固件恢复功能时发现,使用 CH552 实现的 USB HID 设备总是获取不到设备名称,而 STM32F072 的就没问题。即使将所有 USB 描述符都改成一样的,问题也仍然存在。

调试 PC 端程序看到底层调用的 HidD_GetProductString() 函数执行结果是成功的,只是返回的字符串长度为 0, 所以就得从硬件下手了。

经过抓包,发现对于 CH552 设备,除了设备枚举过程中的正常 GET_DESCRIPTOR 请求,在 PC 端程序调用 HidD_GetProductString() 时系统又发送了额外的 GET_DESCRIPTOR 请求。而且这些请求的返回数据长度只有 2 字节,明显是不正常的。

CH552 设备枚举过程中正常的 GET_DESCRIPTOR 请求, wLength = 0x00FF = 255:


CH552 设备完成枚举后的额外 GET_DESCRIPTOR 请求, wLength = 0x0102 = 258:


STM32 设备在完成枚举后则没有这些额外的 GET_DESCRIPTOR 请求:


对于为什么系统只对 CH552 设备发送了这些额外的 GET_DESCRIPTOR 请求,经过一番搜索没有找到答案。但显然 CH552 的程序在这块的处理上有 bug。

CH552 实现的 bootloader 是以 WCH 官方的 USB HID 示例程序 CompatibilityHID.C 为基础,逐步重构和修改而来的。这个示例中有一段处理 SETUP 事物的代码是这样的:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

可以看到只取了 wLength 的低字节部分 wLengthL 作为 SetupLen, 完全没有使用高字节部分 wLengthH。而另一个 VendorDefinedDev.C 示例程序中,就对高字节部分 wLengthH 做了检查:

case UIS_TOKEN_SETUP | 0:                       // endpoint 0# SETUP
    len = USB_RX_LEN;
    if (len == sizeof(USB_SETUP_REQ)) {         // SETUP 包长度
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F) SetupLen = 0x7F;      // 限制总长度
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReqCode = UsbSetupBuf->bRequest;
        // ...

CompositeKM.C 示例程序中也一样检查了 wLengthH:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F)
        {
            SetupLen = 0x7F;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

CompatibilityHID.C 示例程序中原本没有任何字符串描述符,也没有对 GET_DESCRIPTOR 请求的处理代码,相关的内容都是我后添加的。这可能是官方程序中这个 bug 存在了这么长时间的原因。

知道原因后就好修改了,只要在 wLengthH 不为 0 时把 SetupLen 限制到 uint8_t 范围内的最大值 0xFF 就可以了:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH != 0)
        {
            SetupLen = 0xFF;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...
当前语言: 中文 (简体)
请大师动手指导,拒绝低俗  
您的大名
(必填)
电子邮件
(必填,不公开)
个人网站
(可选)
留言
可以使用类似维基标记的语法,点击这里查看说明