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 事物的代码是这样的:
可以看到只取了 wLength 的低字节部分 wLengthL 作为 SetupLen, 完全没有使用高字节部分 wLengthH。而另一个 VendorDefinedDev.C 示例程序中,就对高字节部分 wLengthH 做了检查:
CompositeKM.C 示例程序中也一样检查了 wLengthH:
CompatibilityHID.C 示例程序中原本没有任何字符串描述符,也没有对 GET_DESCRIPTOR 请求的处理代码,相关的内容都是我后添加的。这可能是官方程序中这个 bug 存在了这么长时间的原因。
知道原因后就好修改了,只要在 wLengthH 不为 0 时把 SetupLen 限制到 uint8_t 范围内的最大值 0xFF 就可以了:
调试 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;
// ...
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;
// ...
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;
// ...
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;
// ...
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;
// ...
Current language: 中文 (简体)