Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 33 additions & 29 deletions src/portable/wch/dcd_ch32_usbhs.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ static xfer_ctl_t xfer_status[EP_MAX][2];
TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE];
static bool ep0_tog;
static bool ep_data_tog[EP_MAX][2];
static bool reset_evt_pending;

static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) {
if (ep_dir == TUSB_DIR_IN) {
Expand Down Expand Up @@ -175,7 +176,6 @@ static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) {

bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
(void)rhport;
(void)rh_init;

memset(&xfer_status, 0, sizeof(xfer_status));
memset(ep_data_tog, 0, sizeof(ep_data_tog));
Expand All @@ -186,12 +186,15 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {

USBHSD->CONTROL = 0;

#if TUD_OPT_HIGH_SPEED
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
#else
#error OPT_MODE_FULL_SPEED not currently supported on CH32
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
#endif
if (rh_init->speed == TUSB_SPEED_HIGH || rh_init->speed == TUSB_SPEED_AUTO) {
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
} else if (rh_init->speed == TUSB_SPEED_FULL) {
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
} else if (rh_init->speed == TUSB_SPEED_LOW) {
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_LOW_SPEED;
} else {
return false;
}

USBHSD->INT_EN = 0;
USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN;
Expand Down Expand Up @@ -417,13 +420,32 @@ void dcd_int_handler(uint8_t rhport) {
if (token == USBHS_TOKEN_PID_SOF) {
uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK;
dcd_event_sof(rhport, frame_count, true);
} else if (token == USBHS_TOKEN_PID_OUT) {
// Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit
// after a lost ACK, or a host that doesn't alternate DATA0/DATA1.
} else if (token == USBHS_TOKEN_PID_OUT && (int_status & USBHS_DEV_UIS_TOG_OK)) {
update_out(rhport, ep_num, len);
Comment thread
HiFiPhile marked this conversation as resolved.
Comment on lines +425 to 426

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bypass the toggle gate for isochronous OUT endpoints

When an isochronous OUT endpoint is active, queue_out_packet() and update_out() deliberately skip programming/advancing the data toggle for xfer->is_iso, because ISO transfers do not use the normal retry toggle sequence. This new global TOG_OK gate therefore risks dropping ISO OUT completions whenever the hardware does not assert a meaningful toggle check for that endpoint; limit the check to non-ISO endpoints so audio/ISO OUT payloads still reach update_out().

Useful? React with 👍 / 👎.

} else if (token == USBHS_TOKEN_PID_IN) {
update_in(rhport, ep_num, false);
}
USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */
} else if (int_flag & USBHS_SETUP_FLAG) {
if (reset_evt_pending) {
reset_evt_pending = false;
tusb_speed_t actual_speed;
switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
case USBHS_SPEED_TYPE_FULL:
actual_speed = TUSB_SPEED_FULL;
break;
case USBHS_SPEED_TYPE_LOW:
actual_speed = TUSB_SPEED_LOW;
break;
default:
actual_speed = TUSB_SPEED_HIGH;
break;
}
dcd_event_bus_reset(0, actual_speed, true);
}

tusb_control_request_t const* setup =
(tusb_control_request_t const*) ep0_buffer;
ep0_tog = true;
Expand All @@ -434,27 +456,9 @@ void dcd_int_handler(uint8_t rhport) {

USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
} else if (int_flag & USBHS_BUS_RST_FLAG) {
// TODO CH32 does not detect actual speed at this time (should be known at end of reset)
// This interrupt probably triggered at start of bus reset
// tusb_speed_t actual_speed;
// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
// case USBHS_SPEED_TYPE_HIGH:
// actual_speed = TUSB_SPEED_HIGH;
// break;
// case USBHS_SPEED_TYPE_FULL:
// actual_speed = TUSB_SPEED_FULL;
// break;
// case USBHS_SPEED_TYPE_LOW:
// actual_speed = TUSB_SPEED_LOW;
// break;
// default:
// TU_ASSERT(0,);
// break;
// }
// dcd_event_bus_reset(0, actual_speed, true);

dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);

// This interrupt probably triggered at start of bus reset before the speed negotiation has completed.
// Defer the bus reset event until the next setup packet is received in order to determine the actual speed of the bus.
reset_evt_pending = true;
Comment on lines +459 to +461

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit reset before handling post-reset bus traffic

When a configured device receives a bus reset, this now only sets reset_evt_pending, so any SOF or transfer interrupt that arrives before the first SETUP is still processed with the old TinyUSB configured state; DCD_EVENT_SOF even invokes class SOF handlers immediately in ISR context before the queued reset can run. If the reset is followed by normal SOFs before enumeration, or by suspend/disconnect with no SETUP, the stack remains stale until a SETUP arrives; reset the core immediately or suppress other events while the deferred speed detection is pending.

Useful? React with 👍 / 👎.

USBHSD->DEV_AD = 0;
memset(ep_data_tog, 0, sizeof(ep_data_tog));
ep0_tog = true;
Expand Down
Loading