summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-04-29 17:27:59 +0700
committerhathach <[email protected]>2026-04-29 17:27:59 +0700
commit9d3ad336bfad062fa1e6f6d63feb97a9851cc9e1 (patch)
treeb72ef398d47529eb25810333b319c4ca644cc397
parent939c2f91c200db2bf9874c2e6c50b3530f8a6725 (diff)
deprecated `usbd_control.c` and merge its functionality into `usbd.c`
-rw-r--r--hw/bsp/rp2040/family.cmake1
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/device/usbd.c173
-rw-r--r--src/device/usbd_control.c199
-rw-r--r--src/tinyusb.mk1
-rw-r--r--test/fuzz/rules.mk1
-rw-r--r--test/unit-test/CMakeLists.txt4
7 files changed, 172 insertions, 208 deletions
diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake
index 6e88b9fa1..075582554 100644
--- a/hw/bsp/rp2040/family.cmake
+++ b/hw/bsp/rp2040/family.cmake
@@ -93,7 +93,6 @@ target_sources(tinyusb_device_base INTERFACE
${TOP}/src/portable/raspberrypi/rp2040/dcd_rp2040.c
${TOP}/src/portable/raspberrypi/rp2040/rp2040_usb.c
${TOP}/src/device/usbd.c
- ${TOP}/src/device/usbd_control.c
${TOP}/src/class/audio/audio_device.c
${TOP}/src/class/cdc/cdc_device.c
${TOP}/src/class/dfu/dfu_device.c
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 00f466007..c7a5184c5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,7 +8,6 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/common/tusb_fifo.c
# device
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd.c
- ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd_control.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/audio/audio_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_device.c
diff --git a/src/device/usbd.c b/src/device/usbd.c
index da0ffb4c6..acf808bf6 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -419,11 +419,10 @@ static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_req
}
#endif
-// from usbd_control.c
-void usbd_control_reset(void);
-void usbd_control_set_request(tusb_control_request_t const *request);
-void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp );
-bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+// Control Endpoint
+static void usbd_control_reset(void);
+static void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp);
+static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
//--------------------------------------------------------------------+
// Weak stubs: invoked if no strong implementation is available
@@ -809,6 +808,157 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
}
//--------------------------------------------------------------------+
+// Control Endpoint
+//--------------------------------------------------------------------+
+
+// Weak hook: invoked when the control transfer's status stage completes
+TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) {
+ (void) rhport;
+ (void) request;
+}
+
+typedef struct {
+ tusb_control_request_t request;
+ uint8_t* buffer;
+ uint16_t data_len;
+ uint16_t total_xferred;
+ usbd_control_xfer_cb_t complete_cb;
+} usbd_control_xfer_t;
+
+static usbd_control_xfer_t _ctrl_xfer;
+
+CFG_TUD_MEM_SECTION static struct {
+ TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE);
+} _ctrl_epbuf;
+
+uint8_t* usbd_get_ctrl_buf(void) {
+ return _ctrl_epbuf.buf;
+}
+
+// Endpoint used for the Status stage of a control transfer.
+// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status
+// stage is always IN. Otherwise the Status stage is opposite of the Data stage direction.
+TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) {
+ return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN;
+}
+
+// Queue ZLP status transaction
+TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) {
+ return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false);
+}
+
+// Queue a transaction in Data Stage. Each transaction has up to Endpoint0's max
+// packet size. This function can also transfer a zero-length packet.
+static bool data_stage_xact(uint8_t rhport) {
+ const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE);
+ uint8_t ep_addr = TU_EP0_OUT;
+
+ if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) {
+ ep_addr = TU_EP0_IN;
+ if (0u != xact_len && _ctrl_xfer.buffer != _ctrl_epbuf.buf) {
+ TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len));
+ }
+ }
+
+ return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false);
+}
+
+// Status phase
+bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) {
+ // _ctrl_xfer fields are pre-initialized at process_control_request entry
+ (void) request;
+ return status_stage_xact(rhport, status_stage_ep(&_ctrl_xfer.request));
+}
+
+// Transmit data to/from the control endpoint. If wLength is zero, a status packet is sent instead.
+bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) {
+ // _ctrl_xfer.request and reset fields are pre-initialized at process_control_request entry
+ (void) request;
+ _ctrl_xfer.buffer = (uint8_t*) buffer;
+ _ctrl_xfer.data_len = tu_min16(len, _ctrl_xfer.request.wLength);
+
+ if (_ctrl_xfer.request.wLength > 0U) {
+ if (_ctrl_xfer.data_len > 0U) {
+ TU_ASSERT(buffer);
+ }
+ TU_ASSERT(data_stage_xact(rhport));
+ } else {
+ // wLength == 0: Status stage is always IN per USB 2.0 §9.3.1
+ TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN));
+ }
+
+ return true;
+}
+
+static void usbd_control_reset(void) {
+ tu_varclr(&_ctrl_xfer);
+}
+
+static void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) {
+ _ctrl_xfer.complete_cb = fp;
+}
+
+// Callback when a transaction completes on the DATA stage or Status stage of EP0
+static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) result;
+
+ // Status Stage complete: ep_addr matches the resolved Status stage endpoint
+ uint8_t const ep_status = status_stage_ep(&_ctrl_xfer.request);
+ if (ep_addr == ep_status) {
+ TU_ASSERT(0 == xferred_bytes);
+
+ // invoke optional dcd hook if available
+ dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request);
+
+ if (NULL != _ctrl_xfer.complete_cb) {
+ // TODO refactor with usbd_driver_print_control_complete_name
+ _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request);
+ }
+
+ return true;
+ }
+
+ // Data stage progress
+ if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) {
+ TU_VERIFY(_ctrl_xfer.buffer);
+ if (_ctrl_xfer.buffer != _ctrl_epbuf.buf) {
+ memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes);
+ }
+ TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2);
+ }
+
+ _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes;
+ _ctrl_xfer.buffer += xferred_bytes;
+
+ // Data Stage complete when wLength reached or short packet (incl. ZLP) seen
+ if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) ||
+ (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) {
+ bool is_ok = true;
+
+ if (NULL != _ctrl_xfer.complete_cb) {
+ #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
+ usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb);
+ #endif
+ // Callback can still stall control in status phase, e.g. OUT data doesn't make sense
+ is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request);
+ }
+
+ if (is_ok) {
+ TU_ASSERT(status_stage_xact(rhport, ep_status));
+ } else {
+ // Stall both IN and OUT control endpoint
+ dcd_edpt_stall(rhport, TU_EP0_OUT);
+ dcd_edpt_stall(rhport, TU_EP0_IN);
+ }
+ } else {
+ // More data to transfer
+ TU_ASSERT(data_stage_xact(rhport));
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
// Control Request Parser & Handling
//--------------------------------------------------------------------+
@@ -822,7 +972,14 @@ static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * dri
// This handles the actual request and its response.
// Returns false if unable to complete the request, causing caller to stall control endpoints.
static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) {
- usbd_control_set_complete_callback(NULL);
+ // Initialize control transfer state for this request. The request copy must be
+ // visible to usbd_control_xfer_cb when the (asynchronous) status ZLP completes,
+ // since the SETUP packet event has already gone out of scope by then.
+ _ctrl_xfer.request = *p_request;
+ _ctrl_xfer.buffer = NULL;
+ _ctrl_xfer.total_xferred = 0;
+ _ctrl_xfer.data_len = 0;
+ _ctrl_xfer.complete_cb = NULL;
TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID);
// Vendor request
@@ -865,9 +1022,9 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
// Depending on mcu, status phase could be sent either before or after changing device address,
// or even require stack to not response with status at all
// Therefore DCD must take full responsibility to response and include zlp status packet if needed.
- usbd_control_set_request(p_request); // set request since DCD has no access to tud_control_status() API
+ // _ctrl_xfer.request was already populated at process_control_request() entry, so the
+ // status ZLP that the DCD queues will be recognized by usbd_control_xfer_cb().
dcd_set_address(rhport, (uint8_t) p_request->wValue);
- // skip tud_control_status()
_usbd_dev.addressed = 1;
break;
diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c
index b14d08a9c..38dcc6a82 100644
--- a/src/device/usbd_control.c
+++ b/src/device/usbd_control.c
@@ -24,197 +24,8 @@
* This file is part of the TinyUSB stack.
*/
-#include "tusb_option.h"
-
-#if CFG_TUD_ENABLED
-
-#include "dcd.h"
-#include "tusb.h"
-#include "device/usbd_pvt.h"
-
-//--------------------------------------------------------------------+
-// Callback weak stubs (called if application does not provide)
-//--------------------------------------------------------------------+
-TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) {
- (void) rhport;
- (void) request;
-}
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-
-typedef struct {
- tusb_control_request_t request;
- uint8_t* buffer;
- uint16_t data_len;
- uint16_t total_xferred;
- usbd_control_xfer_cb_t complete_cb;
-} usbd_control_xfer_t;
-
-static usbd_control_xfer_t _ctrl_xfer;
-
-CFG_TUD_MEM_SECTION static struct {
- TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE);
-} _ctrl_epbuf;
-
-uint8_t* usbd_get_ctrl_buf(void) {
- return _ctrl_epbuf.buf;
-}
-
-//--------------------------------------------------------------------+
-// Application API
-//--------------------------------------------------------------------+
-
-// Endpoint used for the Status stage of a control transfer.
-// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status stage
-// is always IN. Otherwise the Status stage is opposite to the Data stage direction.
-TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) {
- return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN;
-}
-
-// Queue ZLP status transaction
-TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) {
- return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false);
-}
-
-// Status phase
-bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = NULL;
- _ctrl_xfer.total_xferred = 0;
- _ctrl_xfer.data_len = 0;
-
- return status_stage_xact(rhport, status_stage_ep(request));
-}
-
-// Queue a transaction in Data Stage
-// Each transaction has up to Endpoint0's max packet size.
-// This function can also transfer an zero-length packet
-static bool data_stage_xact(uint8_t rhport) {
- const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE);
- uint8_t ep_addr = TU_EP0_OUT;
-
- if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) {
- ep_addr = TU_EP0_IN;
- if (0u != xact_len && _ctrl_xfer.buffer != _ctrl_epbuf.buf) {
- TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len));
- }
- }
-
- return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false);
-}
-
-// Transmit data to/from the control endpoint.
-// If the request's wLength is zero, a status packet is sent instead.
-bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = (uint8_t*) buffer;
- _ctrl_xfer.total_xferred = 0U;
- _ctrl_xfer.data_len = tu_min16(len, request->wLength);
-
- if (request->wLength > 0U) {
- if (_ctrl_xfer.data_len > 0U) {
- TU_ASSERT(buffer);
- }
- TU_ASSERT(data_stage_xact(rhport));
- } else {
- TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN));
- }
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// USBD API
-//--------------------------------------------------------------------+
-void usbd_control_reset(void);
-void usbd_control_set_request(const tusb_control_request_t* request);
-void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp);
-bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
-
-void usbd_control_reset(void) {
- tu_varclr(&_ctrl_xfer);
-}
-
-// Set complete callback
-void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) {
- _ctrl_xfer.complete_cb = fp;
-}
-
-// for dcd_set_address where DCD is responsible for status response
-void usbd_control_set_request(const tusb_control_request_t* request) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = NULL;
- _ctrl_xfer.total_xferred = 0;
- _ctrl_xfer.data_len = 0;
-}
-
-// callback when a transaction complete on
-// - DATA stage of control endpoint or
-// - Status stage
-bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
- (void) result;
-
- // Status Stage complete: endpoint matches the Status stage endpoint
- uint8_t const ep_status = status_stage_ep(&_ctrl_xfer.request);
- if (ep_addr == ep_status) {
- TU_ASSERT(0 == xferred_bytes);
-
- // invoke optional dcd hook if available
- dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request);
-
- if (NULL != _ctrl_xfer.complete_cb) {
- // TODO refactor with usbd_driver_print_control_complete_name
- _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request);
- }
-
- return true;
- }
-
- // Data stage complete
- if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) {
- TU_VERIFY(_ctrl_xfer.buffer);
- if (_ctrl_xfer.buffer != _ctrl_epbuf.buf) {
- memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes);
- }
- TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2);
- }
-
- _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes;
- _ctrl_xfer.buffer += xferred_bytes;
-
- // Data Stage is complete when all request's length are transferred or
- // a short packet is sent including zero-length packet.
- if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) ||
- (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) {
- // DATA stage is complete
- bool is_ok = true;
-
- // invoke complete callback if set
- // callback can still stall control in status phase e.g out data does not make sense
- if (NULL != _ctrl_xfer.complete_cb) {
- #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
- usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb);
- #endif
-
- is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request);
- }
-
- if (is_ok) {
- TU_ASSERT(status_stage_xact(rhport, ep_status));
- } else {
- // Stall both IN and OUT control endpoint
- dcd_edpt_stall(rhport, TU_EP0_OUT);
- dcd_edpt_stall(rhport, TU_EP0_IN);
- }
- } else {
- // More data to transfer
- TU_ASSERT(data_stage_xact(rhport));
- }
-
- return true;
-}
-
-#endif
+// The usbd control function that used to live in this file has been merged
+// into src/device/usbd.c. This translation unit is intentionally empty and is
+// kept only so external/vendor build systems that still reference the path
+// keep resolving. Drop usbd_control.c from your build to silence the warning.
+#warning "src/device/usbd_control.c is deprecated and now empty; remove it from your build (its content lives in src/device/usbd.c)."
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index 169098016..e3ef35dcf 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -3,7 +3,6 @@ TINYUSB_SRC_C += \
src/tusb.c \
src/common/tusb_fifo.c \
src/device/usbd.c \
- src/device/usbd_control.c \
src/typec/usbc.c \
src/class/audio/audio_device.c \
src/class/cdc/cdc_device.c \
diff --git a/test/fuzz/rules.mk b/test/fuzz/rules.mk
index 329dcce11..c14330312 100644
--- a/test/fuzz/rules.mk
+++ b/test/fuzz/rules.mk
@@ -23,7 +23,6 @@ SRC_C += \
src/tusb.c \
src/common/tusb_fifo.c \
src/device/usbd.c \
- src/device/usbd_control.c \
src/class/audio/audio_device.c \
src/class/cdc/cdc_device.c \
src/class/dfu/dfu_device.c \
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index b44a91d57..a33af4563 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -117,14 +117,14 @@ add_ceedling_test(
add_ceedling_test(
test_usbd
${CEEDLING_WORKDIR}/test/device/usbd/test_usbd.c
- "${CEEDLING_WORKDIR}/../../src/tusb.c;${CEEDLING_WORKDIR}/../../src/device/usbd.c;${CEEDLING_WORKDIR}/../../src/device/usbd_control.c;${CEEDLING_WORKDIR}/../../src/common/tusb_fifo.c"
+ "${CEEDLING_WORKDIR}/../../src/tusb.c;${CEEDLING_WORKDIR}/../../src/device/usbd.c;${CEEDLING_WORKDIR}/../../src/common/tusb_fifo.c"
"${CEEDLING_BUILD_DIR}/test/mocks/test_usbd/mock_dcd.c;${CEEDLING_BUILD_DIR}/test/mocks/test_usbd/mock_msc_device.c"
)
add_ceedling_test(
test_msc_device
${CEEDLING_WORKDIR}/test/device/msc/test_msc_device.c
- "${CEEDLING_WORKDIR}/../../src/tusb.c;${CEEDLING_WORKDIR}/../../src/device/usbd.c;${CEEDLING_WORKDIR}/../../src/device/usbd_control.c;${CEEDLING_WORKDIR}/../../src/class/msc/msc_device.c;${CEEDLING_WORKDIR}/../../src/common/tusb_fifo.c"
+ "${CEEDLING_WORKDIR}/../../src/tusb.c;${CEEDLING_WORKDIR}/../../src/device/usbd.c;${CEEDLING_WORKDIR}/../../src/class/msc/msc_device.c;${CEEDLING_WORKDIR}/../../src/common/tusb_fifo.c"
"${CEEDLING_BUILD_DIR}/test/mocks/test_msc_device/mock_dcd.c"
)