blob: 46471a42523ccb387a99b797643f6c91681c29bb [file]
/*
* SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*/
#ifndef TUSB_SYSVIEW_H_
#define TUSB_SYSVIEW_H_
#include "tusb_option.h"
#include "common/tusb_compiler.h" // TU_XSTRCAT
#include "common/tusb_verify.h" // TU_ASSERT, TU_MESS_FAILED, TU_BREAKPOINT (TUD_SYSVIEW_ASSERT)
#ifdef __cplusplus
extern "C" {
#endif
#define TU_SYSVIEW_ENABLED (CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW)
// Category levels — overridable, must expand to a literal 1..4. Defined
// unconditionally (not gated on TU_SYSVIEW_ENABLED) so usbd.h/usbh.h can
// compare CFG_TUD_SYSVIEW/CFG_TUH_SYSVIEW against them even when SYSVIEW is
// fully disabled; an undefined macro reads as 0 in #if, which would make
// "CFG_TUD_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_ISR" true (0 >= 0) instead of
// false when the level macro is otherwise unavailable.
#ifndef CFG_TUSB_SYSVIEW_LEVEL_ISR
#define CFG_TUSB_SYSVIEW_LEVEL_ISR 1 // USB interrupt enter/exit
#endif
#ifndef CFG_TUSB_SYSVIEW_LEVEL_USB
#define CFG_TUSB_SYSVIEW_LEVEL_USB 2 // usbd/usbh core functions
#endif
#ifndef CFG_TUSB_SYSVIEW_LEVEL_PORT
#define CFG_TUSB_SYSVIEW_LEVEL_PORT 3 // dcd/hcd API (wrapped at usbd/usbh call sites)
#endif
#ifndef CFG_TUSB_SYSVIEW_LEVEL_CLASS
#define CFG_TUSB_SYSVIEW_LEVEL_CLASS 4 // class driver API
#endif
#if TU_SYSVIEW_ENABLED
// SEGGER.h defines INLINE (under #ifndef) and leaves it defined; nothing here uses it, but a
// vendor SDK header included later in the same TU (lpcopen's lpc_types.h, unconditional
// #define INLINE inline) redefines it and fails -Werror. Drop only the copy SEGGER.h added --
// an INLINE the SDK defined earlier is the SDK's to keep.
#ifdef INLINE
#define TU_SV_INLINE_PREDEFINED
#endif
#include "SEGGER_SYSVIEW.h"
#ifndef TU_SV_INLINE_PREDEFINED
#undef INLINE
#endif
#undef TU_SV_INLINE_PREDEFINED
// Function-timing event ids, recorded as TU_SV_EVENT_BASE + id (see below).
// Not registered as a SEGGER_SYSVIEW_MODULE (SystemView shows them as raw
// "Event(txxx)" instead of a name) — kept here, in this fixed order, so
// Task 5's host-side reporter (sysview_report.py) can map id -> name itself:
// 0 tud_task, 1 usbd_edpt_xfer, 2 dcd_edpt_xfer, 3 tud_cdc_write_flush,
// 4 tud_cdc_read, 5 mscd_xfer_cb, 6 tuh_task, 7 hcd_edpt_xfer
typedef enum {
TU_SV_ID_TUD_TASK = 0, // one usbd event processed (level USB)
TU_SV_ID_USBD_XFER, // usbd_edpt_xfer (level USB)
TU_SV_ID_DCD_XFER, // dcd_edpt_xfer call (level PORT)
TU_SV_ID_CDC_FLUSH, // tud_cdc_n_write_flush (level CLASS)
TU_SV_ID_CDC_READ, // tud_cdc_n_read (level CLASS)
TU_SV_ID_MSC_XFER, // mscd_xfer_cb (level CLASS)
TU_SV_ID_TUH_TASK, // one usbh event processed (level USB)
TU_SV_ID_HCD_XFER, // hcd_edpt_xfer call (level PORT)
TU_SV_ID_COUNT
} tu_sysview_id_t;
void tusb_sysview_init(void);
void tusb_sysview_stack_report(void);
// Give a kernel object (mutex, semaphore) a name in SystemView, so a blocked task
// reads "waiting on usbd_mutex" instead of a bare address. Registered, not sent
// immediately: objects are created in tud_init()/tuh_init(), long before the
// recorder attaches, and SEGGER only replays the system-description callback on
// connect -- a name sent at creation time is gone from the ring by then.
void tusb_sysview_name_resource(const void* handle, const char* name);
// CPU clock reported to SystemView. Weak default returns the CMSIS SystemCoreClock; BSPs whose
// SDK has no such global (e.g. rp2040's Pico SDK) override it in hw/bsp/<family>/family.c.
uint32_t tusb_sysview_cpu_freq(void);
// FreeRTOSConfig.h's traceMALLOC/traceFREE hooks (Task 4) declare these too, for
// TUs that never include this header; shared guard avoids -Wredundant-decls
// regardless of which header a given TU includes first.
#ifndef TU_SYSVIEW_HEAP_HOOKS_DECLARED
#define TU_SYSVIEW_HEAP_HOOKS_DECLARED
void tusb_sysview_heap_alloc(void* ptr, unsigned size);
void tusb_sysview_heap_free(void* ptr);
#endif
// Fixed event base instead of a registered SEGGER_SYSVIEW_MODULE: SystemView
// 4.10b on Linux greys out File > Save Recording / Export Data as soon as ANY
// module is registered (bench-proven with 5 module configurations spanning
// content/callback/ordering/description-file), which silently kills every
// export. 512 == MODULE_EVENT_OFFSET, the value SEGGER_SYSVIEW_RegisterModule()
// would hand out to the first (and only) module here anyway
// (lib/SystemView/SYSVIEW/SEGGER_SYSVIEW.c:182),
// so recorded event ids are unchanged — only the RegisterModule() call itself
// is gone, restoring host-side export.
#define TU_SV_EVENT_BASE 512
// Per-level backends: _TUD_SV_CALL_<n> is live iff CFG_TUD_SYSVIEW >= n.
// TU_LOG-style: TUD_SYSVIEW_CALL(level, id) token-pastes to the backend, so a
// site whose level exceeds the config expands to nothing. The level argument
// is one of the CFG_TUSB_SYSVIEW_LEVEL_* macros (expands to 1..4 first).
#define _TU_SV_RECORD(_id) SEGGER_SYSVIEW_RecordVoid(TU_SV_EVENT_BASE + (_id))
#define _TU_SV_END(_id) SEGGER_SYSVIEW_RecordEndCall(TU_SV_EVENT_BASE + (_id))
#if CFG_TUD_SYSVIEW >= 1
#define _TUD_SV_CALL_1(_id) _TU_SV_RECORD(_id)
#define _TUD_SV_RET_1(_id) _TU_SV_END(_id)
#else
#define _TUD_SV_CALL_1(_id)
#define _TUD_SV_RET_1(_id)
#endif
#if CFG_TUD_SYSVIEW >= 2
#define _TUD_SV_CALL_2(_id) _TU_SV_RECORD(_id)
#define _TUD_SV_RET_2(_id) _TU_SV_END(_id)
#else
#define _TUD_SV_CALL_2(_id)
#define _TUD_SV_RET_2(_id)
#endif
#if CFG_TUD_SYSVIEW >= 3
#define _TUD_SV_CALL_3(_id) _TU_SV_RECORD(_id)
#define _TUD_SV_RET_3(_id) _TU_SV_END(_id)
#else
#define _TUD_SV_CALL_3(_id)
#define _TUD_SV_RET_3(_id)
#endif
#if CFG_TUD_SYSVIEW >= 4
#define _TUD_SV_CALL_4(_id) _TU_SV_RECORD(_id)
#define _TUD_SV_RET_4(_id) _TU_SV_END(_id)
#else
#define _TUD_SV_CALL_4(_id)
#define _TUD_SV_RET_4(_id)
#endif
#if CFG_TUH_SYSVIEW >= 1
#define _TUH_SV_CALL_1(_id) _TU_SV_RECORD(_id)
#define _TUH_SV_RET_1(_id) _TU_SV_END(_id)
#else
#define _TUH_SV_CALL_1(_id)
#define _TUH_SV_RET_1(_id)
#endif
#if CFG_TUH_SYSVIEW >= 2
#define _TUH_SV_CALL_2(_id) _TU_SV_RECORD(_id)
#define _TUH_SV_RET_2(_id) _TU_SV_END(_id)
#else
#define _TUH_SV_CALL_2(_id)
#define _TUH_SV_RET_2(_id)
#endif
#if CFG_TUH_SYSVIEW >= 3
#define _TUH_SV_CALL_3(_id) _TU_SV_RECORD(_id)
#define _TUH_SV_RET_3(_id) _TU_SV_END(_id)
#else
#define _TUH_SV_CALL_3(_id)
#define _TUH_SV_RET_3(_id)
#endif
#if CFG_TUH_SYSVIEW >= 4
#define _TUH_SV_CALL_4(_id) _TU_SV_RECORD(_id)
#define _TUH_SV_RET_4(_id) _TU_SV_END(_id)
#else
#define _TUH_SV_CALL_4(_id)
#define _TUH_SV_RET_4(_id)
#endif
#define TUD_SYSVIEW_CALL(_level, _id) TU_XSTRCAT(_TUD_SV_CALL_, _level)(_id)
#define TUD_SYSVIEW_RET(_level, _id) TU_XSTRCAT(_TUD_SV_RET_, _level)(_id)
#define TUH_SYSVIEW_CALL(_level, _id) TU_XSTRCAT(_TUH_SV_CALL_, _level)(_id)
#define TUH_SYSVIEW_RET(_level, _id) TU_XSTRCAT(_TUH_SV_RET_, _level)(_id)
// ISR wrap serves the shared tusb_int_handler entry (device and/or host). Routed through a
// depth-counted pair of functions (tusb_sysview.c), not straight to SEGGER_SYSVIEW_Record*ISR():
// ten dual-role BSPs call tud_int_handler()+tuh_int_handler() back-to-back from ONE hardware
// ISR, and family_support.cmake keeps CFG_TUD_SYSVIEW == CFG_TUH_SYSVIEW, so both self-wrap and
// one real interrupt would otherwise emit ENTER,EXIT,ENTER,EXIT -- double activation count, each
// span timed at roughly half its true duration. The depth counter alone only fixes NESTED
// pairs, though: those ten BSPs' vector ISRs (hw/bsp/<family>/family.c) each add one more outer
// ENTER/EXIT bracket around the whole tud_+tuh_int_handler() body, which is what makes the two
// self-wrapped inner calls nest inside it for this counter to collapse.
#if (CFG_TUD_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_ISR) || (CFG_TUH_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_ISR)
void tusb_sysview_isr_enter(void);
void tusb_sysview_isr_exit(void);
#define TU_SYSVIEW_ISR_ENTER() tusb_sysview_isr_enter()
#define TU_SYSVIEW_ISR_EXIT() tusb_sysview_isr_exit()
#else
#define TU_SYSVIEW_ISR_ENTER()
#define TU_SYSVIEW_ISR_EXIT()
#endif
#else // !TU_SYSVIEW_ENABLED
#define TU_SYSVIEW_ISR_ENTER()
#define TU_SYSVIEW_ISR_EXIT()
#define TUD_SYSVIEW_CALL(_level, _id)
#define TUD_SYSVIEW_RET(_level, _id)
#define TUH_SYSVIEW_CALL(_level, _id)
#define TUH_SYSVIEW_RET(_level, _id)
#define tusb_sysview_init()
#define tusb_sysview_stack_report()
#endif // TU_SYSVIEW_ENABLED
// TU_ASSERT with a SystemView RET inserted before the return, so a failing assertion inside an
// instrumented function still closes its CALL/RET pair instead of leaving it dangling for the
// decoder to splice onto a later invocation. _ret is the returned value, left empty for a void
// function. Callers bind the level/id/return once with a file-local alias so the expansion
// itself lives in exactly one place.
#if CFG_TUD_SYSVIEW
// SYSVIEW compiled in: the RET must run before the return, which means evaluating _cond exactly
// once and branching before any return happens. TU_ASSERT's own expansion (tusb_verify.h,
// itself an application override point via #ifndef TU_ASSERT) is a self-contained do/while with
// the return baked in -- calling into it and splicing a statement before its return would mean
// either re-evaluating _cond (unsafe: several call sites pass a function call with side effects,
// e.g. usbd_edpt_xfer()/prepare_cbw()) or duplicating its body. So this hard-codes TU_ASSERT's
// DEFAULT failure sequence instead of routing through an application's TU_ASSERT override --
// documented limitation, not fixed here. Concretely: an application whose TU_ASSERT override
// recovers instead of trapping (e.g. logs and continues, or resets) still hits TU_BREAKPOINT()
// at every ON site in a SYSVIEW build, so a failure a production build would have survived can
// instead halt a SYSVIEW build.
// Level-gated the same way the CALL/RET backends are (token-paste on the level): only a site
// whose level is actually compiled in needs the hard-coded sequence. Below its level the RET
// expands to nothing, so there is no reason to bypass the override -- defer to TU_ASSERT and
// keep it working. Without this, -DSYSVIEW=1 silently disabled an application's TU_ASSERT for
// all six class/USB-level sites while inserting no RET at all.
#define _TUD_SV_ASSERT_ON(_cond, _level, _id, _ret) \
do { if (!(_cond)) { TU_MESS_FAILED(); TU_BREAKPOINT(); \
TUD_SYSVIEW_RET(_level, _id); return _ret; } } while(0)
#define _TUD_SV_ASSERT_OFF(_cond, _level, _id, _ret) TU_ASSERT(_cond, _ret)
#define _TUD_SV_ASSERT_1 _TUD_SV_ASSERT_ON
#if CFG_TUD_SYSVIEW >= 2
#define _TUD_SV_ASSERT_2 _TUD_SV_ASSERT_ON
#else
#define _TUD_SV_ASSERT_2 _TUD_SV_ASSERT_OFF
#endif
#if CFG_TUD_SYSVIEW >= 3
#define _TUD_SV_ASSERT_3 _TUD_SV_ASSERT_ON
#else
#define _TUD_SV_ASSERT_3 _TUD_SV_ASSERT_OFF
#endif
#if CFG_TUD_SYSVIEW >= 4
#define _TUD_SV_ASSERT_4 _TUD_SV_ASSERT_ON
#else
#define _TUD_SV_ASSERT_4 _TUD_SV_ASSERT_OFF
#endif
#define TUD_SYSVIEW_ASSERT(_cond, _level, _id, _ret) \
TU_XSTRCAT(_TUD_SV_ASSERT_, _level)(_cond, _level, _id, _ret)
#else
// SYSVIEW off: TUD_SYSVIEW_RET is already a no-op here, so defer entirely to TU_ASSERT --
// this is what lets an application's #define TU_ASSERT override (tusb_verify.h) take effect.
#define TUD_SYSVIEW_ASSERT(_cond, _level, _id, _ret) TU_ASSERT(_cond, _ret)
#endif
#ifdef __cplusplus
}
#endif
#endif // TUSB_SYSVIEW_H_