在使用 TinyUSB 的 DWC2 Host 栈时,我们就来聊聊这个大麻烦的原因以及如何搞定它.
正常情况下,当 USB 端口的状态发生变化时,hprt
寄存器里的 HPRT_CONN_DETECT
位会被置位,同时 hprt_bm.conn_status
会显示当前端口的连接状态.这样,系统就能及时知道设备是连接还是拔出了.
下面这个是函数的原始实现
static void handle_hprt_irq(uint8_t rhport, bool in_isr) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
uint32_t hprt = dwc2->hprt & ~HPRT_W1_MASK;
const dwc2_hprt_t hprt_bm = dwc2->hprt_bm;
if (dwc2->hprt & HPRT_CONN_DETECT) {
// Port Connect Detect
hprt |= HPRT_CONN_DETECT;
if (hprt_bm.conn_status) {
hcd_event_device_attach(rhport, in_isr);
} else {
hcd_event_device_remove(rhport, in_isr);
}
}
if (dwc2->hprt & HPRT_ENABLE_CHANGE) {
// Port enable change
hprt |= HPRT_ENABLE_CHANGE;
if (hprt_bm.enable) {
// Port enable
const tusb_speed_t speed = hprt_speed_get(dwc2);
port0_enable(dwc2, speed);
} else {
// TU_ASSERT(false, );
}
}
dwc2->hprt = hprt; // clear interrupt
}
但是,有些芯片存在一个小问题,当设备拔出时,hprt_bm.conn_status
从 1
变成 0
,但 dwc2->hprt & HPRT_CONN_DETECT
却没有被置位,这就导致 Host 栈没有发起正确的中断(但是其实发起了中断且只置位在GINT,HPRT可能为全0),从而无法执行 hcd_event_device_remove
,设备拔出后系统依然认为设备还在.
猜测这是芯片硬件层面的问题,当设备拔出时,芯片本应通过 HPRT_CONN_DETECT
中断通知软件层,但由于设计缺陷,这个中断有时候并不会被正确触发.因此,软件层无法及时感知到设备状态的变化.
下面是修改后的 handle_hprt_irq
函数代码:
#include <stdbool.h>
#define MAX_RHPORTS 16
static void handle_hprt_irq(uint8_t rhport, bool in_isr) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
uint32_t hprt = dwc2->hprt & ~HPRT_W1_MASK;
const dwc2_hprt_t hprt_bm = dwc2->hprt_bm;
// 用静态数组保存每个 rhport 的之前连接状态
static bool previous_conn_status[MAX_RHPORTS] = {false};
// 获取当前的连接状态
bool current_conn_status = hprt_bm.conn_status;
// 检查是否有连接检测中断
if (dwc2->hprt & HPRT_CONN_DETECT) {
// 端口连接检测
hprt |= HPRT_CONN_DETECT;
if (current_conn_status) {
// 设备连接上来了
hcd_event_device_attach(rhport, in_isr);
} else {
// 设备拔出来了
hcd_event_device_remove(rhport, in_isr);
}
} else {
// 如果没有检测到中断,但连接状态变化了
if (previous_conn_status[rhport] && !current_conn_status) {
// 连接状态从 1 变成 0,执行设备移除
hcd_event_device_remove(rhport, in_isr);
}
}
// 处理端口使能状态变化
if (dwc2->hprt & HPRT_ENABLE_CHANGE) {
// 端口使能状态变化
hprt |= HPRT_ENABLE_CHANGE;
if (hprt_bm.enable) {
// 端口已使能
const tusb_speed_t speed = hprt_speed_get(dwc2);
port0_enable(dwc2, speed);
} else {
// 端口未使能,执行适当的处理
// 可以在这里添加错误处理逻辑
// TU_ASSERT(false, "Port disabled unexpectedly");
}
}
// 清除中断标志
dwc2->hprt = hprt;
// 更新之前的连接状态
previous_conn_status[rhport] = current_conn_status;
}