解决 TinyUSB DWC2 Host 栈中无法检测设备拔出的问题

/ 0评 / 0

在使用 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_status1 变成 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;
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注