From 3baa7c8fbafd2cbccc5810626ebbfaa50c0ea4da Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 11 Aug 2026 17:07:23 +0700 Subject: 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. --- src/class/cdc/cdc_device.c | 11 +- src/class/msc/msc_device.c | 29 +++- src/common/tusb_sysview.c | 4 +- src/device/usbd.c | 37 ++++- src/host/usbh.c | 202 +++++++++++++++++---------- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 8 ++ src/portable/raspberrypi/rp2040/hcd_rp2040.c | 9 ++ 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) { -- cgit v1.3.1