Skip to content

Commit cffb211

Browse files
fix(dcd): Fixed race condition on device disconnect
TinyUSB does not provide any locking means to protect the DCD variables. This can lead to race conditions when the user is trying to submit a transfer while the device is being disconnected. This can cause the device to be in an inconsistent state, leading to a crash or undefined behavior. This commit adds a spin-lock to protect the DCD variables during device disconnect. Closes espressif/esp-idf#9691 Also reported in espressif/esp-usb#131
1 parent 1a783b3 commit cffb211

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

src/portable/synopsys/dwc2/dcd_dwc2.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
#include "device/dcd.h"
4242
#include "dwc2_common.h"
43+
#include "dwc2_critical.h"
4344

4445
//--------------------------------------------------------------------+
4546
// MACRO TYPEDEF CONSTANT ENUM
@@ -52,6 +53,9 @@ typedef struct {
5253
uint8_t interval;
5354
} xfer_ctl_t;
5455

56+
/*
57+
This variable is modified from ISR context, so it must be protected by critical section
58+
*/
5559
static xfer_ctl_t xfer_status[DWC2_EP_MAX][2];
5660
#define XFER_CTL_BASE(_ep, _dir) (&xfer_status[_ep][_dir])
5761

@@ -321,6 +325,9 @@ static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) {
321325
}
322326
}
323327

328+
// Since this function returns void, it is not possible to return a boolean success message
329+
// We must make sure that this function is not called when the EP is disabled
330+
// Must be called from critical section
324331
static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uint8_t dir) {
325332
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
326333
xfer_ctl_t* const xfer = XFER_CTL_BASE(epnum, dir);
@@ -531,6 +538,7 @@ void dcd_edpt_close_all(uint8_t rhport) {
531538
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
532539
uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
533540

541+
DCD_ENTER_CRITICAL();
534542
_dcd_data.allocated_epin_count = 0;
535543

536544
// Disable non-control interrupt
@@ -550,6 +558,7 @@ void dcd_edpt_close_all(uint8_t rhport) {
550558
dfifo_flush_rx(dwc2);
551559

552560
dfifo_device_init(rhport); // re-init dfifo
561+
DCD_EXIT_CRITICAL();
553562
}
554563

555564
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
@@ -568,7 +577,12 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
568577
uint8_t const epnum = tu_edpt_number(ep_addr);
569578
uint8_t const dir = tu_edpt_dir(ep_addr);
570579

580+
DCD_ENTER_CRITICAL();
571581
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir);
582+
if (xfer->max_size == 0) {
583+
DCD_EXIT_CRITICAL();
584+
return false; // Endpoint is closed
585+
}
572586
xfer->buffer = buffer;
573587
xfer->ff = NULL;
574588
xfer->total_len = total_bytes;
@@ -580,6 +594,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
580594

581595
// Schedule packets to be sent within interrupt
582596
edpt_schedule_packets(rhport, epnum, dir);
597+
DCD_EXIT_CRITICAL();
583598

584599
return true;
585600
}
@@ -595,14 +610,20 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t
595610
uint8_t const epnum = tu_edpt_number(ep_addr);
596611
uint8_t const dir = tu_edpt_dir(ep_addr);
597612

613+
DCD_ENTER_CRITICAL();
598614
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir);
615+
if (xfer->max_size == 0) {
616+
DCD_EXIT_CRITICAL();
617+
return false; // Endpoint is closed
618+
}
599619
xfer->buffer = NULL;
600620
xfer->ff = ff;
601621
xfer->total_len = total_bytes;
602622

603623
// Schedule packets to be sent within interrupt
604624
// TODO xfer fifo may only available for slave mode
605625
edpt_schedule_packets(rhport, epnum, dir);
626+
DCD_EXIT_CRITICAL();
606627

607628
return true;
608629
}
@@ -631,6 +652,7 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
631652
//--------------------------------------------------------------------
632653

633654
// 7.4.1 Initialization on USB Reset
655+
// Must be called from critical section
634656
static void handle_bus_reset(uint8_t rhport) {
635657
dwc2_regs_t *dwc2 = DWC2_REG(rhport);
636658
const uint8_t ep_count = dwc2_ep_count(dwc2);
@@ -989,8 +1011,10 @@ void dcd_int_handler(uint8_t rhport) {
9891011

9901012
if (gintsts & GINTSTS_USBRST) {
9911013
// USBRST is start of reset.
1014+
DCD_ENTER_CRITICAL();
9921015
dwc2->gintsts = GINTSTS_USBRST;
9931016
handle_bus_reset(rhport);
1017+
DCD_EXIT_CRITICAL();
9941018
}
9951019

9961020
if (gintsts & GINTSTS_ENUMDNE) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef TUSB_DWC2_CRITICAL_H_
8+
#define TUSB_DWC2_CRITICAL_H_
9+
10+
#include "common/tusb_mcu.h"
11+
12+
#if defined(TUP_USBIP_DWC2_ESP32)
13+
#include "freertos/FreeRTOS.h"
14+
static portMUX_TYPE dcd_lock = portMUX_INITIALIZER_UNLOCKED;
15+
#define DCD_ENTER_CRITICAL() portENTER_CRITICAL(&dcd_lock)
16+
#define DCD_EXIT_CRITICAL() portEXIT_CRITICAL(&dcd_lock)
17+
18+
#else
19+
// Define critical section macros for DWC2 as no-op if not defined
20+
// This is to avoid breaking existing code that does not use critical section
21+
#define DCD_ENTER_CRITICAL() // no-op
22+
#define DCD_EXIT_CRITICAL() // no-op
23+
#endif
24+
25+
#endif // TUSB_DWC2_CRITICAL_H_

0 commit comments

Comments
 (0)