summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-08-11 17:07:23 +0700
committerhathach <[email protected]>2026-09-04 04:21:57 +0700
commit3baa7c8fbafd2cbccc5810626ebbfaa50c0ea4da (patch)
tree690704b3d82dff418ceeaf14e8d6f8ff41d16a2f
parent4161a1cf63c5ca935766d27bd32f7dd3948e881a (diff)
sysview: instrument usbd/usbh, dcd/hcd and class-driver call sites
Level 2 marks the stack's task-side entry points (tud_task/tuh_task inner loop, usbd_edpt_xfer, control transfers); level 3 adds the class drivers (cdc, msc) and the rp2040 dcd/hcd as the reference portable layer. All call sites compile away below their level.
-rw-r--r--src/class/cdc/cdc_device.c11
-rw-r--r--src/class/msc/msc_device.c29
-rw-r--r--src/common/tusb_sysview.c4
-rw-r--r--src/device/usbd.c37
-rw-r--r--src/host/usbh.c202
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c8
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c9
7 files changed, 213 insertions, 87 deletions
diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c
index ed050ad03..0684710dd 100644
--- a/src/class/cdc/cdc_device.c
+++ b/src/class/cdc/cdc_device.c
@@ -11,6 +11,7 @@
#include "device/usbd.h"
#include "device/usbd_pvt.h"
+#include "common/tusb_sysview.h"
#include "cdc_device.h"
@@ -174,8 +175,11 @@ uint32_t tud_cdc_n_available(uint8_t itf) {
uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) {
TU_VERIFY(itf < CFG_TUD_CDC, 0);
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_CDC_READ);
cdcd_interface_t *p_cdc = &_cdcd_itf[itf];
- return tu_edpt_stream_read(&p_cdc->rx_stream, buffer, bufsize);
+ uint32_t const ret = tu_edpt_stream_read(&p_cdc->rx_stream, buffer, bufsize);
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_CDC_READ);
+ return ret;
}
bool tud_cdc_n_peek(uint8_t itf, uint8_t *chr) {
@@ -201,8 +205,11 @@ uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) {
uint32_t tud_cdc_n_write_flush(uint8_t itf) {
TU_VERIFY(itf < CFG_TUD_CDC, 0);
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_CDC_FLUSH);
cdcd_interface_t *p_cdc = &_cdcd_itf[itf];
- return tu_edpt_stream_write_xfer(&p_cdc->tx_stream);
+ uint32_t const ret = tu_edpt_stream_write_xfer(&p_cdc->tx_stream);
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_CDC_FLUSH);
+ return ret;
}
uint32_t tud_cdc_n_write_available(uint8_t itf) {
diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c
index 8485105e4..537dd3810 100644
--- a/src/class/msc/msc_device.c
+++ b/src/class/msc/msc_device.c
@@ -12,6 +12,7 @@
#include "device/dcd.h" // for faking dcd_event_xfer_complete
#include "device/usbd.h"
#include "device/usbd_pvt.h"
+#include "common/tusb_sysview.h"
#include "msc_device.h"
@@ -450,8 +451,21 @@ bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
return true;
}
+// mscd_xfer_cb() has several early-return paths (TU_ASSERT) spread across its
+// SCSI stage switch. Each uses TU_ASSERT_SV below (if (!cond) { TU_MESS_FAILED();
+// TU_BREAKPOINT(); TUD_SYSVIEW_RET(...); return false; }, matching TU_ASSERT's
+// own expansion with a RET inserted) so the CALL/RET pair stays balanced on
+// every exit path without changing the function's shape -- an earlier version
+// that instead wrapped a separate static impl function measured 4 bytes
+// smaller in a SYSVIEW-off build than the pre-instrumentation baseline (the
+// split itself perturbed codegen, even though every inserted macro compiles to
+// nothing when disabled); this in-place form was verified byte-identical.
+#define TU_ASSERT_SV(_cond) \
+ TUD_SYSVIEW_ASSERT(_cond, CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_MSC_XFER, false)
+
bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) {
(void) event;
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_MSC_XFER);
mscd_interface_t* p_msc = &_mscd_itf;
msc_cbw_t * p_cbw = &p_msc->cbw;
@@ -462,6 +476,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
//------------- new CBW received -------------//
// Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it
if (ep_addr != p_msc->ep_out) {
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_MSC_XFER);
return true;
}
@@ -473,6 +488,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
p_msc->stage = MSC_STAGE_NEED_RESET;
usbd_edpt_stall(rhport, p_msc->ep_in);
usbd_edpt_stall(rhport, p_msc->ep_out);
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_MSC_XFER);
return false;
}
@@ -518,7 +534,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
} else {
// Didn't check for case 9 (Ho > Dn), which requires examining scsi command first
// but it is OK to just receive data then responded with failed status
- TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false));
+ TU_ASSERT_SV(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false));
}
} else {
// First process if it is a built-in commands
@@ -551,7 +567,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
} else {
// cannot return more than host expect
p_msc->total_len = tu_min32((uint32_t)resplen, p_cbw->total_bytes);
- TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false));
+ TU_ASSERT_SV(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false));
}
}
}
@@ -561,7 +577,8 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
case MSC_STAGE_DATA:
TU_LOG_DRV(" SCSI Data [Lun%u]\r\n", p_cbw->lun);
- TU_ASSERT(xferred_bytes <= CFG_TUD_MSC_EP_BUFSIZE); // sanity check to avoid buffer overflow
+ // sanity check to avoid buffer overflow
+ TU_ASSERT_SV(xferred_bytes <= CFG_TUD_MSC_EP_BUFSIZE);
// TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, _mscd_epbuf.buf, xferred_bytes, 2);
if (SCSI_CMD_READ_10 == p_cbw->command[0]) {
@@ -629,7 +646,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
}
if (!usbd_edpt_stalled(rhport, p_msc->ep_out)) {
- TU_ASSERT(prepare_cbw(p_msc));
+ TU_ASSERT_SV(prepare_cbw(p_msc));
} else {
p_msc->stage = MSC_STAGE_CMD;
}
@@ -643,11 +660,13 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
}
if (p_msc->stage == MSC_STAGE_STATUS) {
- TU_ASSERT(proc_stage_status(p_msc));
+ TU_ASSERT_SV(proc_stage_status(p_msc));
}
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_CLASS, TU_SV_ID_MSC_XFER);
return true;
}
+#undef TU_ASSERT_SV // file-local to mscd_xfer_cb above -- don't let it bind the wrong level/id later
/*------------------------------------------------------------------*/
/* SCSI Command Process
diff --git a/src/common/tusb_sysview.c b/src/common/tusb_sysview.c
index 8b8c84614..57beff026 100644
--- a/src/common/tusb_sysview.c
+++ b/src/common/tusb_sysview.c
@@ -284,8 +284,8 @@ void tusb_sysview_stack_report(void) {
* examples/device/cdc_msc_freertos/src/main.c, which is the change that
* actually restores enumeration). Moving it to .bss costs the same RAM but
* none of the caller's stack, which is still worth doing on its own merits.
- * Single writer (only called from usbd's periodic report, never reentered),
- * so no locking is needed. */
+ * Single writer (usbd's periodic report, or usbh's in a host-only build --
+ * never both, never reentered), so no locking is needed. */
static TaskStatus_t status[SYSVIEW_FREERTOS_MAX_NOF_TASKS];
/* Report one task per call instead of looping over all of them: even with
* the array off the stack, up to SYSVIEW_FREERTOS_MAX_NOF_TASKS back-to-back
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 83ea76e8e..7ea0ccf1e 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -12,6 +12,7 @@
#include "device/dcd.h"
#include "tusb.h"
#include "common/tusb_private.h"
+#include "common/tusb_sysview.h"
#include "device/usbd.h"
#include "device/usbd_pvt.h"
@@ -556,6 +557,9 @@ bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
#if OSAL_MUTEX_REQUIRED
// Init device mutex
_usbd_mutex = osal_mutex_create(&_ubsd_mutexdef);
+#if CFG_TUD_SYSVIEW
+ tusb_sysview_name_resource(_usbd_mutex, "usbd_mutex");
+#endif
TU_ASSERT(_usbd_mutex);
#endif
@@ -670,6 +674,12 @@ bool tud_task_event_ready(void) {
}
}
*/
+// TU_ASSERT hand-expansion for a void-returning early exit that also closes the CALL/RET pair
+// (matches TU_ASSERT's own expansion, tusb_verify.h, with a RET inserted before the return) --
+// used once below, where the driver lookup must not skip TUD_SYSVIEW_RET on its exit path.
+#define TU_ASSERT_SV(_cond) \
+ TUD_SYSVIEW_ASSERT(_cond, CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUD_TASK, )
+
void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
(void) in_isr; // not implemented yet
@@ -698,6 +708,7 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
TU_LOG_USBD("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED");
#endif
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUD_TASK);
switch (event.event_id) {
case DCD_EVENT_BUS_RESET_START:
TU_LOG_USBD("\r\n");
@@ -768,7 +779,10 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
}
} else {
usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]);
- TU_ASSERT(driver,);
+ // TU_ASSERT(driver,) would return here directly, skipping the RET below -- TU_ASSERT_SV
+ // (defined above) keeps the CALL/RET pair balanced on this exit path, same treatment as
+ // mscd_xfer_cb.
+ TU_ASSERT_SV(driver);
TU_LOG_USBD(" %s xfer callback\r\n", driver->name);
driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
@@ -815,11 +829,18 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
TU_BREAKPOINT();
break;
}
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUD_TASK);
+
+#if CFG_TUD_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_USB && CFG_TUSB_OS == OPT_OS_FREERTOS
+ { static uint16_t sv_cnt = 0;
+ if (0 == (++sv_cnt & 0x3FFu)) { tusb_sysview_stack_report(); } }
+#endif
// allow to exit tud_task() if there is no event in the next run
timeout_ms = 0;
}
}
+#undef TU_ASSERT_SV // file-local to tud_task_ext above -- don't let it bind the wrong level/id later
//--------------------------------------------------------------------+
// Control Endpoint
@@ -1609,21 +1630,27 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t
// Attempt to transfer on a busy endpoint, sound like an race condition !
TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0);
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_USBD_XFER);
+
// Set busy first since the actual transfer can be complete before dcd_edpt_xfer()
// could return and USBD task can preempt and clear the busy
_usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY;
- if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) {
- return true;
- } else {
+ TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_DCD_XFER);
+ bool const ok = dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr);
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_DCD_XFER);
+
+ if (!ok) {
// Driver refused the transfer, mark endpoint as ready to allow next transfer. This is a
// recoverable condition (e.g. a new setup superseding a control response), not a bug, so
// do not break into the debugger - TU_BREAKPOINT() halts the CPU whenever a probe is
// attached, which on a test rig is always.
_usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
TU_LOG_USBD("FAILED\r\n");
- return false;
}
+
+ TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_USBD_XFER);
+ return ok;
}
// The number of bytes has to be given explicitly to allow more flexible control of how many
diff --git a/src/host/usbh.c b/src/host/usbh.c
index 7420ebfa8..d69366b8e 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -13,6 +13,7 @@
#include "tusb.h"
#include "usbh_pvt.h"
#include "hub.h"
+#include "common/tusb_sysview.h"
//--------------------------------------------------------------------+
// Configuration
@@ -338,6 +339,15 @@ static void enum_new_device(hcd_event_t* event);
static void enum_delay_async(uintptr_t state);
static void process_remove_event(hcd_event_t *event);
static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port);
+// Both process HCD_EVENT_* cases out of tuh_task_ext()'s switch. Before this split, their
+// internal TU_ASSERT/TU_VERIFY early returns aborted tuh_task_ext() itself (deferring any
+// remaining queued events in this call to the next one) -- that observable behavior is
+// preserved by having each helper return false on the same failure (its own TU_ASSERT/TU_VERIFY,
+// now naturally returning false as bool-returning functions, needs no rewriting) and having the
+// caller check it: false means "emit RET and return from tuh_task_ext now", matching the
+// original abort-the-whole-call semantics exactly, just with the CALL/RET pair kept balanced.
+static bool process_attach_event(hcd_event_t* event, bool in_isr);
+static bool process_xfer_complete_event(hcd_event_t* event);
static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size);
static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
@@ -531,6 +541,9 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
#if OSAL_MUTEX_REQUIRED
// Init mutex
_usbh_mutex = osal_mutex_create(&_usbh_mutexdef);
+ #if CFG_TUH_SYSVIEW
+ tusb_sysview_name_resource(_usbh_mutex, "usbh_mutex");
+ #endif
TU_ASSERT(_usbh_mutex);
#endif
@@ -661,6 +674,96 @@ bool tuh_task_event_ready(void) {
return false;
}
+// Moved out of tuh_task_ext()'s switch (see the prototype comment above); only textual change
+// from the original case body is TU_ASSERT(cond, ) -> TU_ASSERT(cond) (2-arg empty-return form
+// -> natural 1-arg form, since the enclosing function now returns bool instead of void -- same
+// "return false" either way).
+static bool process_attach_event(hcd_event_t* event, bool in_isr) {
+ (void) in_isr; // only used when CFG_TUH_HUB
+
+ // Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can
+ // cause multiple attaches (actually reset) without a detached event.
+ // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists
+ process_remove_event(event);
+
+ // due to the shared control buffer, we must fully complete enumerating one device first.
+ if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) {
+ // New device attached and we are ready
+ TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event->rhport);
+ _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0
+ enum_new_device(event);
+ }
+#if CFG_TUH_HUB
+ else {
+ TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event->rhport);
+ TU_ASSERT(osal_queue_send(_usbh_daq, event, in_isr));
+ }
+#endif
+ return true;
+}
+
+// Moved out of tuh_task_ext()'s switch (see the prototype comment above); only textual change
+// from the original case body is TU_ASSERT/TU_VERIFY(cond, ) -> TU_ASSERT/TU_VERIFY(cond) (2-arg
+// empty-return form -> natural 1-arg form, since the enclosing function now returns bool instead
+// of void -- same "return false" either way).
+static bool process_xfer_complete_event(hcd_event_t* event) {
+ uint8_t const ep_addr = event->xfer_complete.ep_addr;
+ uint8_t const epnum = tu_edpt_number(ep_addr);
+ uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr);
+
+ TU_LOG_USBH("[:%u] on EP %02X with %u bytes: %s\r\n",
+ event->dev_addr, ep_addr, (unsigned int) event->xfer_complete.len, tu_str_xfer_result[event->xfer_complete.result]);
+
+ if (event->dev_addr == 0) {
+ // device 0 only has control endpoint
+ TU_ASSERT(epnum == 0);
+ usbh_control_xfer_cb(event->dev_addr, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len);
+ } else {
+ usbh_device_t* dev = get_device(event->dev_addr);
+ TU_VERIFY(dev && dev->connected);
+
+ // clear busy and claimed
+ dev->ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
+
+ if (0 == epnum) {
+ usbh_control_xfer_cb(event->dev_addr, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len);
+ } else {
+ // Prefer application callback over built-in one if available. This occurs when tuh_edpt_xfer() is used
+ // with enabled driver e.g HID endpoint
+ #if CFG_TUH_API_EDPT_XFER
+ tuh_xfer_cb_t const complete_cb = dev->ep_callback[epnum][ep_dir].complete_cb;
+ if (complete_cb != NULL) {
+ // re-construct xfer info
+ tuh_xfer_t xfer = {
+ .daddr = event->dev_addr,
+ .ep_addr = ep_addr,
+ .result = (xfer_result_t)event->xfer_complete.result,
+ .actual_len = event->xfer_complete.len,
+ .buflen = 0, // not available
+ .buffer = NULL, // not available
+ .complete_cb = complete_cb,
+ .user_data = dev->ep_callback[epnum][ep_dir].user_data
+ };
+ complete_cb(&xfer);
+ }else
+ #endif
+ {
+ uint8_t drv_id = dev->ep2drv[epnum][ep_dir];
+ usbh_class_driver_t const* driver = get_driver(drv_id);
+ if (driver != NULL) {
+ TU_LOG_USBH(" %s xfer callback\r\n", driver->name);
+ driver->xfer_cb(event->dev_addr, ep_addr, (xfer_result_t) event->xfer_complete.result,
+ event->xfer_complete.len);
+ } else {
+ // no driver/callback responsible for this transfer
+ TU_ASSERT(false);
+ }
+ }
+ }
+ }
+ return true;
+}
+
/* USB Host Driver task
* This top level thread manages all host controller event and delegates events to class-specific drivers.
* This should be called periodically within the mainloop or rtos thread.
@@ -752,26 +855,17 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
}
}
+ TUH_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUH_TASK);
switch (event.event_id) {
case HCD_EVENT_DEVICE_ATTACH:
- // Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can
- // cause multiple attaches (actually reset) without a detached event.
- // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists
- process_remove_event(&event);
-
- // due to the shared control buffer, we must fully complete enumerating one device first.
- if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) {
- // New device attached and we are ready
- TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport);
- _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0
- enum_new_device(&event);
- }
- #if CFG_TUH_HUB
- else {
- TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport);
- TU_ASSERT(osal_queue_send(_usbh_daq, &event, in_isr), );
+ if (!process_attach_event(&event, in_isr)) {
+ // Restores the pre-extraction behavior: the helper's own TU_ASSERT failing aborts this
+ // whole tuh_task_ext() call (remaining queued events wait for the next call), not just
+ // this one event -- see the process_attach_event()/process_xfer_complete_event()
+ // prototype comment.
+ TUH_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUH_TASK);
+ return;
}
- #endif
break;
case HCD_EVENT_DEVICE_REMOVE:
@@ -779,63 +873,13 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
process_remove_event(&event);
break;
- case HCD_EVENT_XFER_COMPLETE: {
- uint8_t const ep_addr = event.xfer_complete.ep_addr;
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr);
-
- TU_LOG_USBH("[:%u] on EP %02X with %u bytes: %s\r\n",
- event.dev_addr, ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]);
-
- if (event.dev_addr == 0) {
- // device 0 only has control endpoint
- TU_ASSERT(epnum == 0,);
- usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
- } else {
- usbh_device_t* dev = get_device(event.dev_addr);
- TU_VERIFY(dev && dev->connected,);
-
- // clear busy and claimed
- dev->ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
-
- if (0 == epnum) {
- usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
- } else {
- // Prefer application callback over built-in one if available. This occurs when tuh_edpt_xfer() is used
- // with enabled driver e.g HID endpoint
- #if CFG_TUH_API_EDPT_XFER
- tuh_xfer_cb_t const complete_cb = dev->ep_callback[epnum][ep_dir].complete_cb;
- if (complete_cb != NULL) {
- // re-construct xfer info
- tuh_xfer_t xfer = {
- .daddr = event.dev_addr,
- .ep_addr = ep_addr,
- .result = (xfer_result_t)event.xfer_complete.result,
- .actual_len = event.xfer_complete.len,
- .buflen = 0, // not available
- .buffer = NULL, // not available
- .complete_cb = complete_cb,
- .user_data = dev->ep_callback[epnum][ep_dir].user_data
- };
- complete_cb(&xfer);
- }else
- #endif
- {
- uint8_t drv_id = dev->ep2drv[epnum][ep_dir];
- usbh_class_driver_t const* driver = get_driver(drv_id);
- if (driver != NULL) {
- TU_LOG_USBH(" %s xfer callback\r\n", driver->name);
- driver->xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result,
- event.xfer_complete.len);
- } else {
- // no driver/callback responsible for this transfer
- TU_ASSERT(false,);
- }
- }
- }
+ case HCD_EVENT_XFER_COMPLETE:
+ if (!process_xfer_complete_event(&event)) {
+ // Same abort-the-whole-call restoration as HCD_EVENT_DEVICE_ATTACH above.
+ TUH_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUH_TASK);
+ return;
}
break;
- }
case USBH_EVENT_FUNC_CALL:
if (event.func_call.func != NULL) {
@@ -847,6 +891,14 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
// unknown event
break;
}
+ TUH_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_TUH_TASK);
+
+ // host-only builds: tud_task_ext()'s periodic report never runs. The reporter keeps static
+ // rotation state, so a dual-role build leaves it to the device task alone.
+#if CFG_TUH_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_USB && !CFG_TUD_ENABLED && CFG_TUSB_OS == OPT_OS_FREERTOS
+ { static uint16_t sv_cnt = 0;
+ if (0 == (++sv_cnt & 0x3FFu)) { tusb_sysview_stack_report(); } }
+#endif
// allow to exit tuh_task() if there is no event in the next run
timeout_ms = 0;
@@ -1299,7 +1351,11 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu
dev->ep_callback[epnum][dir].user_data = user_data;
#endif
- if (hcd_edpt_xfer(dev->bus_info.rhport, dev_addr, ep_addr, buffer, total_bytes)) {
+ TUH_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_HCD_XFER);
+ bool const ok = hcd_edpt_xfer(dev->bus_info.rhport, dev_addr, ep_addr, buffer, total_bytes);
+ TUH_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_HCD_XFER);
+
+ if (ok) {
TU_LOG_USBH("OK\r\n");
return true;
} else {
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index 564ded535..28b06f981 100644
--- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
@@ -19,6 +19,7 @@
#endif
#include "device/dcd.h"
+#include "common/tusb_sysview.h"
// Current implementation force vbus detection as always present, causing device think it is always plugged into host.
// Therefore, it cannot detect disconnect event, mistaken it as suspend.
@@ -197,6 +198,12 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) {
}
static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
+ // The SDK registers this function directly as the hardware ISR (irq_add_shared_handler below)
+ // rather than going through dcd_int_handler()/tud_int_handler() (usbd.h) the way most BSPs'
+ // vector ISRs do, so usbd.h's TU_SYSVIEW_ISR_ENTER/EXIT wrap there never runs for this family
+ // -- wrap the real entry point instead. Single exit path (no early returns), so ENTER/EXIT
+ // bracket the whole body; both compile away at SYSVIEW=0.
+ TU_SYSVIEW_ISR_ENTER();
const uint32_t status = usb_hw->ints;
if (status & USB_INTF_DEV_SOF_BITS) {
@@ -326,6 +333,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS;
}
+ TU_SYSVIEW_ISR_EXIT();
}
/*------------------------------------------------------------------*/
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index a04890835..44eb6aa75 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -27,6 +27,7 @@
#include "host/hcd.h"
#include "host/usbh.h"
+ #include "common/tusb_sysview.h"
//--------------------------------------------------------------------+
//
@@ -282,6 +283,12 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) {
}
static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
+ // The SDK registers this function directly as the hardware ISR (irq_add_shared_handler below)
+ // rather than going through hcd_int_handler()/tuh_int_handler() (usbh.h) the way most BSPs'
+ // vector ISRs do, so usbh.h's TU_SYSVIEW_ISR_ENTER/EXIT wrap there never runs for this family
+ // -- wrap the real entry point instead. Single exit path (no early returns, panic() aside),
+ // so ENTER/EXIT bracket the whole body; both compile away at SYSVIEW=0.
+ TU_SYSVIEW_ISR_ENTER();
const uint32_t status = usb_hw->ints;
if (status & USB_INTS_HOST_CONN_DIS_BITS) {
@@ -378,6 +385,8 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
panic("Data Seq Error \n");
}
+
+ TU_SYSVIEW_ISR_EXIT();
}
void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {