summaryrefslogtreecommitdiff
path: root/src/common/tusb_sysview.h
blob: 46471a42523ccb387a99b797643f6c91681c29bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/*
 * 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_