# sysview v2 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Make SEGGER SystemView a first-class TinyUSB analysis feature — leveled in-tree instrumentation (`CFG_TUD_SYSVIEW`/`CFG_TUH_SYSVIEW` = 0–4, like `CFG_TUSB_DEBUG`) for USB task/ISR/per-function timing plus heap/stack metrics, a pinned `get_deps` dependency, machine-readable reports, post-mortem crash capture, and an OpenOCD capture route. **Architecture:** Target-side instrumentation moves from skill scripts into `src/common/tusb_sysview.[ch]` behind `CFG_TUD_SYSVIEW`/`CFG_TUH_SYSVIEW` levels (default 0 = off, zero cost; 1 = USB ISR, 2 = +usbd/usbh, 3 = +dcd/hcd API at the usbd/usbh call sites, 4 = +class drivers), hooked at `tusb_rhport_init` (init) and `tusb_int_handler` (ISR) so **no application edits are needed** — `cmake -DSYSVIEW=` is the entire instrumentation step. Host side keeps the validated v1 recorder (GUI live recorder under Xvfb) and gains a post-mortem dump script and an OpenOCD raw-capture recipe, both feeding the existing `--from-raw` decode. The reporter grows function/stack/heap/marker/blocked-time tables and `--json`. **Tech Stack:** C99 (TinyUSB style), CMake (`hw/bsp/family_support.cmake`), SEGGER SystemView target sources V4.12.0 (github.com/SEGGERMicro/SystemView) + vendored `lib/SEGGER_RTT`, Python 3 host scripts, same54_xplained + J-Trace `jtrace` on htpc for validation, stm32h743nucleo (OpenOCD/ST-Link, ci.lan rig) for the OpenOCD route. ## Global Constraints - Branch: work on `claude/add-systemview-debug` in worktree `.worktrees/add-systemview-debug` (LOCAL — never push). Symlink deps per CLAUDE.md worktree note. - Commits: imperative mood, **no Co-Authored-By/Claude-Session trailers** (authorship rule). `pre-commit run --files ` before every commit. - C99, 2-space indent, snake_case, `TU_`/`tusb_` prefixes; headers self-contained. - `CFG_TUD_SYSVIEW=0`/`CFG_TUH_SYSVIEW=0` (default) must add **zero** code/data — gated by a code-size compare (Task 8). - Never modify `lib/FreeRTOS-Kernel` or `hw/mcu/*` (vendor trees). `lib/SystemView` is a fetched dep — never edit its contents. - Hardware: one probe, one client (kill stray `systemview`/`Xvfb`/`JLink*` first); captures to scratchpad or `/tmp/sysview-*` (never `/tmp/sv-*` — SystemView deletes those); pristine reflash + revert any temporary validation edits before finishing a task that flashed hardware. - Validation board `same54_xplained`: J-Link device `ATSAME54P20`, probe nickname `jtrace`, TTY `/dev/serial/by-id/usb-TinyUSB_TinyUSB_Device_*-if00`. Traffic one-liner (referenced by tasks as “CDC load”): `stty -F $TTY raw -echo; (timeout 7 cat $TTY >/dev/null &); for i in $(seq 65); do head -c 512 /dev/zero > $TTY; sleep 0.1; done` - Worktree paths below are relative to the worktree root. --- ### Task 1: `lib/SystemView` as a pinned optional dependency **Files:** - Modify: `tools/get_deps.py` (deps_optional dict, alphabetical position after `lib/lwip`) **Interfaces:** - Produces: `lib/SystemView/` populated at V4.12.0 (`SYSVIEW/SEGGER_SYSVIEW.c`, `Sample/FreeRTOSV11/…`, `Config/…`). Task 3's CMake requires exactly this path. - [ ] **Step 1: Add the dependency entry** In `tools/get_deps.py`, `deps_optional`, after the `'lib/lwip'` entry: ```python 'lib/SystemView': ['https://github.com/SEGGERMicro/SystemView.git', '92ca7a810c5765ba64911919acd511c61b6b083f', 'all'], ``` (That commit is the `V4.12.0` tag.) - [ ] **Step 2: Verify the fetch** Run: `python3 tools/get_deps.py samd5x_e5x` (from the worktree root) Expected: clones into `lib/SystemView`; then `ls lib/SystemView/SYSVIEW/SEGGER_SYSVIEW.c lib/SystemView/Sample/FreeRTOSV11/SEGGER_SYSVIEW_FreeRTOS.c` → both exist. - [ ] **Step 3: Commit** ```bash git add tools/get_deps.py git commit -m "add SEGGER SystemView target sources as optional dependency (V4.12.0)" ``` --- ### Task 2: In-tree core instrumentation (`tusb_sysview.[ch]` + hooks) **Files:** - Create: `src/common/tusb_sysview.h`, `src/common/tusb_sysview.c` - Modify: `src/tusb_option.h` (add `CFG_TUD_SYSVIEW`/`CFG_TUH_SYSVIEW` defaults), `src/tusb.c:65` (`tusb_rhport_init` — init hook), `src/tusb.c:130` (`tusb_int_handler` — ISR wrap) **Interfaces:** - Produces: `tusb_sysview_init(void)`; `TU_SYSVIEW_ISR_ENTER()/TU_SYSVIEW_ISR_EXIT()`; `TUD_SYSVIEW_CALL/RET(level, id)` + `TUH_SYSVIEW_CALL/RET(level, id)` with `tu_sysview_id_t` enum and `CFG_TUSB_SYSVIEW_LEVEL_{ISR,USB,PORT,CLASS}` category levels; `tusb_sysview_stack_report(void)`; `tusb_sysview_heap_alloc(void*, unsigned)` / `tusb_sysview_heap_free(void*)`. All compile to nothing when both config levels are 0; a site compiles only when its level ≤ the configured level (TU_LOG-style token paste). Tasks 3–5 consume these exact names. - [ ] **Step 1: `tusb_option.h` defaults** Next to the other `CFG_TUSB_*` defaults. Leveled like `CFG_TUSB_DEBUG` (0 = off; a site is compiled only when its level ≤ the configured level): ```c // SEGGER SystemView instrumentation level (like CFG_TUSB_DEBUG): // 0=off 1=USB ISR 2=+usbd/usbh functions 3=+dcd/hcd API 4=+class driver API // Requires lib/SystemView (python3 tools/get_deps.py) and a SYSVIEW= build. #ifndef CFG_TUD_SYSVIEW #define CFG_TUD_SYSVIEW 0 #endif #ifndef CFG_TUH_SYSVIEW #define CFG_TUH_SYSVIEW 0 #endif ``` - [ ] **Step 2: Write `src/common/tusb_sysview.h`** ```c #ifndef TUSB_SYSVIEW_H_ #define TUSB_SYSVIEW_H_ #include "tusb_option.h" #include "common/tusb_compiler.h" // TU_XSTRCAT #define TU_SYSVIEW_ENABLED (CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW) #if TU_SYSVIEW_ENABLED #include "SEGGER_SYSVIEW.h" // Category levels — overridable, must expand to a literal 1..4 #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 // Function-timing event ids: index into the module description string in // tusb_sysview.c — the two MUST stay in the same order. 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; extern SEGGER_SYSVIEW_MODULE tusb_sysview_module; void tusb_sysview_init(void); void tusb_sysview_stack_report(void); void tusb_sysview_heap_alloc(void* ptr, unsigned size); void tusb_sysview_heap_free(void* ptr); // Per-level backends: _TUD_SV_CALL_ 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(tusb_sysview_module.EventOffset + (_id)) #define _TU_SV_END(_id) SEGGER_SYSVIEW_RecordEndCall(tusb_sysview_module.EventOffset + (_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 // same 4-block ladder for _TUH_SV_CALL_/RET_ gated on CFG_TUH_SYSVIEW // (write it out — 16 more lines, identical shape) #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) #if (CFG_TUD_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_ISR) || (CFG_TUH_SYSVIEW >= CFG_TUSB_SYSVIEW_LEVEL_ISR) #define TU_SYSVIEW_ISR_ENTER() SEGGER_SYSVIEW_RecordEnterISR() #define TU_SYSVIEW_ISR_EXIT() SEGGER_SYSVIEW_RecordExitISR() #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 #endif // TUSB_SYSVIEW_H_ ``` - [ ] **Step 3: Write `src/common/tusb_sysview.c`** Port of the validated v1 `SEGGER_SYSVIEW_Config_TinyUSB.c` plus module + heap/stack helpers. The whole file is inside `#if CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW`: ```c #include "tusb_option.h" #if CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW #include "common/tusb_sysview.h" #if CFG_TUSB_OS == OPT_OS_FREERTOS #include "FreeRTOS.h" #include "task.h" extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; #define SYSVIEW_OS_API (&SYSVIEW_X_OS_TraceAPI) #define SYSVIEW_OS_DESC ",O=FreeRTOS" #else #define SYSVIEW_OS_API 0 #define SYSVIEW_OS_DESC "" #endif #ifndef SYSVIEW_APP_NAME #define SYSVIEW_APP_NAME "TinyUSB" #endif #ifndef SYSVIEW_DEVICE_NAME #define SYSVIEW_DEVICE_NAME "Cortex-M" #endif #ifndef SYSVIEW_RAM_BASE #define SYSVIEW_RAM_BASE (0x20000000) #endif extern unsigned int SystemCoreClock; // Event names, same order as tu_sysview_id_t SEGGER_SYSVIEW_MODULE tusb_sysview_module = { "M=TinyUSB," "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", TU_SV_ID_COUNT, 0, NULL, NULL }; static void send_sys_desc(void) { SEGGER_SYSVIEW_SendSysDesc("N=" SYSVIEW_APP_NAME ",D=" SYSVIEW_DEVICE_NAME SYSVIEW_OS_DESC); SEGGER_SYSVIEW_SendSysDesc("I#15=SysTick"); } void tusb_sysview_init(void) { static bool inited = false; if (inited) { return; } inited = true; /* DWT cycle counter drives timestamps; enable on-target so recording * never depends on a debugger-side enable (also needed post-mortem). */ (*(volatile unsigned int*) 0xE000EDFC) |= (1u << 24); /* DEMCR.TRCENA */ (*(volatile unsigned int*) 0xE0001000) |= 1u; /* DWT_CTRL.CYCCNTENA */ SEGGER_SYSVIEW_Init(SystemCoreClock, SystemCoreClock, SYSVIEW_OS_API, send_sys_desc); SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE); SEGGER_SYSVIEW_RegisterModule(&tusb_sysview_module); SEGGER_SYSVIEW_Start(); /* self-start: host recorders only drain */ } void tusb_sysview_heap_alloc(void* ptr, unsigned size) { static bool heap_defined = false; if (!heap_defined) { heap_defined = true; SEGGER_SYSVIEW_HeapDefine(ptr, ptr, size, 0); /* base unknowable here; events still carry ptr+size */ } SEGGER_SYSVIEW_HeapAlloc(ptr, ptr, size); } void tusb_sysview_heap_free(void* ptr) { SEGGER_SYSVIEW_HeapFree(ptr, ptr); } void tusb_sysview_stack_report(void) { #if CFG_TUSB_OS == OPT_OS_FREERTOS && (configUSE_TRACE_FACILITY == 1) TaskStatus_t status[8]; UBaseType_t n = uxTaskGetSystemState(status, 8, NULL); for (UBaseType_t i = 0; i < n; i++) { SEGGER_SYSVIEW_TASKINFO info = {0}; info.TaskID = (U32)(uintptr_t) status[i].xHandle; info.sName = status[i].pcTaskName; info.Prio = status[i].uxCurrentPriority; info.StackBase = (U32)(uintptr_t) status[i].pxStackBase; info.StackUsage = status[i].usStackHighWaterMark * sizeof(StackType_t); SEGGER_SYSVIEW_SendTaskInfo(&info); } #endif } #endif /* CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW */ ``` Note: `SEGGER_SYSVIEW_TASKINFO` field names must be checked against `lib/SystemView/SYSVIEW/SEGGER_SYSVIEW.h` when compiling (V4.12 has `TaskID/sName/Prio/StackBase/StackSize/StackUsage` — use whichever of StackSize/StackUsage exist; `SendTaskInfo` also emits the STACK_INFO event). - [ ] **Step 4: Hook init and ISR in `src/tusb.c`** Top of file, with the other includes: `#include "common/tusb_sysview.h"`. In `tusb_rhport_init()` (line ~65), first statement of the function body: ```c tusb_sysview_init(); // no-op unless a SYSVIEW level is set ``` In `tusb_int_handler()` (line ~130), wrap the existing body: ```c void tusb_int_handler(uint8_t rhport, bool in_isr) { TU_SYSVIEW_ISR_ENTER(); ... existing body unchanged ... TU_SYSVIEW_ISR_EXIT(); } ``` (If the body has early `return`s, restructure to a single exit so `TU_SYSVIEW_ISR_EXIT()` always runs — check the actual body first.) - [ ] **Step 5: Verify disabled build is untouched** Run: `cd examples/device/cdc_msc_freertos && rm -rf build-off && cmake -B build-off -DBOARD=same54_xplained -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DJLINK_OPTION="-USB jtrace" . && cmake --build build-off` Expected: builds green with NO sysview sources compiled (`grep -c SYSVIEW build-off/.ninja_log` → 0). - [ ] **Step 6: Commit** ```bash git add src/common/tusb_sysview.h src/common/tusb_sysview.c src/tusb_option.h src/tusb.c git commit -m "add leveled CFG_TUD/TUH_SYSVIEW SystemView instrumentation core" ``` --- ### Task 3: Build wiring (`SYSVIEW=`) in family_support.cmake + hardware parity run **Files:** - Modify: `hw/bsp/family_support.cmake` (inside `family_configure_common`, adjacent to the `LOGGER` rtt block at ~line 480) - Delete: `.claude/skills/sysview/scripts/sysview.cmake`, `.claude/skills/sysview/scripts/SEGGER_SYSVIEW_Config_TinyUSB.c` (superseded — delete in the SAME commit that lands the replacement) **Interfaces:** - Consumes: Task 1 dep path, Task 2 sources/defines. - Produces: `cmake -DSYSVIEW=<1..4|ON>` as the complete instrumentation build switch (Tasks 4–8 build with it). CMake cache vars honored: `SYSVIEW_BUFFER_SIZE` (default 16384), `SYSVIEW_RAM_BASE` (default 0x20000000). - [ ] **Step 1: Add the SYSVIEW block** In `family_configure_common`, after the LOGGER handling: ```cmake if (SYSVIEW) if (SYSVIEW STREQUAL "ON") set(SYSVIEW 4) # bare -DSYSVIEW=ON = full instrumentation endif () if (NOT SYSVIEW MATCHES "^[1-4]$") message(FATAL_ERROR "SYSVIEW must be 1..4 (1=ISR 2=+usbd/usbh 3=+dcd/hcd 4=+class)") endif () set(SYSVIEW_SRC ${TOP}/lib/SystemView) if (NOT EXISTS ${SYSVIEW_SRC}/SYSVIEW/SEGGER_SYSVIEW.c) message(FATAL_ERROR "SYSVIEW needs lib/SystemView: run python3 tools/get_deps.py ") endif () if (NOT DEFINED SYSVIEW_BUFFER_SIZE) set(SYSVIEW_BUFFER_SIZE 16384) # 4 KB overflows under bulk USB load endif () if (NOT DEFINED SYSVIEW_RAM_BASE) set(SYSVIEW_RAM_BASE 0x20000000) endif () # vendored lib/SEGGER_RTT predates SEGGER_RTT_ConfDefaults.h; bridge it file(WRITE ${CMAKE_BINARY_DIR}/sysview_shim/SEGGER_RTT_ConfDefaults.h "#include \"SEGGER_RTT_Conf.h\"\n") target_sources(${TARGET} PUBLIC ${SYSVIEW_SRC}/SYSVIEW/SEGGER_SYSVIEW.c ${TOP}/src/common/tusb_sysview.c ${TOP}/lib/SEGGER_RTT/RTT/SEGGER_RTT_ASM_ARMv7M.S) if (NOT LOGGER STREQUAL "rtt") target_sources(${TARGET} PUBLIC ${TOP}/lib/SEGGER_RTT/RTT/SEGGER_RTT.c) endif () target_include_directories(${TARGET} PUBLIC ${SYSVIEW_SRC}/SEGGER ${SYSVIEW_SRC}/SYSVIEW ${SYSVIEW_SRC}/Config ${CMAKE_BINARY_DIR}/sysview_shim ${TOP}/lib/SEGGER_RTT/RTT ${TOP}/lib/SEGGER_RTT/Config) # vendor sources are not expected to pass TinyUSB's -Werror set set_source_files_properties( ${SYSVIEW_SRC}/SYSVIEW/SEGGER_SYSVIEW.c ${SYSVIEW_SRC}/Sample/FreeRTOSV11/SEGGER_SYSVIEW_FreeRTOS.c ${TOP}/lib/SEGGER_RTT/RTT/SEGGER_RTT.c TARGET_DIRECTORY ${TARGET} PROPERTIES COMPILE_OPTIONS "-w") target_compile_definitions(${TARGET} PUBLIC CFG_TUD_SYSVIEW=${SYSVIEW} CFG_TUH_SYSVIEW=${SYSVIEW} SYSVIEW_APP_NAME="${TARGET}" SYSVIEW_DEVICE_NAME="${BOARD}" SYSVIEW_RAM_BASE=${SYSVIEW_RAM_BASE} SEGGER_SYSVIEW_RTT_BUFFER_SIZE=${SYSVIEW_BUFFER_SIZE} SYSVIEW_FREERTOS_MAX_NOF_TASKS=16) if (TARGET freertos_kernel) target_sources(${TARGET} PUBLIC ${SYSVIEW_SRC}/Sample/FreeRTOSV11/SEGGER_SYSVIEW_FreeRTOS.c) # freertos_config is the INTERFACE target the kernel and app both see: # defines + include dirs there make the FreeRTOSConfig.h guarded block work target_compile_definitions(freertos_config INTERFACE CFG_TUD_SYSVIEW=${SYSVIEW} CFG_TUH_SYSVIEW=${SYSVIEW} SYSVIEW_FREERTOS_MAX_NOF_TASKS=16) target_include_directories(freertos_config INTERFACE ${SYSVIEW_SRC}/SEGGER ${SYSVIEW_SRC}/SYSVIEW ${SYSVIEW_SRC}/Config ${SYSVIEW_SRC}/Sample/FreeRTOSV11 ${CMAKE_BINARY_DIR}/sysview_shim ${TOP}/lib/SEGGER_RTT/RTT ${TOP}/lib/SEGGER_RTT/Config) endif () message(STATUS "SYSVIEW instrumentation enabled (buffer=${SYSVIEW_BUFFER_SIZE})") endif () ``` NOTE: until Task 4 adds the FreeRTOSConfig.h guarded include, task events come only after Task 4 — this task's parity run therefore ALSO adds Task 4's samd5x_e5x FreeRTOSConfig block if built before Task 4… to avoid that ordering trap, **run Task 4's Step 1 edit before this task's hardware step** (tasks 3 and 4 land as separate commits but validate together). - [ ] **Step 2: Build instrumented** Run: `python3 tools/get_deps.py samd5x_e5x` (if not yet), then `cd examples/device/cdc_msc_freertos && rm -rf build-sv && cmake -B build-sv -DBOARD=same54_xplained -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DSYSVIEW=4 -DJLINK_OPTION="-USB jtrace" . && cmake --build build-sv` Expected: green; `arm-none-eabi-nm build-sv/cdc_msc_freertos.elf | grep -c SEGGER_SYSVIEW` ≥ 20. - [ ] **Step 3: Hardware parity run (the v1 regression test)** Flash + record + report exactly as the sysview SKILL documents (`ninja -C build-sv cdc_msc_freertos-jlink`, then `sysview_record.py --device ATSAME54P20 --probe jtrace --elf build-sv/cdc_msc_freertos.elf --duration-ms 8000 --out /tmp/sysview-v2 --traffic-cmd ""`, then `sysview_report.py /tmp/sysview-v2`). Expected: report parity with v1 numbers — cdc ~3–4 % CPU, usbd task present, USB `ISR 96/98/99` ~34 µs p50, overflow 0 — with **zero source edits** (the win this task exists for). - [ ] **Step 4: Commit (including script deletions)** ```bash git rm .claude/skills/sysview/scripts/sysview.cmake .claude/skills/sysview/scripts/SEGGER_SYSVIEW_Config_TinyUSB.c git add hw/bsp/family_support.cmake git commit -m "wire leveled SYSVIEW build option; retire skill-local cmake injection" ``` --- ### Task 4: FreeRTOS stack + heap metrics **Files:** - Modify: `hw/bsp/samd5x_e5x/FreeRTOSConfig/FreeRTOSConfig.h` (guarded block at end of file, before the final `#endif`) - Modify: `src/device/usbd.c` (periodic `tusb_sysview_stack_report()` from the task loop) - Modify: `.claude/skills/sysview/scripts/sysview_report.py` (stack + heap + blocked-time output) **Interfaces:** - Consumes: Task 2's `tusb_sysview_stack_report/heap_alloc/heap_free`, Task 3's `freertos_config` defines. - Produces: `Stack Information` populated in contexts.csv; `Heap Alloc/Free` rows in events.txt (dynamic builds); reporter prints `stack` table (task, stack high-water bytes) + `heap` line (allocs/frees/net bytes) + `blocked_ms` column. - [ ] **Step 1: FreeRTOSConfig guarded block (samd5x_e5x)** At the END of the file, immediately before the closing `#endif`: ```c /* SEGGER SystemView instrumentation (SYSVIEW= build) — must be last */ #if (defined(CFG_TUD_SYSVIEW) && CFG_TUD_SYSVIEW) || (defined(CFG_TUH_SYSVIEW) && CFG_TUH_SYSVIEW) #undef INCLUDE_uxTaskPriorityGet #define INCLUDE_uxTaskPriorityGet 1 #undef INCLUDE_xTaskGetIdleTaskHandle #define INCLUDE_xTaskGetIdleTaskHandle 1 extern void tusb_sysview_heap_alloc(void* ptr, unsigned size); extern void tusb_sysview_heap_free(void* ptr); #define traceMALLOC(pvAddress, uiSize) tusb_sysview_heap_alloc(pvAddress, uiSize) #define traceFREE(pvAddress, uiSize) tusb_sysview_heap_free(pvAddress) #include "SEGGER_SYSVIEW_FreeRTOS.h" #endif ``` - [ ] **Step 2: Periodic stack report from the usbd task** In `src/device/usbd.c`, inside the `tud_task_ext` event loop (after an event is dequeued and processed): ```c #if CFG_TUD_SYSVIEW { static uint16_t sv_cnt = 0; if (0 == (++sv_cnt & 0x3FFu)) { tusb_sysview_stack_report(); } } #endif ``` (`#include "common/tusb_sysview.h"` with usbd.c's other includes.) - [ ] **Step 3: Reporter — stack, heap, blocked time** In `sysview_report.py`: (a) contexts table gains `blk_ms` column from the existing `Total Blocked Time` CSV field (`parse_time_s`, already imported); (b) after the ISR table, if events.txt present, parse stack/heap rows. Format discovery is part of this step: record once (Step 4), inspect the literal `event` names in events.txt (`Stack Info`, `Heap Alloc`/`Heap Free` are expected but MUST be read from the actual export), then lock the parser to what is observed. Output shape: ``` stack high-water bytes_used usbd 372 cdc 288 ... heap: allocs=N frees=M net_bytes=K (or "heap: no events (static allocation build)") ``` - [ ] **Step 4: Hardware validation (stack; heap absent)** Rebuild `build-sv`, flash, record 8 s under CDC load, report. Expected: stack table lists blinky/usbd/cdc/IDLE with plausible high-water bytes; heap line prints the static-allocation notice; `blk_ms` populated (cdc mostly blocked); overflow 0; task/ISR numbers unchanged from Task 3. - [ ] **Step 5: Heap-mapping validation (temporary dynamic build)** Temporarily set `configSUPPORT_DYNAMIC_ALLOCATION 1` in the samd5x_e5x FreeRTOSConfig.h, rebuild, flash, record boot (queues/semaphores allocate at init), report. Expected: `heap: allocs>0`. **Revert the temporary edit** (`git checkout hw/bsp/samd5x_e5x/FreeRTOSConfig/FreeRTOSConfig.h` would also revert Step 1 — instead flip the single line back by editing), rebuild, reflash pristine-instrumented. - [ ] **Step 6: Commit** ```bash git add hw/bsp/samd5x_e5x/FreeRTOSConfig/FreeRTOSConfig.h src/device/usbd.c .claude/skills/sysview/scripts/sysview_report.py git commit -m "sysview: FreeRTOS stack high-water + heap trace mapping + reporter columns" ``` --- ### Task 5: Per-function timing (hot functions) + markers/Printf/DisableEvents recipes **Files:** - Modify: `src/device/usbd.c` (`TUD_SYSVIEW_CALL/RET` around event processing + `usbd_edpt_xfer`), `src/class/cdc/cdc_device.c` (`tud_cdc_n_write_flush`, `tud_cdc_n_read`), `src/class/msc/msc_device.c` (`mscd_xfer_cb`), `src/host/usbh.c` (`tuh_task_ext` event processing, guarded `#if CFG_TUH_ENABLED`) - Modify: `.claude/skills/sysview/scripts/sysview_report.py` (function-timing table + marker table) **Interfaces:** - Consumes: Task 2 macros/ids. - Produces: reporter `function` table: name, n, p50/p99/max µs; `marker` table (same shape, from `Mark Start/Stop` pairs). - [ ] **Step 1: Instrument call sites (leveled)** Pattern (every site pairs CALL with RET on ALL exit paths — restructure to single-exit where needed; example for `usbd_edpt_xfer` in usbd.c, level USB, with the dcd boundary inside it at level PORT): ```c bool usbd_edpt_xfer(...) { TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_USBD_XFER); ... TUD_SYSVIEW_CALL(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_DCD_XFER); bool ok = dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes); TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_PORT, TU_SV_ID_DCD_XFER); ... TUD_SYSVIEW_RET(CFG_TUSB_SYSVIEW_LEVEL_USB, TU_SV_ID_USBD_XFER); return ret; } ``` Sites and their levels (dcd/hcd instrumentation lives at the usbd/usbh call sites — portable drivers are never edited): | site | macro/level | id | |---|---|---| | `tud_task_ext` per-event processing (around the event switch, NOT the blocking queue wait) | `TUD` / `LEVEL_USB` | `TU_SV_ID_TUD_TASK` | | `usbd_edpt_xfer` body | `TUD` / `LEVEL_USB` | `TU_SV_ID_USBD_XFER` | | the `dcd_edpt_xfer(...)` call inside it | `TUD` / `LEVEL_PORT` | `TU_SV_ID_DCD_XFER` | | `tud_cdc_n_write_flush` | `TUD` / `LEVEL_CLASS` | `TU_SV_ID_CDC_FLUSH` | | `tud_cdc_n_read` | `TUD` / `LEVEL_CLASS` | `TU_SV_ID_CDC_READ` | | `mscd_xfer_cb` | `TUD` / `LEVEL_CLASS` | `TU_SV_ID_MSC_XFER` | | `tuh_task_ext` per-event processing | `TUH` / `LEVEL_USB` | `TU_SV_ID_TUH_TASK` | | the `hcd_edpt_xfer(...)` call in usbh.c | `TUH` / `LEVEL_PORT` | `TU_SV_ID_HCD_XFER` | Each file adds `#include "common/tusb_sysview.h"`. - [ ] **Step 1b: Level-gating compile check** Build the example twice: `-DSYSVIEW=1` and `-DSYSVIEW=4`. Expected: both green; the level-1 ELF contains NO `_TU_SV_RECORD`-emitting sites (`arm-none-eabi-nm build-sv1/*.elf | grep -c RecordVoid` → 0 uses via module events; simplest observable: level-1 events.txt from a short record shows ISR events but no `tud_task` module events, level-4 shows both). - [ ] **Step 2: Reporter — function + marker tables** Discovery step (same protocol as Task 4 Step 3): record once, read how module events and `RecordEndCall` render in events.txt (named per the module description, e.g. `tud_task` + a return/end event), then implement pairing: per id, duration = end_ts − start_ts within the same context; emit n/p50/p99/max µs. Markers: pair `Mark Start`/`Mark Stop` per marker id the same way. If module events export as raw ids (decode risk noted in spec), fall back to mapping `eventint` (= module EventOffset + id, offset visible in the `Module Description` row) → name table copied from `tu_sysview_id_t`. - [ ] **Step 3: Hardware validation** Rebuild, flash, record 8 s under CDC load + msc mount, report. Expected: function table shows `tud_task`, `usbd_edpt_xfer`, `tud_cdc_write_flush`, `tud_cdc_read` with n>100 and p50 in single-digit to tens of µs; overflow 0 at 16 KB (if nonzero, that is Task 5's data for the DisableEvents recipe — record the working mask). - [ ] **Step 4: Overflow A/B + DisableEvents mask (deterministic, 4 KB)** Build with `-DSYSVIEW_BUFFER_SIZE=4096`; record under CDC load → expected: overflow > 0 (reliable at 4 KB, per v1). Then add a temporary `SEGGER_SYSVIEW_DisableEvents()` after init in `tusb_sysview_init`, determining `` empirically (start: disable API/module+PLOT classes, keep task+ISR — masks in `SEGGER_SYSVIEW.h` `SYSVIEW_EVTMASK_*`) until the same 4 KB run reports overflow 0. Record the final mask as a documented `SYSVIEW_EVENT_MASK`-style define in tusb_sysview.c (compile-time optional: `#ifdef SYSVIEW_DISABLE_EVENTS ... #endif`), remove the temporary line. - [ ] **Step 5: Markers + PrintfHost proof** Temporarily add to `examples/device/cdc_msc_freertos/src/main.c` cdc_task: `SEGGER_SYSVIEW_MarkStart(0)/MarkStop(0)` around the echo loop body and one `SEGGER_SYSVIEW_PrintfHost("cdc rx %u", count);` — rebuild, record with `--export-terminal`, report. Expected: marker table row with n>0; terminal.csv contains the printf lines. **Revert the example edit.** - [ ] **Step 6: Commit** ```bash git add src/device/usbd.c src/class/cdc/cdc_device.c src/class/msc/msc_device.c src/host/usbh.c src/common/tusb_sysview.c .claude/skills/sysview/scripts/sysview_report.py git commit -m "sysview: per-function timing instrumentation + marker/function report tables" ``` --- ### Task 6: Post-mortem capture (`SYSVIEW_POST_MORTEM=1` + `sysview_dump.py`) **Files:** - Modify: `hw/bsp/family_support.cmake` (extend Task 3 block) - Create: `.claude/skills/sysview/scripts/sysview_dump.py` **Interfaces:** - Consumes: Task 3 build block; v1 `sysview_record.py --from-raw` (unchanged). - Produces: `sysview_dump.py --device X --probe Y --elf Z --out DIR [--resume]` → `DIR/capture.SVDat` (raw, decodable via `--from-raw`). - [ ] **Step 1: CMake option** Inside the Task 3 `if (SYSVIEW)` block: ```cmake if (SYSVIEW_POST_MORTEM) target_compile_definitions(${TARGET} PUBLIC SEGGER_SYSVIEW_POST_MORTEM_MODE=1) # SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT stays at its source default (8) endif () ``` - [ ] **Step 2: Write `sysview_dump.py`** Flow (reuse `resolve_probe`/`rtt_cb_from_elf` patterns from `sysview_record.py` — copy the two helpers, do not import): 1. Resolve probe serial; RTT control block address from `--elf`. 2. One JLinkExe session (`-CommandFile`, NO reset commands — attach, `h`): `mem32 ` to read the channel-1 descriptor (pBuffer, SizeOfBuffer, WrOff, RdOff — offsets: aUp[] starts at cb+0x18, each SEGGER_RTT_BUFFER_UP is 6 words: sName,pBuffer,SizeOfBuffer,WrOff, RdOff,Flags → channel 1 descriptor at cb+0x18+24), verify magic "SEGGER RTT" at cb first (abort if absent), then `savebin /ring.bin, , `. 3. Linearize BOTH candidates (spec's open question): A = ring[WrOff:] + ring[:WrOff]; B = ring[WrOff:] + ring[:RdOff]. Write A as `capture.SVDat`, B as `capture_rdoff.SVDat` when RdOff differs. 4. Default leaves the core halted (mid-autopsy); `--resume` sends `g`. Print WrOff/RdOff/size and which files were written. - [ ] **Step 3: Hardware validation (split-point discovery)** Build `-DSYSVIEW=4 -DSYSVIEW_POST_MORTEM=1`, flash, run CDC load ~10 s, run `sysview_dump.py` mid-load (its halt = the simulated crash), decode candidate A via `sysview_record.py --from-raw … --no-events`, report. Expected: contexts table with cdc/usbd/ISRs and sane numbers (leading pre-sync garbage tolerated). If A fails/garbles wholesale, decode B. **Record the winner in the dump script (drop the loser) and in the SKILL (Task 8).** Reflash pristine (non-post-mortem) build after. - [ ] **Step 4: Commit** ```bash git add hw/bsp/family_support.cmake .claude/skills/sysview/scripts/sysview_dump.py git commit -m "sysview: post-mortem mode build option + halted-target dump script" ``` --- ### Task 7: OpenOCD capture route (non-SEGGER probes) **Files:** - No code — validation + recipe evidence for Task 8's SKILL text. **Interfaces:** - Consumes: existing OpenOCD rtt recipes (target-debug SKILL), `--from-raw`. - Produces: verified command sequence for the SKILL's OpenOCD section. - [ ] **Step 1: Build for stm32h743nucleo** `python3 tools/get_deps.py stm32h7`; build `cdc_msc_freertos` `-DBOARD=stm32h743nucleo -DSYSVIEW=4` (add the Task 4 guarded block to `hw/bsp/stm32h7/FreeRTOSConfig/FreeRTOSConfig.h` — same text, own commit). - [ ] **Step 2: Rig validation (ci.lan, lock held)** Hold `board_lock.py hold stm32h743nucleo --reason "sysview openocd validation"`. Flash via the board's openocd target. Capture raw channel 1: ```bash openocd -f interface/stlink-dap.cfg -c 'adapter serial ' -f target/stm32h7x.cfg \ -c init -c 'rtt setup 0x24000000 0x80000 "SEGGER RTT"' -c 'rtt start' \ -c 'rtt server start 19333 1' & timeout 10 nc localhost 19333 > /tmp/sysview-h7/capture.SVDat # drive CDC load meanwhile ``` (RAM base/size from the h743 linker script; rtt re-`start` after reflash.) Decode: `sysview_record.py --from-raw /tmp/sysview-h7/capture.SVDat --out /tmp/sysview-h7 --no-events` (decode runs on htpc — copy the file over) → report. Expected: contexts/ISR tables populate; note overflow count (OpenOCD polls — nonzero overflow under burst is the documented caveat, not a failure). Release the lock; reflash pristine. - [ ] **Step 3: Commit (the h7 config block from Step 1)** ```bash git add hw/bsp/stm32h7/FreeRTOSConfig/FreeRTOSConfig.h git commit -m "sysview: enable FreeRTOS trace hooks for stm32h7 family" ``` --- ### Task 8: `--json` report, SKILL.md rewrite, zero-cost gate, GREEN verification **Files:** - Modify: `.claude/skills/sysview/scripts/sysview_report.py` (`--json`) - Modify: `.claude/skills/sysview/SKILL.md` (build = `-DSYSVIEW=` with the level table, no source edits, function/stack/heap tables, post-mortem, OpenOCD route, DisableEvents mask, updated warnings) - Modify: `.claude/skills/target-debug/SKILL.md` + `.claude/agents/target-debugger.md` (one-line row updates: sysview needs no source edits now; post-mortem answers "what ran before the crash") **Interfaces:** - Consumes: everything above. - Produces: `sysview_report.py --json` → single JSON object `{contexts:[{name,activations,cpu_pct,total_ms,min_us,avg_us,max_us,blocked_ms}], isr:[…], ready_run:[…], functions:[…], markers:[…], stack:[…], heap:{allocs,frees,net_bytes}|null, overflow:N}` printed to stdout (tables suppressed) — the future HIL/PR-comment payload. - [ ] **Step 1: Implement `--json`** (serialize the already-computed tables; `json.dumps(..., indent=1)`; every numeric field a number, not a formatted string). - [ ] **Step 2: Zero-cost gate** — code-size skill single-example compare: `python3 tools/metrics_compare_base.py -e device/cdc_msc_freertos -b same54_xplained` (per code-size SKILL) comparing branch (SYSVIEW **off**) vs master. Expected: 0 flash / 0 RAM delta. Nonzero = a hook leaked outside `#if` — fix before proceeding. - [ ] **Step 3: Build sweep for touched families** — full example set for `same54_xplained` and `stm32h743nucleo`, both WITHOUT `SYSVIEW` (default path unchanged) and WITH `-DSYSVIEW=4`. Expected: all green. Plus `pre-commit run --all-files`. - [ ] **Step 4: Rewrite SKILL.md** — Iron-Law order: the doc is rewritten only now, AFTER every recipe above was hardware-proven; carry over v1's still-true gotchas (recorder internals, `/tmp/sv-*`, license dialog), replace the instrument section ("build with `-DSYSVIEW=` — no source edits; level table 1=ISR 2=+usbd/usbh 3=+dcd/hcd 4=+class"), add function/stack/heap reading guide, post-mortem + OpenOCD sections with the exact validated commands, `--json`. - [ ] **Step 5: GREEN verification (writing-skills)** — fresh subagent, dry-run scenario: "find the hot TinyUSB functions and check task stacks on same54_xplained during CDC jitter, headless; also: what would you do after a hardfault, and on an ST-Link-only board?" Expected: correct `-DSYSVIEW=` flow with zero source edits (picking level 3+ to see dcd timing), report/`--json` for numbers, post-mortem dump for the crash, OpenOCD raw route for ST-Link. REFACTOR SKILL.md on any gap and re-verify. - [ ] **Step 6: Final commit + bookkeeping** ```bash git add .claude/skills/sysview .claude/skills/target-debug/SKILL.md .claude/agents/target-debugger.md git commit -m "sysview v2: json report, first-class build docs, post-mortem + openocd sections" ``` Update the session memory (`sysview_skill.md`): v2 landed on branch, still unpushed. --- ## Self-review notes - Spec coverage: dependency (T1), in-tree first-class + zero-edit (T2/T3), hot functions (T5), stack+heap (T4), reportable/json (T4/T5/T8), post-mortem + split-point discovery (T6), OpenOCD answer validated (T7), quick wins markers/Printf/DisableEvents deterministic A/B (T5), out-of-scope list untouched. - Known discovery points are explicit steps with acceptance criteria (events.txt naming for module/stack/heap rows; post-mortem split point; DisableEvents mask) — not placeholders. - Type/name consistency: `tu_sysview_id_t`, `tusb_sysview_module`, `TUD/TUH_SYSVIEW_CALL/RET(level, id)`, `tusb_sysview_stack_report`, `tusb_sysview_heap_alloc/free` used identically across T2/T4/T5.