From fc350ae34b036a7746f69d3ea1f389af113b0969 Mon Sep 17 00:00:00 2001 From: fmarotta Date: Tue, 9 Sep 2025 16:44:44 +0200 Subject: [PATCH 1/3] Added orbtop-rtos --- Inc/generics.h | 3 +- Inc/git_version_info.h.in | 2 +- Inc/rtos/exceptions.h | 37 ++ Inc/rtos/rtos_support.h | 177 +++++ Inc/rtos/rtx5.h | 119 ++++ Src/itmDecoder.c | 2 + Src/msgDecoder.c | 7 +- Src/rtos/exceptions.c | 104 +++ Src/rtos/options.c | 210 ++++++ Src/rtos/options.h | 42 ++ Src/rtos/orbtop_rtos.c | 1046 ++++++++++++++++++++++++++++++ Src/rtos/output/output_console.c | 192 ++++++ Src/rtos/output/output_console.h | 26 + Src/rtos/output/output_ftrace.c | 133 ++++ Src/rtos/output/output_ftrace.h | 16 + Src/rtos/output/output_handler.c | 197 ++++++ Src/rtos/output/output_handler.h | 79 +++ Src/rtos/output/output_json.c | 335 ++++++++++ Src/rtos/output/output_json.h | 19 + Src/rtos/rtos_api.c | 887 +++++++++++++++++++++++++ Src/rtos/rtx5/rtx5.c | 409 ++++++++++++ Src/rtos/telnet_client.c | 352 ++++++++++ Src/rtos/telnet_client.h | 18 + openocd/stm32h74x.cfg | 535 +++++++++++++++ 24 files changed, 4944 insertions(+), 3 deletions(-) create mode 100644 Inc/rtos/exceptions.h create mode 100644 Inc/rtos/rtos_support.h create mode 100644 Inc/rtos/rtx5.h create mode 100644 Src/rtos/exceptions.c create mode 100644 Src/rtos/options.c create mode 100644 Src/rtos/options.h create mode 100644 Src/rtos/orbtop_rtos.c create mode 100644 Src/rtos/output/output_console.c create mode 100644 Src/rtos/output/output_console.h create mode 100644 Src/rtos/output/output_ftrace.c create mode 100644 Src/rtos/output/output_ftrace.h create mode 100644 Src/rtos/output/output_handler.c create mode 100644 Src/rtos/output/output_handler.h create mode 100644 Src/rtos/output/output_json.c create mode 100644 Src/rtos/output/output_json.h create mode 100644 Src/rtos/rtos_api.c create mode 100644 Src/rtos/rtx5/rtx5.c create mode 100644 Src/rtos/telnet_client.c create mode 100644 Src/rtos/telnet_client.h create mode 100644 openocd/stm32h74x.cfg diff --git a/Inc/generics.h b/Inc/generics.h index 1a7ff3e4..de15bc0f 100644 --- a/Inc/generics.h +++ b/Inc/generics.h @@ -66,7 +66,7 @@ typedef int errcode; #define C_CLR_LN CMD_ALERT "U" /* The actual control codes that do the work */ -#define CC_CLEAR_SCREEN "\033[2J\033[;H" +#define CC_CLEAR_SCREEN "\033[H\033[2J\033[3J" #define CC_PREV_LN "\033[1F" #define CC_CLR_LN "\033[K" #define CC_COLOUR "\033[%d;3%dm" @@ -99,6 +99,7 @@ char *genericsUnescape( char *str ); uint64_t genericsTimestampuS( void ); uint32_t genericsTimestampmS( void ); bool genericsSetReportLevel( enum verbLevel lset ); +enum verbLevel genericsGetReportLevel( void ); void genericsFPrintf( FILE *stream, const char *fmt, ... ); char *genericsGetBaseDirectory( void ); const char *genericsBasename( const char *n ); diff --git a/Inc/git_version_info.h.in b/Inc/git_version_info.h.in index 692497e1..5a026e10 100644 --- a/Inc/git_version_info.h.in +++ b/Inc/git_version_info.h.in @@ -1 +1 @@ -#define GIT_DESCRIBE "@VCS_TAG@" +#define GIT_DESCRIBE "@GIT_DESCRIBE@" diff --git a/Inc/rtos/exceptions.h b/Inc/rtos/exceptions.h new file mode 100644 index 00000000..b4b2d060 --- /dev/null +++ b/Inc/rtos/exceptions.h @@ -0,0 +1,37 @@ +#ifndef EXCEPTIONS_H +#define EXCEPTIONS_H + +#include +#include + +#define MAX_EXCEPTIONS (512) +#define NO_EXCEPTION (0xFFFFFFFF) + +struct exceptionRecord { + uint64_t visits; + int64_t totalTime; + int64_t minTime; + int64_t maxTime; + int64_t entryTime; + int64_t maxWallTime; + int64_t thisTime; + int64_t stealTime; + uint32_t prev; + uint32_t maxDepth; +}; + +struct exceptionStats { + struct exceptionRecord er[MAX_EXCEPTIONS]; + uint32_t exceptionActive; + int64_t timeStamp; + int64_t lastReportTicks; +}; + +const char* exceptionGetName(uint32_t exceptionNum); +void exceptionInit(struct exceptionStats *stats); +void exceptionEnter(struct exceptionStats *stats, uint32_t exceptionNum, int64_t timestamp); +void exceptionExit(struct exceptionStats *stats, int64_t timestamp); +void exceptionReset(struct exceptionStats *stats); +bool exceptionIsActive(struct exceptionStats *stats); + +#endif \ No newline at end of file diff --git a/Inc/rtos/rtos_support.h b/Inc/rtos/rtos_support.h new file mode 100644 index 00000000..17672983 --- /dev/null +++ b/Inc/rtos/rtos_support.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * RTOS Support for Orbuculum + * ========================== + * + * Generic RTOS thread tracking support + */ + +#ifndef _RTOS_SUPPORT_H_ +#define _RTOS_SUPPORT_H_ + +#include +#include +#include "uthash.h" + +/* RTOS Thread Entry */ +struct rtosThread { + /* Thread identification */ + uint32_t tcb_addr; /* Thread control block address (key) */ + char name[64]; /* Thread name (from memory or "UNNAMED") */ + uint32_t entry_func; /* Thread entry function address */ + const char *entry_func_name; /* Thread entry function name from symbols */ + int8_t priority; /* Thread priority (signed for RTX5) */ + uint32_t name_ptr; /* Puntero al nombre en memoria (debug) */ + + /* Execution time tracking */ + uint64_t accumulated_time_us; /* Accumulated execution time in current window (microseconds) */ + uint64_t accumulated_cycles; /* Accumulated CPU cycles in current window */ + uint64_t last_scheduled_us; /* Timestamp when thread became active (microseconds) */ + uint64_t context_switches; /* Total number of times scheduled (all time) */ + uint64_t window_switches; /* Number of switches in current window */ + uint32_t max_cpu_percent; /* Maximum CPU usage seen (in 0.001% units) */ + + /* Thread state tracking for reuse detection */ + uint32_t name_hash; /* Hash of name for change detection */ + uint32_t func_hash; /* Hash of function for change detection */ + + UT_hash_handle hh; /* Hash handle */ +}; + +/* RTOS types */ +enum rtosType { + RTOS_NONE = 0, + RTOS_RTX5, + RTOS_FREERTOS, + RTOS_THREADX, + RTOS_UNKNOWN +}; + +/* RTOS verification result codes */ +enum rtosVerifyResult { + RTOS_VERIFY_SUCCESS = 0, /* Target matches ELF */ + RTOS_VERIFY_NO_CONNECTION = 1, /* Cannot connect to target (telnet down) */ + RTOS_VERIFY_MISMATCH = -1, /* Target does not match ELF */ + RTOS_VERIFY_ERROR = -2 /* Other error */ +}; + +/* Forward declarations */ +struct rtosState; +struct rtosThread; +struct SymbolSet; +struct rtosDetection; + +/* RTOS Operations - Virtual table for RTOS-specific operations */ +struct rtosOps { + /* Read thread information from target memory */ + int (*read_thread_info)(struct rtosState *rtos, + struct SymbolSet *symbols, + struct rtosThread *thread, + uint32_t tcb_addr); + + /* Get priority name string */ + const char* (*get_priority_name)(int8_t priority); + + /* Detect RTOS from symbols */ + bool (*detect)(struct SymbolSet *symbols, struct rtosDetection *result); + + /* Initialize RTOS-specific data */ + int (*init)(struct rtosState *rtos, struct SymbolSet *symbols); + + /* Cleanup RTOS-specific data */ + void (*cleanup)(struct rtosState *rtos); + + /* Get thread state name */ + const char* (*get_state_name)(uint8_t state); + + /* Check if a thread is the idle thread (optional) */ + bool (*is_idle_thread)(struct rtosThread *thread); + + /* Verify RTOS version match between ELF and target (optional) */ + int (*verify_target_match)(struct rtosState *rtos, struct SymbolSet *symbols); +}; + +/* RTOS State */ +struct rtosState { + enum rtosType type; /* Type of RTOS detected */ + bool enabled; /* Is RTOS tracking active? */ + const char *name; /* RTOS name string */ + + /* Operations table */ + const struct rtosOps *ops; /* RTOS-specific operations */ + + /* Current state */ + uint32_t current_thread; /* Currently executing thread TCB */ + uint64_t last_switch_time; /* Timestamp of last context switch (system time) */ + uint32_t last_cyccnt; /* Last CYCCNT value (32-bit wrapping counter) */ + uint64_t total_cycles; /* Total accumulated cycles (handles wrapping) */ + uint32_t cpu_freq; /* CPU frequency in Hz for time calculations */ + + /* Thread tracking */ + struct rtosThread *threads; /* Hash table of all threads */ + uint32_t thread_count; /* Number of threads detected */ + uint32_t max_cpu_usage; /* Maximum CPU usage seen (in 0.01% units) */ + + /* RTOS-specific private data */ + void *priv; /* Private data for RTOS implementation */ + + /* Configuration */ + int telnet_port; /* Telnet port for GDB connection */ + + /* Output configuration for real-time events */ + void *output_config; /* Output handler for thread switches (OutputConfig*) */ +}; + +/* RTOS detection result */ +struct rtosDetection { + enum rtosType type; + const char *name; + uint32_t confidence; /* 0-100% confidence in detection */ + const char *reason; /* Why this RTOS was detected */ +}; + +/* Function declarations */ + +/* Main RTOS detection and initialization */ +struct rtosState *rtosDetectAndInit(struct SymbolSet *symbols, const char *requested_type, int options_telnetPort, uint32_t cpu_freq); + +/* Register RTOS implementations */ +void rtosRegisterRTX5(void); +void rtosRegisterFreeRTOS(void); +void rtosRegisterThreadX(void); +void rtosFree(struct rtosState *rtos); + +/* Thread name/function lookup from symbols */ +const char *rtosLookupPointerAsString(struct SymbolSet *symbols, uint32_t ptr_value); +const char *rtosLookupPointerAsFunction(struct SymbolSet *symbols, uint32_t ptr_value); +bool rtosResolveThreadInfo(struct rtosThread *thread, struct SymbolSet *symbols, + uint32_t name_ptr, uint32_t func_ptr); + + +/* DWT handling */ +void rtosHandleDWTMatch(struct rtosState *rtos, struct SymbolSet *symbols, + uint32_t comp_num, uint32_t address, uint32_t value, int options_telnetPort); + +void rtosHandleDWTMatchWithTimestamp(struct rtosState *rtos, struct SymbolSet *symbols, + uint32_t comp_num, uint32_t address, uint32_t value, + uint64_t itm_timestamp, int options_telnetPort); + +/* Output functions */ +void rtosDumpThreadInfo(struct rtosState *rtos, FILE *f, uint64_t window_time_us, bool itm_overflow, const char *sort_order); + +/* Thread metrics update functions */ +void rtosUpdateThreadCpuMetrics(struct rtosState *rtos, uint64_t window_time_us); +void rtosResetThreadCounters(struct rtosState *rtos); + +/* Memory reading functions (implemented in orbtop_rtos.c) */ +uint32_t rtosReadMemoryWord(uint32_t address); +char *rtosReadMemoryString(uint32_t address, char *buffer, size_t maxlen); + +/* DWT configuration via telnet */ +void rtosConfigureDWT(uint32_t watch_address); + +/* Cache management */ +void rtosClearMemoryCacheForTCB(uint32_t tcb_addr); + +#endif /* _RTOS_SUPPORT_H_ */ \ No newline at end of file diff --git a/Inc/rtos/rtx5.h b/Inc/rtos/rtx5.h new file mode 100644 index 00000000..afcb5eb7 --- /dev/null +++ b/Inc/rtos/rtx5.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * RTX5 Thread Tracking Support for Orbuculum + * ========================================== + * Host-side header for decoding RTX5 (CMSIS-RTOS2) data structures + * on a 32-bit ARMv7-M target (Cortex-M7). Keep ONLY target layout here. + */ + +#ifndef _RTX5_H_ +#define _RTX5_H_ + +#include +#include + +/* -------------------------------------------------------------------------- + * Target layout assumptions + * -------------------------------------------------------------------------- + * - ARMv7-M (Cortex-M7) 32-bit + * - Little-endian + * - Natural alignment (no packed attributes in the target) + * + * All offsets below are ABSOLUTE from the symbol base of the corresponding + * target object (e.g., &osRtxInfo). Use them as 32-bit addresses; do NOT + * reinterpret with host pointer sizes. + */ + +/* osRtxInfo (global RTOS runtime) — absolute offsets from &osRtxInfo */ +#define RTX5_INFO_OS_ID_OFFSET 0x00u /* const char* */ +#define RTX5_INFO_VERSION_OFFSET 0x04u /* uint32_t */ +#define RTX5_INFO_KERNEL_OFFSET 0x08u /* struct {...} */ +#define RTX5_INFO_TICK_IRQN_OFFSET 0x10u /* int32_t */ + +/* thread sub-structure (inside osRtxInfo) */ +#define RTX5_INFO_THREAD_OFFSET 0x14u /* struct {...} */ + +/* thread.run sub-structure */ +#define RTX5_INFO_THREAD_RUN_OFFSET 0x14u +#define RTX5_INFO_THREAD_RUN_CURR_OFFSET 0x14u /* osRtxInfo.thread.run.curr (osRtxThread_t*) */ +#define RTX5_INFO_THREAD_RUN_NEXT_OFFSET 0x18u /* osRtxInfo.thread.run.next (osRtxThread_t*) */ + +/* Convenience macros (address arithmetic on 32-bit target addresses) */ +#define RTX5_ADDR(base, off) ((uint32_t)((base) + (off))) +#define RTX5_ADDR_CURR(base) RTX5_ADDR((base), RTX5_INFO_THREAD_RUN_CURR_OFFSET) +#define RTX5_ADDR_NEXT(base) RTX5_ADDR((base), RTX5_INFO_THREAD_RUN_NEXT_OFFSET) + +/* RTX5 Thread Control Block (TCB) — offsets from a thread control block base */ +#define RTX5_THREAD_ID_OFFSET 0 /* uint8_t id (1=thread,2=timer,...) */ +#define RTX5_THREAD_STATE_OFFSET 1 /* uint8_t state */ +#define RTX5_THREAD_FLAGS_OFFSET 2 /* uint8_t flags */ +#define RTX5_THREAD_ATTR_OFFSET 3 /* uint8_t attributes */ +#define RTX5_THREAD_NAME_OFFSET 4 /* uint32_t pointer to name */ +#define RTX5_THREAD_THREAD_NEXT_OFFSET 8 /* uint32_t next in thread list */ +#define RTX5_THREAD_THREAD_PREV_OFFSET 12 /* uint32_t prev in thread list */ +#define RTX5_THREAD_DELAY_NEXT_OFFSET 16 /* uint32_t next in delay list */ +#define RTX5_THREAD_DELAY_PREV_OFFSET 20 /* uint32_t prev in delay list */ +#define RTX5_THREAD_THREAD_JOIN_OFFSET 24 /* uint32_t waiting-to-join thread */ +#define RTX5_THREAD_DELAY_OFFSET 28 /* uint32_t delay time */ +#define RTX5_THREAD_PRIORITY_OFFSET 32 /* int8_t priority */ +#define RTX5_THREAD_PRIORITY_BASE_OFFSET 33 /* int8_t base priority */ +#define RTX5_THREAD_STACK_FRAME_OFFSET 34 /* uint8_t stack frame type */ +#define RTX5_THREAD_FLAGS_OPTIONS_OFFSET 35 /* uint8_t flags options */ +#define RTX5_THREAD_WAIT_FLAGS_OFFSET 36 /* uint32_t waiting flags */ +#define RTX5_THREAD_THREAD_FLAGS_OFFSET 40 /* uint32_t thread flags */ +#define RTX5_THREAD_MUTEX_LIST_OFFSET 44 /* uint32_t owned mutex list ptr */ +#define RTX5_THREAD_STACK_MEM_OFFSET 48 /* uint32_t stack memory ptr */ +#define RTX5_THREAD_STACK_SIZE_OFFSET 52 /* uint32_t stack size */ +#define RTX5_THREAD_SP_OFFSET 56 /* uint32_t current SP */ +#define RTX5_THREAD_THREAD_ADDR_OFFSET 60 /* uint32_t entry function address */ +#define RTX5_THREAD_TZ_MEMORY_OFFSET 64 /* uint32_t TZ memory id */ +#define RTX5_THREAD_TZ_MODULE_OFFSET 68 /* uint32_t TZ module id */ +#define RTX5_THREAD_RESERVED_OFFSET 72 /* uint32_t reserved */ +#define RTX5_THREAD_ZONE_OFFSET 76 /* uint32_t zone number */ + +#define RTX5_THREAD_CB_SIZE 80 /* total size of TCB in bytes */ + +/* Thread states */ +#define RTX5_THREAD_INACTIVE 0 +#define RTX5_THREAD_READY 1 +#define RTX5_THREAD_RUNNING 2 +#define RTX5_THREAD_BLOCKED 3 +#define RTX5_THREAD_TERMINATED 4 + +/* Object identifiers */ +#define RTX5_ID_INVALID 0x00u +#define RTX5_ID_THREAD 0xF1u +#define RTX5_ID_TIMER 0xF2u +#define RTX5_ID_EVENTFLAGS 0xF3u +#define RTX5_ID_MUTEX 0xF5u +#define RTX5_ID_SEMAPHORE 0xF6u +#define RTX5_ID_MEMPOOL 0xF7u +#define RTX5_ID_MESSAGE 0xF9u +#define RTX5_ID_MESSAGEQUEUE 0xFAu + +struct rtx5_info { + uint32_t thread_run_curr; /* current thread pointer (target address) */ + uint32_t thread_run_next; /* next thread pointer (target address) */ +}; + +struct rtx5_thread_track { + uint32_t thread_addr; /* TCB base address on target */ + char name[64]; /* copied name */ + uint32_t thread_func; /* entry function */ + uint8_t priority; /* priority */ + uint8_t state; /* state */ + uint64_t total_cycles; /* stats */ + uint64_t run_count; + uint64_t last_scheduled; + uint64_t min_runtime; + uint64_t max_runtime; + uint32_t stack_usage; + struct rtx5_thread_track *next; + struct rtx5_thread_track *prev; +}; + + +const char *rtx5GetPriorityName(int8_t priority); +const struct rtosOps *rtx5GetOps(void); +#endif /* _RTX5_H_ */ \ No newline at end of file diff --git a/Src/itmDecoder.c b/Src/itmDecoder.c index 853c8730..3f8a0539 100644 --- a/Src/itmDecoder.c +++ b/Src/itmDecoder.c @@ -11,6 +11,7 @@ #include #include +#include #include "itmDecoder.h" #include "msgDecoder.h" @@ -425,6 +426,7 @@ enum ITMPumpEvent ITMPump( struct ITMDecoder *i, uint8_t c ) } break; + // ----------------------------------------------------- } } diff --git a/Src/msgDecoder.c b/Src/msgDecoder.c index 610b7092..78d6a2bc 100644 --- a/Src/msgDecoder.c +++ b/Src/msgDecoder.c @@ -136,7 +136,12 @@ static bool _handleHW( struct ITMPacket *packet, struct msg *decoded ) // -------------- default: - if ( ( ( packet->srcAddr & 0x19 ) == 0x10 ) || ( ( packet->srcAddr & 0x19 ) == 0x11 ) ) + /* Special case for srcAddr 0x13 from DWT comparator 1 */ + if ( packet->srcAddr == 0x13 ) + { + wasDecoded = _handleDataAccessWP( packet, ( struct wptMsg * )decoded ); + } + else if ( ( ( packet->srcAddr & 0x19 ) == 0x10 ) || ( ( packet->srcAddr & 0x19 ) == 0x11 ) ) { wasDecoded = _handleDataRWWP( packet, ( struct watchMsg * )decoded ); } diff --git a/Src/rtos/exceptions.c b/Src/rtos/exceptions.c new file mode 100644 index 00000000..14b119b1 --- /dev/null +++ b/Src/rtos/exceptions.c @@ -0,0 +1,104 @@ +#include +#include +#include + +static const char *ExceptionNames[] = { + [0] = "None", + [1] = "Reset", + [2] = "NMI", + [3] = "HardFault", + [4] = "MemManage", + [5] = "BusFault", + [6] = "UsageFault", + [7] = "Reserved", + [8] = "Reserved", + [9] = "Reserved", + [10] = "Reserved", + [11] = "SVCall", + [12] = "DebugMonitor", + [13] = "Reserved", + [14] = "PendSV", + [15] = "SysTick", +}; + +const char* exceptionGetName(uint32_t exceptionNum) +{ + static char irqName[30]; + + if (exceptionNum < 16) { + return ExceptionNames[exceptionNum]; + } + + snprintf(irqName, sizeof(irqName), "IRQ %d", exceptionNum - 16); + return irqName; +} + +void exceptionInit(struct exceptionStats *stats) +{ + memset(stats, 0, sizeof(struct exceptionStats)); + stats->exceptionActive = NO_EXCEPTION; +} + +void exceptionEnter(struct exceptionStats *stats, uint32_t exceptionNum, int64_t timestamp) +{ + if (exceptionNum >= MAX_EXCEPTIONS) { + return; + } + + stats->er[exceptionNum].prev = stats->exceptionActive; + stats->er[exceptionNum].entryTime = timestamp; + stats->er[exceptionNum].thisTime = 0; + stats->er[exceptionNum].stealTime = 0; + stats->er[exceptionNum].maxDepth = + (stats->exceptionActive != NO_EXCEPTION && + stats->er[exceptionNum].maxDepth < stats->er[stats->exceptionActive].maxDepth + 1) ? + stats->er[stats->exceptionActive].maxDepth + 1 : stats->er[exceptionNum].maxDepth; + + stats->exceptionActive = exceptionNum; +} + +void exceptionExit(struct exceptionStats *stats, int64_t timestamp) +{ + if (stats->exceptionActive == NO_EXCEPTION) { + return; + } + + uint32_t e = stats->exceptionActive; + int64_t exTime = timestamp - stats->er[e].entryTime - stats->er[e].stealTime; + + stats->er[e].visits++; + stats->er[e].totalTime += exTime; + stats->er[e].thisTime = exTime; + + if (stats->er[e].minTime > exTime || stats->er[e].minTime == 0) { + stats->er[e].minTime = exTime; + } + + if (stats->er[e].maxTime < exTime) { + stats->er[e].maxTime = exTime; + } + + int64_t wallTime = timestamp - stats->er[e].entryTime; + if (stats->er[e].maxWallTime < wallTime) { + stats->er[e].maxWallTime = wallTime; + } + + stats->exceptionActive = stats->er[e].prev; + + if (stats->exceptionActive != NO_EXCEPTION) { + stats->er[stats->exceptionActive].stealTime += wallTime; + } +} + +void exceptionReset(struct exceptionStats *stats) +{ + for (uint32_t e = 0; e < MAX_EXCEPTIONS; e++) { + memset(&stats->er[e], 0, sizeof(struct exceptionRecord)); + } + stats->exceptionActive = NO_EXCEPTION; +} + +bool exceptionIsActive(struct exceptionStats *stats) +{ + return stats->exceptionActive != NO_EXCEPTION; +} \ No newline at end of file diff --git a/Src/rtos/options.c b/Src/rtos/options.c new file mode 100644 index 00000000..4a40be24 --- /dev/null +++ b/Src/rtos/options.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TOP_UPDATE_INTERVAL 1000 + +static ProgramOptions defaultOptions = { + .forceITMSync = true, + .tag = 1, + .demangle = true, + .displayInterval = TOP_UPDATE_INTERVAL * 1000, + .port = OFCLIENT_SERVER_PORT, + .server = "localhost", + .protocol = PROT_OFLOW, + .rtos = NULL, + .rtosSort = "cpu", + .telnetPort = 4444, + .udpPort = 0, + .mono = false, + .cpuFreq = 0, + .outputExceptions = false +}; + +ProgramOptions* options_get_defaults(void) { + return &defaultOptions; +} + +void options_print_help(const char *progName) { + fprintf(stdout, "Usage: %s [options]\n", progName); + fprintf(stdout, "\nRequired:\n"); + fprintf(stdout, " -e, --elf-file: ELF file for symbols\n"); + fprintf(stdout, "\nOptional:\n"); + fprintf(stdout, " -D, --no-demangle: Switch off C++ symbol demangling\n"); + fprintf(stdout, " -E, --exceptions: Include exceptions in output\n"); + fprintf(stdout, " -F, --cpu-freq: CPU frequency for time calculations (omit to show NA)\n"); + fprintf(stdout, " -f, --input-file: Take input from file\n"); + fprintf(stdout, " -h, --help: This help\n"); + fprintf(stdout, " -I, --interval: Display interval (default %dms)\n", TOP_UPDATE_INTERVAL); + fprintf(stdout, " -j, --json-output: or 'udp:port' for JSON output (REQUIRED argument)\n"); + fprintf(stdout, " -K, --ftrace: ftrace trace output (use - for stdout or /tmp/trace.pipe for live)\n"); + fprintf(stdout, " -M, --no-colour: Suppress colour in output\n"); + fprintf(stdout, " -n, --itm-sync: Enforce ITM sync requirement\n"); + fprintf(stdout, " -O, --objdump-opts: Options to pass directly to objdump\n"); + fprintf(stdout, " -p, --protocol: Protocol (OFLOW|ITM)\n"); + fprintf(stdout, " -P, --pace: Delay in data transmission\n"); + fprintf(stdout, " -s, --server: : (default localhost:%d)\n", OFCLIENT_SERVER_PORT); + fprintf(stdout, " -T, --rtos: RTOS type (rtx5)\n"); + fprintf(stdout, " -S, --rtos-sort: Sort: cpu|maxcpu|tcb|name|func|priority|switches\n"); + fprintf(stdout, " -W, --telnet-port: Telnet port for OpenOCD (default 4444)\n"); + fprintf(stdout, " -t, --tag: OFLOW tag (default 1)\n"); + fprintf(stdout, " -v, --verbose: Verbose 0(errors)..3(debug)\n"); + fprintf(stdout, " -V, --version: Print version\n"); + fprintf(stdout, "\nEnvironment Variables:\n"); + fprintf(stdout, " OBJDUMP: Use non-standard objdump binary\n"); + fprintf(stdout, "\nRuntime Keys (RTOS mode):\n"); + fprintf(stdout, " t: Sort by TCB address\n"); + fprintf(stdout, " c: Sort by current CPU usage\n"); + fprintf(stdout, " m: Sort by maximum CPU usage\n"); + fprintf(stdout, " n: Sort by thread name\n"); + fprintf(stdout, " f: Sort by function name\n"); + fprintf(stdout, " p: Sort by priority\n"); + fprintf(stdout, " s: Sort by context switches\n"); + fprintf(stdout, " r: Reset maximum CPU values\n"); +} + +static struct option longOptions[] = { + {"no-demangle", no_argument, NULL, 'D'}, + {"elf-file", required_argument, NULL, 'e'}, + {"exceptions", no_argument, NULL, 'E'}, + {"cpu-freq", required_argument, NULL, 'F'}, + {"input-file", required_argument, NULL, 'f'}, + {"interval", required_argument, NULL, 'I'}, + {"json-output", required_argument, NULL, 'j'}, + {"ftrace", required_argument, NULL, 'K'}, + {"no-colour", no_argument, NULL, 'M'}, + {"no-color", no_argument, NULL, 'M'}, + {"itm-sync", no_argument, NULL, 'n'}, + {"objdump-opts", required_argument, NULL, 'O'}, + {"protocol", required_argument, NULL, 'p'}, + {"pace", required_argument, NULL, 'P'}, + {"server", required_argument, NULL, 's'}, + {"rtos", required_argument, NULL, 'T'}, + {"rtos-sort", required_argument, NULL, 'S'}, + {"telnet-port", required_argument, NULL, 'W'}, + {"tag", required_argument, NULL, 't'}, + {"verbose", required_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} +}; + +int options_parse(int argc, char *argv[], ProgramOptions *opts) { + int c; + + memcpy(opts, &defaultOptions, sizeof(ProgramOptions)); + + while ((c = getopt_long(argc, argv, "De:EF:f:I:j:K:MnO:p:P:s:S:T:W:t:v:hV", + longOptions, NULL)) != -1) { + switch (c) { + case 'D': + opts->demangle = false; + break; + case 'e': + opts->elffile = optarg; + break; + case 'E': + opts->outputExceptions = true; + break; + case 'F': + opts->cpuFreq = atoi(optarg); + break; + case 'f': + opts->file = optarg; + break; + case 'I': + opts->displayInterval = (int64_t)(atof(optarg) * 1000); + break; + case 'j': + if (!optarg || strlen(optarg) == 0) + { + fprintf(stderr, "Error: -j/--json-output requires an argument (file path or 'udp:port')\n"); + return -1; + } + opts->json = optarg; + if (strncmp(optarg, "udp:", 4) == 0) + { + opts->udpPort = atoi(optarg + 4); + if (opts->udpPort <= 0 || opts->udpPort > 65535) + { + fprintf(stderr, "Error: Invalid UDP port number: %s\n", optarg + 4); + return -1; + } + } + break; + case 'K': + opts->ftrace = optarg; + break; + case 'M': + opts->mono = true; + break; + case 'n': + opts->forceITMSync = false; + break; + case 'O': + opts->odoptions = optarg; + break; + case 'p': + if (strcmp(optarg, "OFLOW") == 0) { + opts->protocol = PROT_OFLOW; + } else if (strcmp(optarg, "ITM") == 0) { + opts->protocol = PROT_ITM; + } else { + fprintf(stderr, "Unknown protocol: %s\n", optarg); + return -1; + } + break; + case 'P': + opts->paceDelay = atoi(optarg); + if (opts->paceDelay <= 0) { + fprintf(stderr, "Pace delay must be positive\n"); + return -1; + } + break; + case 's': + opts->server = optarg; + char *colon = strchr(optarg, ':'); + if (colon) { + *colon = 0; + opts->port = atoi(++colon); + } + break; + case 'S': + opts->rtosSort = optarg; + break; + case 'T': + opts->rtos = optarg; + break; + case 'W': + opts->telnetPort = atoi(optarg); + break; + case 't': + opts->tag = atoi(optarg); + break; + case 'v': + genericsSetReportLevel(atoi(optarg)); + break; + case 'V': + genericsFPrintf(stdout, "pe-orbtop-rtos version " GIT_DESCRIBE EOL); + exit(0); + case 'h': + options_print_help(argv[0]); + return -1; + default: + fprintf(stderr, "Unknown option\n"); + return -1; + } + } + + if (!opts->elffile) { + fprintf(stderr, "Error: ELF file required (-e)\n"); + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/Src/rtos/options.h b/Src/rtos/options.h new file mode 100644 index 00000000..8991b13a --- /dev/null +++ b/Src/rtos/options.h @@ -0,0 +1,42 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include +#include + +enum Protocol { + PROT_OFLOW, + PROT_ITM, + PROT_UNKNOWN +}; + +typedef struct { + uint32_t tag; + bool outputExceptions; + bool forceITMSync; + char *file; + uint32_t hwOutputs; + char *elffile; + char *odoptions; + char *json; + char *ftrace; + bool mono; + int paceDelay; + bool demangle; + int64_t displayInterval; + int port; + char *server; + enum Protocol protocol; + char *rtos; + char *rtosSort; + int telnetPort; + int udpPort; + uint32_t cpuFreq; + bool cpuFreqSpecified; +} ProgramOptions; + +int options_parse(int argc, char *argv[], ProgramOptions *opts); +void options_print_help(const char *progName); +ProgramOptions* options_get_defaults(void); + +#endif \ No newline at end of file diff --git a/Src/rtos/orbtop_rtos.c b/Src/rtos/orbtop_rtos.c new file mode 100644 index 00000000..067ce5f4 --- /dev/null +++ b/Src/rtos/orbtop_rtos.c @@ -0,0 +1,1046 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * ITM Top for Orbuculum + * ===================== + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CUTOFF (10) /* Default cutoff at 0.1% */ +#define TOP_UPDATE_INTERVAL (1000) /* Interval between each on screen update */ + +#define MAX_EXCEPTIONS (512) /* Maximum number of exceptions to be considered */ +#define NO_EXCEPTION (0xFFFFFFFF) /* Flag indicating no exception is being processed */ + +#define MSG_REORDER_BUFLEN (10) /* Maximum number of samples to re-order for timekeeping */ + +#define DWT_NUM_EVENTS 6 +const char *evName[DWT_NUM_EVENTS] = {"CPI", "Exc", "Slp", "LSU", "Fld", "Cyc"}; + +/* Protocol enum is defined in options.h */ +const char *protString[] = {"OFLOW", "ITM", NULL}; + + + +static ProgramOptions options; + +/* ----------- LIVE STATE ----------------- */ +struct +{ + struct ITMDecoder i; /* The decoders and the packets from them */ + struct MSGSeq d; /* Message (re-)sequencer */ + struct ITMPacket h; + struct OFLOW c; + enum timeDelay timeStatus; /* Indicator of if this time is exact */ + uint64_t timeStamp; /* Latest received time */ + struct Frame cobsPart; /* Any part frame that has been received */ + + struct SymbolSet *s; /* Symbols read from elf */ + + struct exceptionRecord er[MAX_EXCEPTIONS]; /* Exceptions we received on this interval */ + uint32_t currentException; /* Exception we are currently embedded in */ + uint32_t erDepth; /* Current depth of exception stack */ + + int64_t lastReportus; /* Last time an output report was generated, in microseconds */ + int64_t lastReportTicks; /* Last time an output report was generated, in ticks */ + uint32_t ITMoverflows; /* Has an ITM overflow been detected? */ + uint32_t SWPkt; /* Number of SW Packets received */ + uint32_t TSPkt; /* Number of TS Packets received */ + uint32_t HWPkt; /* Number of HW Packets received */ + uint32_t dwt_event_acc[DWT_NUM_EVENTS]; /* Accumulator for DWT events */ + + uint32_t interrupts; + bool ending; /* Flag to exit */ + + /* RTOS tracking state */ + struct rtosState *rtos; /* RTOS tracking state */ +} _r; + +static struct termios old_tio; +static bool terminal_modified = false; +static OutputConfig *_outputConfig = NULL; + +static void _closeTelnet( void ); + +static void _initOutput( void ) +{ + if (_outputConfig) + { + output_cleanup(_outputConfig); + free(_outputConfig->udp_dest); + free(_outputConfig); + } + + _outputConfig = calloc(1, sizeof(OutputConfig)); + if (!_outputConfig) return; + + _outputConfig->mono = options.mono; + _outputConfig->udp_socket = -1; + + if (options.json) + { + if (strncmp(options.json, "udp:", 4) == 0) + { + _outputConfig->mode = OUTPUT_JSON_UDP; + _outputConfig->udp_dest = calloc(1, sizeof(struct sockaddr_in)); + if (_outputConfig->udp_dest) + { + _outputConfig->udp_dest->sin_port = htons(options.udpPort); + } + output_init(_outputConfig); + } + else + { + _outputConfig->mode = OUTPUT_JSON_FILE; + if (strcmp(options.json, "-") == 0) + { + _outputConfig->file = stdout; + } + else + { + _outputConfig->file = fopen(options.json, "w"); + if (!_outputConfig->file) + { + genericsReport(V_ERROR, "Cannot open JSON output file %s" EOL, options.json); + _outputConfig->mode = OUTPUT_CONSOLE; + } + } + } + } + else + { + _outputConfig->mode = OUTPUT_CONSOLE; + } +} + +static void _reinitializeRTOS( void ) +{ + if ( !options.rtos ) return; + + /* Save output config before cleaning up */ + OutputConfig *saved_output = NULL; + if ( _r.rtos && _r.rtos->output_config ) + { + saved_output = _r.rtos->output_config; + } + + /* Close telnet connection to force fresh reconnect */ + _closeTelnet(); + + /* Clean up existing RTOS state */ + if ( _r.rtos ) + { + _r.rtos->output_config = NULL; /* Don't free the output config yet */ + rtosFree( _r.rtos ); + _r.rtos = NULL; + } + + /* Wait for OpenOCD telnet to be ready - retry up to 10 times */ + for ( int retry = 0; retry < 10; retry++ ) + { + if ( retry > 0 ) + { + genericsReport( V_INFO, "Waiting for OpenOCD telnet to be ready... (attempt %d/10)" EOL, retry + 1 ); + usleep( 500000 ); /* Wait 500ms between retries */ + } + + _r.rtos = rtosDetectAndInit(_r.s, options.rtos, options.telnetPort, options.cpuFreq); + if ( _r.rtos ) + { + /* Restore the output config */ + if ( saved_output ) + { + _r.rtos->output_config = saved_output; + genericsReport( V_DEBUG, "Restored output_config to RTOS after reinit" EOL ); + } + genericsReport( V_INFO, "RTOS reconnected and verified for %s" EOL, _r.rtos->name ); + return; + } + } + + genericsReport( V_ERROR, "RTOS reinitialization failed after 10 attempts" EOL ); +} +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +// Internally available routines +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +int64_t _timestamp( void ) + +{ + struct timeval te; + gettimeofday( &te, NULL ); // get current time + int64_t microseconds = te.tv_sec * 1000000LL + ( te.tv_usec ); // accumulate microseconds + return microseconds; +} +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +// Handler for individual message types from SWO +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +void _exitEx( int64_t ts ) + +{ + if ( _r.currentException == NO_EXCEPTION ) + { + /* This can happen under startup and overflow conditions */ + return; + } + + /* Calculate total time for this exception as we're leaving it */ + int64_t thisTime = ts - _r.er[_r.currentException].entryTime; + int64_t thisStealTime = _r.er[_r.currentException].stealTime; + + _r.er[_r.currentException].thisTime += thisTime; + _r.er[_r.currentException].visits++; + _r.er[_r.currentException].totalTime += _r.er[_r.currentException].thisTime; + + /* Zero the entryTime as it's used to show when an exception is 'live' */ + _r.er[_r.currentException].entryTime = 0; + + /* ...and account for this time */ + if ( ( !_r.er[_r.currentException].minTime ) || ( _r.er[_r.currentException].thisTime < _r.er[_r.currentException].minTime ) ) + { + _r.er[_r.currentException].minTime = _r.er[_r.currentException].thisTime; + } + + if ( _r.er[_r.currentException].thisTime > _r.er[_r.currentException].maxTime ) + { + _r.er[_r.currentException].maxTime = _r.er[_r.currentException].thisTime; + } + + const int64_t walltime = _r.er[_r.currentException].thisTime + _r.er[_r.currentException].stealTime; + + if ( walltime > _r.er[_r.currentException].maxWallTime ) + { + _r.er[_r.currentException].maxWallTime = walltime; + } + + if ( _r.erDepth > _r.er[_r.currentException].maxDepth ) + { + _r.er[_r.currentException].maxDepth = _r.erDepth; + } + + /* Step out of this exception */ + _r.currentException = _r.er[_r.currentException].prev; + + if ( _r.erDepth ) + { + _r.erDepth--; + } + + /* If we are still in an exception then carry on accounting */ + if ( _r.currentException != NO_EXCEPTION ) + { + _r.er[_r.currentException].entryTime = ts; + _r.er[_r.currentException].stealTime += thisTime + thisStealTime; + } +} +// ==================================================================================================== +void _handleTS( struct TSMsg *m, struct ITMDecoder *i ) + +{ + assert( m->msgtype == MSG_TS ); + + _r.timeStatus = m->timeStatus; + _r.timeStamp += m->timeInc; +} +// ==================================================================================================== +void _handleException( struct excMsg *m, struct ITMDecoder *i ) + +{ + assert( m->msgtype == MSG_EXCEPTION ); + assert( m->exceptionNumber < MAX_EXCEPTIONS ); + + genericsReport( V_DEBUG, "Exception event: num=%d, type=%d" EOL, m->exceptionNumber, m->eventType ); + + switch ( m->eventType ) + { + case EXEVENT_ENTER: + if ( _r.er[m->exceptionNumber].entryTime != 0 ) + { + /* We beleive we are already in this exception. This can happen when we've lost + * messages due to ITM overflow. Don't process the enter. Everything will get + * fixed up in the next EXEXIT_RESUME which will reset everything. + */ + break; + } + + if ( _r.currentException != NO_EXCEPTION ) + { + /* Already in an exception ... account for time until now */ + _r.er[_r.currentException].thisTime += _r.timeStamp - _r.er[_r.currentException].entryTime; + } + + /* Record however we got to this exception */ + _r.er[m->exceptionNumber].prev = _r.currentException; + + /* Now dip into this exception */ + _r.currentException = m->exceptionNumber; + _r.er[m->exceptionNumber].entryTime = _r.timeStamp; + _r.er[m->exceptionNumber].thisTime = 0; + _r.er[m->exceptionNumber].stealTime = 0; + _r.erDepth++; + break; + + case EXEVENT_RESUME: /* Unwind all levels of exception (deals with tail chaining) */ + while ( ( _r.currentException != m->exceptionNumber ) && ( _r.erDepth ) ) + { + _exitEx( _r.timeStamp ); + } + + break; + + case EXEVENT_EXIT: /* Exit single level of exception */ + _exitEx( _r.timeStamp ); + break; + + default: + case EXEVENT_UNKNOWN: + genericsReport( V_INFO, "Unrecognised exception event (%d,%d)" EOL, m->eventType, m->exceptionNumber ); + break; + }; +} +// ==================================================================================================== +// ==================================================================================================== +void _handleDWTEvent( struct dwtMsg *m, struct ITMPacket *p ) + +{ + /* DWT events are different from watchpoints - just count them */ + for ( uint32_t i = 0; i < DWT_NUM_EVENTS; i++ ) + { + if ( m->event & ( 1 << i ) ) + { + _r.dwt_event_acc[i]++; + } + } +} +// ==================================================================================================== +void _handleDataAccessWP( struct wptMsg *m, struct ITMDecoder *i ) +{ + genericsReport( V_DEBUG, "DWT WP: comp=%d data=0x%08X" EOL, m->comp, m->data ); + + /* Handle RTX5 thread switch watchpoint */ + if ( _r.rtos && _r.rtos->enabled ) + { + /* Call RTOS handler with watchpoint data - accept both comp 0 and 1 */ + if ( m->comp == 0 || m->comp == 1 ) + { + genericsReport( V_DEBUG, "DWT WP: comp=%d data=0x%08X, _r.timeStamp=%llu" EOL, + m->comp, m->data, _r.timeStamp ); + /* Use _r.timeStamp which is the accumulated ITM timestamp */ + rtosHandleDWTMatchWithTimestamp(_r.rtos, _r.s, m->comp, 0, m->data, _r.timeStamp, options.telnetPort); + } + } +} +static void _closeTelnet( void ) +{ + telnet_disconnect(); +} + +int options_telnetPort = 0; +int options_udpPort = 0; +void rtosConfigureDWT(uint32_t watch_address) +{ + telnet_configure_dwt(watch_address); +} + +void rtosClearMemoryCacheForTCB(uint32_t tcb_addr) +{ + telnet_clear_cache_for_tcb(tcb_addr); +} + +/* Public wrappers for memory reading */ +uint32_t rtosReadMemoryWord( uint32_t address ) +{ + return telnet_read_memory_word( address ); +} + +char *rtosReadMemoryString( uint32_t address, char *buffer, size_t maxlen ) +{ + return telnet_read_memory_string( address, buffer, maxlen ); +} + + + +// ==================================================================================================== +static void _processOutput( int64_t lastTime ) +{ + if (!_outputConfig) return; + + IntervalOutput interval = { + .timestamp = lastTime, + .interval_us = lastTime - _r.lastReportus, + .interval_ticks = _r.timeStamp - _r.lastReportTicks, + .ticks_per_ms = (_r.lastReportTicks && lastTime != _r.lastReportus) ? + ((_r.timeStamp - _r.lastReportTicks) * 1000) / (lastTime - _r.lastReportus) : 0 + }; + + if (_outputConfig->mode == OUTPUT_CONSOLE) + { + output_start_frame(_outputConfig, &interval); + + if (_r.rtos && _r.rtos->enabled && _r.rtos->threads) + { + uint64_t window_time_us = (lastTime - _r.lastReportus); + bool itm_overflow = (_r.ITMoverflows != ITMDecoderGetStats(&_r.i)->overflow); + + /* Update CPU metrics first (calculates percentages and updates max values) */ + rtosUpdateThreadCpuMetrics(_r.rtos, window_time_us); + + /* Then output the data */ + output_console_rtos_threads(_outputConfig, _r.rtos, window_time_us, itm_overflow, options.rtosSort); + } + + if (options.outputExceptions) + { + output_console_exception_header(_outputConfig); + + bool hasExceptions = false; + for (uint32_t e = 0; e < MAX_EXCEPTIONS; e++) + { + if (_r.er[e].visits) + { + hasExceptions = true; + break; + } + } + + if (!hasExceptions) + { + output_console_no_exceptions(_outputConfig); + } + else + { + for (uint32_t e = 0; e < MAX_EXCEPTIONS; e++) + { + if (_r.er[e].visits) + { + char nameBuf[30]; + snprintf(nameBuf, sizeof(nameBuf), "%2d (%s)", e, exceptionGetName(e)); + + ExceptionOutput exc = { + .exception_num = e, + .exception_name = nameBuf, + .visits = _r.er[e].visits, + .max_depth = _r.er[e].maxDepth, + .total_time = _r.er[e].totalTime, + .min_time = _r.er[e].minTime, + .max_time = _r.er[e].maxTime, + .max_wall_time = _r.er[e].maxWallTime, + .util_percent = (_r.timeStamp - _r.lastReportTicks) ? + ((float)_r.er[e].totalTime / (_r.timeStamp - _r.lastReportTicks) * 100.0f) : 0, + .ave_time = _r.er[e].visits ? (_r.er[e].totalTime / _r.er[e].visits) : 0 + }; + + output_exception_entry(_outputConfig, &exc); + } + } + } + output_console_exception_footer(_outputConfig); + } + + output_console_status_indicators(_outputConfig, + _r.ITMoverflows != ITMDecoderGetStats(&_r.i)->overflow, + _r.SWPkt != ITMDecoderGetStats(&_r.i)->SWPkt, + _r.TSPkt != ITMDecoderGetStats(&_r.i)->TSPkt, + _r.HWPkt != ITMDecoderGetStats(&_r.i)->HWPkt); + + uint64_t interval_ms = (lastTime - _r.lastReportus) / 1000; + output_console_interval_info(_outputConfig, interval_ms, _r.timeStamp - _r.lastReportTicks, + interval.ticks_per_ms, _r.lastReportTicks && lastTime != _r.lastReportus); + + output_console_sort_options(_outputConfig, _r.rtos && _r.rtos->enabled); + + StatsOutput stats = { + .overflow = ITMDecoderGetStats(&_r.i)->overflow, + .sync_count = ITMDecoderGetStats(&_r.i)->syncCount, + .error_count = ITMDecoderGetStats(&_r.i)->ErrorPkt, + .sw_packets = _r.SWPkt, + .ts_packets = _r.TSPkt, + .hw_packets = _r.HWPkt + }; + output_stats(_outputConfig, &stats); + } + else if (_outputConfig->mode == OUTPUT_JSON_FILE || _outputConfig->mode == OUTPUT_JSON_UDP) + { + if (_r.rtos && _r.rtos->enabled && _r.rtos->threads) + { + uint64_t window_time_us = (lastTime - _r.lastReportus); + bool itm_overflow = (_r.ITMoverflows != ITMDecoderGetStats(&_r.i)->overflow); + + /* Update CPU metrics first (calculates percentages and updates max values) */ + rtosUpdateThreadCpuMetrics(_r.rtos, window_time_us); + + /* Then output the data */ + output_json_rtos_threads(_outputConfig, _r.rtos, window_time_us, itm_overflow); + } + + if (options.outputExceptions) + { + output_json_exceptions(_outputConfig, _r.er, MAX_EXCEPTIONS, _r.timeStamp, _r.lastReportTicks); + } + } + + /* Reset thread counters for next interval */ + rtosResetThreadCounters(_r.rtos); +} + +// ==================================================================================================== +// Pump characters into the itm decoder +// ==================================================================================================== +void _itmPumpProcess( uint8_t c ) +{ + typedef void ( *handlers )( void *decoded, struct ITMDecoder * i ); + + /* Handlers for each complete message received */ + static const handlers h[MSG_NUM_MSGS] = + { + /* MSG_UNKNOWN */ NULL, + /* MSG_RESERVED */ NULL, + /* MSG_ERROR */ NULL, + /* MSG_NONE */ NULL, + /* MSG_SOFTWARE */ NULL, + /* MSG_NISYNC */ NULL, + /* MSG_OSW */ NULL, + /* MSG_DATA_ACCESS_WP */ ( handlers )_handleDataAccessWP, + /* MSG_DATA_RWWP */ NULL, + /* MSG_PC_SAMPLE */ NULL, /* PC samples no longer used */ + /* MSG_DWT_EVENT */ ( handlers )_handleDWTEvent, + /* MSG_EXCEPTION */ ( handlers )_handleException, + /* MSG_TS */ ( handlers )_handleTS + }; + + struct msg *p; + + if ( !MSGSeqPump( &_r.d, c ) ) + { + return; + } + + /* We are synced timewise, so empty anything that has been waiting */ + while ( true ) + { + p = MSGSeqGetPacket( &_r.d ); + + if ( !p ) + { + /* all read */ + break; + } + + assert( p->genericMsg.msgtype < MSG_NUM_MSGS ); + + if ( h[p->genericMsg.msgtype] ) + { + ( h[p->genericMsg.msgtype] )( p, &_r.i ); + } + } + + return; +} +// ==================================================================================================== +// ==================================================================================================== +// Protocol pump for decoding messages +// ==================================================================================================== +// ==================================================================================================== + +static void _OFLOWpacketRxed ( struct OFLOWFrame *p, void *param ) +{ + if ( !p->good ) + { + genericsReport( V_INFO, "Bad packet received" EOL ); + } + else + { + if ( p->tag == options.tag ) + { + for ( int i = 0; i < p->len; i++ ) + { + _itmPumpProcess( p->d[i] ); + } + } + } +} + +// ==================================================================================================== + +static struct Stream *_openStream( void ) +{ + if ( options.file != NULL ) + { + return streamCreateFile( options.file ); + } + else + { + return streamCreateSocket( options.server, options.port ); + } +} + +// ==================================================================================================== +static void _intHandler( int sig ) + +{ + /* CTRL-C exit is not an error... */ + _r.ending = true; + + /* Restore terminal settings */ + if (terminal_modified) { + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); + terminal_modified = false; + } +} +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +// Externally available routines +// ==================================================================================================== +// ==================================================================================================== +// ==================================================================================================== +int main( int argc, char *argv[] ) + +{ + uint8_t cbw[TRANSFER_SIZE]; + + bool alreadyReported = false; + + int64_t remainTime; + int64_t thisTime; + struct timeval tv; + enum ReceiveResult receiveResult = RECEIVE_RESULT_OK; + size_t receivedSize = 0; + enum symbolErr r; + + /* Parse command line options using options module */ + if ( options_parse( argc, argv, &options ) != 0 ) + { + exit( -EINVAL ); + } + + genericsScreenHandling( !options.mono ); + + /* Check we've got _some_ symbols to start from */ + r = SymbolSetCreate( &_r.s, options.elffile, NULL, options.demangle, true, true, options.odoptions ); + + switch ( r ) + { + case SYMBOL_NOELF: + genericsExit( -1, "Elf file or symbols in it not found" EOL ); + break; + + case SYMBOL_NOOBJDUMP: + genericsExit( -1, "No objdump found" EOL ); + break; + + case SYMBOL_UNSPECIFIED: + genericsExit( -1, "Unknown error in symbol subsystem" EOL ); + break; + + default: + break; + } + + genericsReport( V_WARN, "Loaded %s" EOL, options.elffile ); + + if ( _r.s ) + { + genericsReport( V_INFO, "Files: %d" EOL "Functions: %d" EOL "Source: %d" EOL, _r.s->fileCount, _r.s->functionCount, _r.s->sourceCount ); + } + + /* Initialize RTOS support if requested */ + if ( options.rtos ) + { + _r.rtos = rtosDetectAndInit(_r.s, options.rtos, options.telnetPort, options.cpuFreq); + if ( !_r.rtos ) + { + /* Only exit if there's a real mismatch, not connection issues */ + if (terminal_modified) { + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); + terminal_modified = false; + } + genericsExit( -1, "RTOS initialization failed - ELF mismatch detected" EOL ); + } + + if ( _r.rtos ) + { + genericsReport( V_INFO, "RTOS tracking enabled for %s" EOL, _r.rtos->name ); + } + + if ( _r.rtos ) + { + + /* Set up ftrace output if requested */ + if ( options.ftrace ) + { + OutputConfig *output = calloc(1, sizeof(OutputConfig)); + if ( output ) + { + output->mode = OUTPUT_FTRACE; + + if ( strcmp(options.ftrace, "-") == 0 ) + { + output->file = stdout; + } + else + { + output->file = fopen(options.ftrace, "w"); + if ( !output->file ) + { + genericsReport( V_ERROR, "Cannot open ftrace output file %s" EOL, options.ftrace ); + free(output); + output = NULL; + } + else + { + setvbuf(output->file, NULL, _IONBF, 0); + } + } + + if ( output && output->file ) + { + _r.rtos->output_config = output; + output_init(output); + genericsReport( V_INFO, "ftrace output enabled to %s" EOL, + options.ftrace[0] == '-' ? "stdout" : options.ftrace ); + } + } + } + } + } + + /* Reset the handlers before we start */ + ITMDecoderInit( &_r.i, options.forceITMSync ); + OFLOWInit( &_r.c ); + MSGSeqInit( &_r.d, &_r.i, MSG_REORDER_BUFLEN ); + + /* This ensures the signal handler gets called */ + if ( SIG_ERR == signal( SIGINT, _intHandler ) ) + { + genericsExit( -1, "Failed to establish Int handler" EOL ); + } + + /* Set terminal to non-canonical mode for immediate key reading */ + struct termios new_tio; + tcgetattr(STDIN_FILENO, &old_tio); + new_tio = old_tio; + new_tio.c_lflag &= ~(ICANON | ECHO); /* Disable canonical mode and echo */ + new_tio.c_cc[VMIN] = 0; /* Non-blocking read */ + new_tio.c_cc[VTIME] = 0; /* No timeout */ + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); + terminal_modified = true; + + /* First interval will be from startup to first packet arriving */ + _r.lastReportus = _timestamp(); + _r.currentException = NO_EXCEPTION; + + /* Initialize output configuration */ + _initOutput(); + + while ( !_r.ending ) + { + struct Stream *stream = _openStream(); + + if ( stream == NULL ) + { + if ( !alreadyReported ) + { + genericsReport( V_ERROR, "No connection" EOL ); + alreadyReported = true; + } + + usleep( 500 * 1000 ); + continue; + } + + alreadyReported = false; + + if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_clear_screen(_outputConfig); + output_console_message(_outputConfig, "Connected..." EOL); + } + + /* Configure exception trace if requested */ + if ( options.outputExceptions ) + { + if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_console_message(_outputConfig, "Exception output ENABLED (-E flag detected)" EOL); + } + if ( options.telnetPort > 0 ) + { + telnet_configure_exception_trace(true); + if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_console_message(_outputConfig, "Sending exception_trace_enable to OpenOCD via telnet" EOL); + } + } + else if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_console_message(_outputConfig, "Warning: Telnet port not configured, cannot enable HW exception trace" EOL); + } + } + else + { + if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_console_message(_outputConfig, "Exception output DISABLED (use -E to enable)" EOL); + } + if ( options.telnetPort > 0 ) + { + telnet_configure_exception_trace(false); + if (_outputConfig && _outputConfig->mode == OUTPUT_CONSOLE) + { + output_console_message(_outputConfig, "Sending exception_trace_disable to OpenOCD via telnet" EOL); + } + } + } + + /* Reinitialize RTOS on ITM reconnection */ + _reinitializeRTOS(); + + thisTime = _r.lastReportus = _timestamp(); + + while ( !_r.ending ) + { + /* Check for keyboard input using select() */ + fd_set readfds; + struct timeval key_tv = {0, 0}; /* Non-blocking check */ + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + + if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, &key_tv) > 0) + { + char key = getchar(); + + /* Handle sort key commands */ + switch (key) + { + case 't': /* Sort by TCB address */ + options.rtosSort = "tcb"; + genericsReport(V_INFO, "Sorting by TCB address" EOL); + break; + case 'c': /* Sort by current CPU usage */ + options.rtosSort = "cpu"; + genericsReport(V_INFO, "Sorting by CPU usage" EOL); + break; + case 'm': /* Sort by maximum CPU usage */ + options.rtosSort = "maxcpu"; + genericsReport(V_INFO, "Sorting by maximum CPU usage" EOL); + break; + case 'n': /* Sort by thread name */ + options.rtosSort = "name"; + genericsReport(V_INFO, "Sorting by thread name" EOL); + break; + case 'f': /* Sort by function name */ + options.rtosSort = "func"; + genericsReport(V_INFO, "Sorting by function name" EOL); + break; + case 'p': /* Sort by priority */ + options.rtosSort = "priority"; + genericsReport(V_INFO, "Sorting by priority" EOL); + break; + case 's': /* Sort by context switches */ + options.rtosSort = "switches"; + genericsReport(V_INFO, "Sorting by context switches" EOL); + break; + case 'r': /* Reset maximum CPU values */ + if (_r.rtos) { + struct rtosThread *thread, *tmp; + HASH_ITER(hh, _r.rtos->threads, thread, tmp) { + thread->max_cpu_percent = 0; + } + _r.rtos->max_cpu_usage = 0; + genericsReport(V_INFO, "Reset all maximum CPU values" EOL); + } + break; + } + } + + remainTime = ( ( _r.lastReportus + options.displayInterval - thisTime ) ); + + if ( remainTime > 0 ) + { + tv.tv_sec = remainTime / 1000000; + tv.tv_usec = remainTime % 1000000; + receiveResult = stream->receive( stream, cbw, TRANSFER_SIZE, &tv, &receivedSize ); + } + else + { + receiveResult = RECEIVE_RESULT_OK; + receivedSize = 0; + } + + thisTime = _timestamp(); + + if ( receiveResult == RECEIVE_RESULT_ERROR ) + { + /* Something went wrong in the receive */ + break; + } + + if ( receiveResult == RECEIVE_RESULT_EOF ) + { + /* We are at EOF, hopefully next loop will get more data. */ + } + + /* Check to make sure our symbols are still appropriate */ + if ( !SymbolSetValid( &_r.s, options.elffile ) ) + { + /* Make sure old references are invalidated */ + + r = SymbolSetCreate( &_r.s, options.elffile, NULL, options.demangle, true, true, options.odoptions ); + + switch ( r ) + { + case SYMBOL_NOELF: + genericsReport( V_WARN, "Elf file or symbols in it not found" EOL ); + break; + + case SYMBOL_NOOBJDUMP: + genericsExit( -1, "No objdump found" EOL ); + break; + + case SYMBOL_UNSPECIFIED: + genericsExit( -1, "Unknown error in symbol subsystem" EOL ); + break; + + default: + break; + } + + if ( SYMBOL_NOELF == r ) + { + usleep( 1000000L ); + continue; + } + + genericsReport( V_WARN, "Loaded %s" EOL, options.elffile ); + + if ( _r.s ) + { + genericsReport( V_INFO, "Files: %d" EOL "Functions: %d" EOL "Source: %d" EOL, _r.s->fileCount, _r.s->functionCount, _r.s->sourceCount ); + } + } + + + + if ( receivedSize ) + { + if ( PROT_OFLOW == options.protocol ) + { + OFLOWPump( &_r.c, cbw, receivedSize, _OFLOWpacketRxed, &_r ); + } + else + { + /* Pump all of the data through the protocol handler */ + uint8_t *c = cbw; + + while ( receivedSize > 0 ) + { + _itmPumpProcess( *c++ ); + receivedSize--; + } + } + } + + /* See if its time to post-process it */ + if ( receiveResult == RECEIVE_RESULT_TIMEOUT || remainTime <= 0 ) + { + _processOutput( thisTime ); + + /* ...and zero the exception records */ + for ( uint32_t e = 0; e < MAX_EXCEPTIONS; e++ ) + { + _r.er[e].visits = _r.er[e].maxDepth = _r.er[e].totalTime = _r.er[e].minTime = _r.er[e].maxTime = _r.er[e].maxWallTime = 0; + } + + /* ... and the event counters */ + for ( uint32_t i = 0; i < DWT_NUM_EVENTS; i++ ) + { + _r.dwt_event_acc[i] = 0; + } + + + /* It's safe to update these here because the ticks won't be updated until more + * records arrive. */ + if ( _r.ITMoverflows != ITMDecoderGetStats( &_r.i )->overflow ) + { + /* We had an overflow, so can't safely track max depth ... reset it */ + _r.erDepth = 0; + } + + _r.ITMoverflows = ITMDecoderGetStats( &_r.i )->overflow; + _r.SWPkt = ITMDecoderGetStats( &_r.i )->SWPkt; + _r.TSPkt = ITMDecoderGetStats( &_r.i )->TSPkt; + _r.HWPkt = ITMDecoderGetStats( &_r.i )->HWPkt; + _r.lastReportus = thisTime; + _r.lastReportTicks = _r.timeStamp; + + /* Check to make sure there's not an unexpected TPIU in here */ + if ( ITMDecoderGetStats( &_r.i )->tpiuSyncCount ) + { + genericsReport( V_WARN, "Got a TPIU sync while decoding ITM...did you miss a -t option?" EOL ); + ITMDecoderGetStats( &_r.i )->tpiuSyncCount = 0; + } + } + + } + + stream->close( stream ); + free( stream ); + } + + if ( !_r.ending && ( !ITMDecoderGetStats( &_r.i )->tpiuSyncCount ) ) + { + genericsReport( V_ERROR, "Read failed" EOL ); + } + + /* Restore terminal settings before exit */ + if (terminal_modified) { + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); + terminal_modified = false; + } + + return -ESRCH; +} +// ==================================================================================================== diff --git a/Src/rtos/output/output_console.c b/Src/rtos/output/output_console.c new file mode 100644 index 00000000..409a8261 --- /dev/null +++ b/Src/rtos/output/output_console.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include + +#define CUTOFF 10 + +static uint32_t printed_lines = 0; + +void output_console_clear_screen(OutputConfig *config) +{ + if (!config->mono) + { + genericsFPrintf(stdout, CLEAR_SCREEN); + } + printed_lines = 0; +} + +void output_console_start_frame(OutputConfig *config, IntervalOutput *interval) +{ + output_console_clear_screen(config); +} + +void output_console_profile_entry(OutputConfig *config, ProfileOutput *entry) +{ + if (!entry) return; + + uint32_t percentage_int = (uint32_t)(entry->percentage * 100); + + if (!config->mono) + { + fprintf(stdout, C_DATA "%3d.%02d%% " C_SUPPORT " %7" PRIu64 " ", + percentage_int / 100, percentage_int % 100, entry->count); + + if (entry->filename) + { + fprintf(stdout, C_CONTEXT "%s" C_RESET "::", entry->filename); + } + + if (entry->line > 0) + { + fprintf(stdout, C_SUPPORT2 "%s" C_RESET "::" C_CONTEXT "%d\n", + entry->function, entry->line); + } else { + fprintf(stdout, C_SUPPORT2 "%s" C_RESET "\n", entry->function); + } + } else { + fprintf(stdout, "%3d.%02d%% %7" PRIu64 " ", + percentage_int / 100, percentage_int % 100, entry->count); + + if (entry->filename) + { + fprintf(stdout, "%s::", entry->filename); + } + + if (entry->line > 0) + { + fprintf(stdout, "%s::%d\n", entry->function, entry->line); + } else { + fprintf(stdout, "%s\n", entry->function); + } + } + +} + +void output_console_exception_header(OutputConfig *config) +{ + genericsFPrintf(stdout, "\n=== Exception Statistics ===\n"); + genericsFPrintf(stdout, "|-------------------|----------|-------|-------------|-------|------------|------------|------------|------------|\n"); + genericsFPrintf(stdout, "| Exception | Count | MaxD | TotalTicks | %% | AveTicks | minTicks | maxTicks | maxWall |\n"); + genericsFPrintf(stdout, "|-------------------|----------|-------|-------------|-------|------------|------------|------------|------------|\n"); +} + +void output_console_exception_entry(OutputConfig *config, ExceptionOutput *exception) +{ + if (!config->mono) + { + genericsFPrintf(stdout, "| " C_DATA "%-17s" C_RESET " | " C_DATA "%8" PRIu64 C_RESET + " | " C_DATA "%5" PRIu32 C_RESET " | " C_DATA "%11" PRId64 C_RESET + " | " C_DATA "%5.1f" C_RESET " | " C_DATA "%10" PRId64 C_RESET + " | " C_DATA "%10" PRId64 C_RESET " | " C_DATA "%10" PRId64 C_RESET + " | " C_DATA "%10" PRId64 C_RESET " |\n", + exception->exception_name, + exception->visits, exception->max_depth, exception->total_time, + exception->util_percent, exception->ave_time, + exception->min_time, exception->max_time, exception->max_wall_time); + } else { + genericsFPrintf(stdout, "| %-17s | %8" PRIu64 " | %5" PRIu32 + " | %11" PRId64 " | %5.1f | %10" PRId64 " | %10" PRId64 + " | %10" PRId64 " | %10" PRId64 " |\n", + exception->exception_name, + exception->visits, exception->max_depth, exception->total_time, + exception->util_percent, exception->ave_time, + exception->min_time, exception->max_time, exception->max_wall_time); + } +} + +void output_console_stats(OutputConfig *config, StatsOutput *stats) +{ + static uint32_t last_overflow = 0; + static uint32_t last_sync = 0; + static uint32_t last_errors = 0; + + genericsReport( V_INFO, " Ovf=%3d (+%d) ITMSync=%3d (+%d) ITMErrors=%3d (+%d)\n", + stats->overflow, stats->overflow - last_overflow, + stats->sync_count, stats->sync_count - last_sync, + stats->error_count, stats->error_count - last_errors); + + last_overflow = stats->overflow; + last_sync = stats->sync_count; + last_errors = stats->error_count; +} + +void output_console_rtos_threads(OutputConfig *config, struct rtosState *rtos, uint64_t window_time_us, bool itm_overflow, const char *sort_by) +{ + if (!rtos || !rtos->enabled || !rtos->threads) + return; + + rtosDumpThreadInfo(rtos, stdout, window_time_us, itm_overflow, sort_by); +} + +void output_console_rtos_info(OutputConfig *config, void *rtos_data) +{ +} + +void output_console_end_frame(OutputConfig *config) +{ + if (!config->mono) + { + fprintf(stdout, "\n" C_RESET); + } else { + fprintf(stdout, "\n"); + } + fflush(stdout); +} + +void output_console_status_line(OutputConfig *config, const char *format, va_list args) +{ + vfprintf(stdout, format, args); + fflush(stdout); +} + +void output_console_exception_footer(OutputConfig *config) +{ + genericsFPrintf(stdout, "|-------------------|----------|-------|-------------|-------|------------|------------|------------|------------|\n"); +} + +void output_console_no_exceptions(OutputConfig *config) +{ + genericsFPrintf(stdout, "| No exceptions detected yet... |\n"); +} + +void output_console_status_indicators(OutputConfig *config, bool overflow, bool sw_changed, bool ts_changed, bool hw_changed) +{ + genericsFPrintf(stdout, EOL C_RESET "[%s%s%s%s" C_RESET "] ", + overflow ? C_OVF_IND "V" : C_RESET "-", + sw_changed ? C_SOFT_IND "S" : C_RESET "-", + ts_changed ? C_TSTAMP_IND "T" : C_RESET "-", + hw_changed ? C_HW_IND "H" : C_RESET "-"); +} + +void output_console_interval_info(OutputConfig *config, uint64_t interval_ms, uint64_t interval_ticks, uint64_t ticks_per_ms, bool has_ticks) +{ + if (has_ticks && ticks_per_ms > 0) + { + genericsFPrintf(stdout, "Interval = " C_DATA "%" PRIu64 "ms " C_RESET "/ " C_DATA "%" PRIu64 C_RESET " (~" C_DATA "%" PRIu64 C_RESET " Ticks/ms)" EOL, + interval_ms, interval_ticks, ticks_per_ms); + } + else + { + genericsFPrintf(stdout, C_RESET "Interval = " C_DATA "%" PRIu64 C_RESET "ms" EOL, interval_ms); + } +} + +void output_console_sort_options(OutputConfig *config, bool show_options) +{ + if (show_options) + { + genericsFPrintf(stdout, C_RESET "Sort: " C_SUPPORT "[t]" C_RESET "cb " C_SUPPORT "[c]" C_RESET "pu " + C_SUPPORT "[m]" C_RESET "ax " C_SUPPORT "[n]" C_RESET "ame " + C_SUPPORT "[f]" C_RESET "unc " C_SUPPORT "[p]" C_RESET "riority " + C_SUPPORT "[s]" C_RESET "witches " C_CYAN "| " C_SUPPORT "[r]" C_RESET "eset max" C_RESET EOL); + } +} + +void output_console_message(OutputConfig *config, const char *message) +{ + genericsFPrintf(stdout, "%s", message); +} \ No newline at end of file diff --git a/Src/rtos/output/output_console.h b/Src/rtos/output/output_console.h new file mode 100644 index 00000000..1af46538 --- /dev/null +++ b/Src/rtos/output/output_console.h @@ -0,0 +1,26 @@ +#ifndef OUTPUT_CONSOLE_H +#define OUTPUT_CONSOLE_H + +#include +#include + +struct rtosState; + +void output_console_start_frame(OutputConfig *config, IntervalOutput *interval); +void output_console_profile_entry(OutputConfig *config, ProfileOutput *entry); +void output_console_exception_entry(OutputConfig *config, ExceptionOutput *exception); +void output_console_stats(OutputConfig *config, StatsOutput *stats); +void output_console_rtos_info(OutputConfig *config, void *rtos_data); +void output_console_rtos_threads(OutputConfig *config, struct rtosState *rtos, uint64_t window_time_us, bool itm_overflow, const char *sort_by); +void output_console_end_frame(OutputConfig *config); +void output_console_clear_screen(OutputConfig *config); +void output_console_status_line(OutputConfig *config, const char *format, va_list args); +void output_console_exception_header(OutputConfig *config); +void output_console_exception_footer(OutputConfig *config); +void output_console_no_exceptions(OutputConfig *config); +void output_console_status_indicators(OutputConfig *config, bool overflow, bool sw_changed, bool ts_changed, bool hw_changed); +void output_console_interval_info(OutputConfig *config, uint64_t interval_ms, uint64_t interval_ticks, uint64_t ticks_per_ms, bool has_ticks); +void output_console_sort_options(OutputConfig *config, bool show_options); +void output_console_message(OutputConfig *config, const char *message); + +#endif \ No newline at end of file diff --git a/Src/rtos/output/output_ftrace.c b/Src/rtos/output/output_ftrace.c new file mode 100644 index 00000000..f67a7c3b --- /dev/null +++ b/Src/rtos/output/output_ftrace.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint64_t base_timestamp_us = 0; +static bool first_switch = true; +static struct rtosThread *last_thread = NULL; +static int cpu_id = 0; + +void output_ftrace_start_frame(OutputConfig *config, IntervalOutput *interval) +{ + if (!config || !config->file) + { + return; + } + + if (first_switch && interval) + { + base_timestamp_us = interval->timestamp; + } +} + +void output_ftrace_thread_switch(OutputConfig *config, struct rtosThread *prev, struct rtosThread *next, uint64_t timestamp_us) +{ + genericsReport(V_DEBUG, "ftrace: thread_switch called - conf...ile=%p, next=%p\n", config, config ? config->file : NULL, next); + + if (!config || !config->file || !next) + { + genericsReport(V_ERROR, "ftrace: thread_switch validation failed - config=%p, file=%p, next=%p\n", + config, config ? config->file : NULL, next); + return; + } + + if (first_switch) + { + base_timestamp_us = timestamp_us; + first_switch = false; + + fprintf(config->file, "# tracer: nop\n"); + fprintf(config->file, "#\n"); + fprintf(config->file, "# entries-in-buffer/entries-written: 0/0 #P:1\n"); + fprintf(config->file, "#\n"); + fprintf(config->file, "# _-----=> irqs-off\n"); + fprintf(config->file, "# / _----=> need-resched\n"); + fprintf(config->file, "# | / _---=> hardirq/softirq\n"); + fprintf(config->file, "# || / _--=> preempt-depth\n"); + fprintf(config->file, "# ||| / delay\n"); + fprintf(config->file, "# TASK-PID CPU# |||| TIMESTAMP FUNCTION\n"); + fprintf(config->file, "# | | | |||| | |\n"); + } + + double t = (timestamp_us - base_timestamp_us) / 1000000.0; + + const char *p_base = "unknown"; + const char *p_entry = NULL; + uintptr_t p_tcb = 0; + int p_prio = 0; + unsigned p_pid = 0; + + if (prev) + { + p_base = (prev->name && prev->name[0]) ? prev->name : "unknown"; + p_entry = prev->entry_func_name; + p_tcb = prev->tcb_addr; + p_prio = prev->priority; + p_pid = (unsigned)(uint32_t)p_tcb; + } + + const char *n_base = (next->name && next->name[0]) ? next->name : "unknown"; + const char *n_entry = next->entry_func_name; + uintptr_t n_tcb = next->tcb_addr; + int n_prio = next->priority; + unsigned n_pid = (unsigned)(uint32_t)n_tcb; + + char p_name[128]; + char n_name[128]; + + if (p_entry && p_entry[0]) + snprintf(p_name, sizeof(p_name), "%s|%s", p_base, p_entry); + else + snprintf(p_name, sizeof(p_name), "%s", p_base); + + if (n_entry && n_entry[0]) + snprintf(n_name, sizeof(n_name), "%s|%s", n_base, n_entry); + else + snprintf(n_name, sizeof(n_name), "%s", n_base); + + fprintf(config->file, + "%16s-%u [%03d] .... %12.6f: sched_switch: prev_comm=%s prev_pid=%u prev_prio=%d prev_state=%c ==> next_comm=%s next_pid=%u next_prio=%d\n", + p_name, p_pid, cpu_id, t, + p_name, p_pid, p_prio, 'S', + n_name, n_pid, n_prio); + + fflush(config->file); + + last_thread = next; +} + + + +void output_ftrace_profile_entry(OutputConfig *config, ProfileOutput *entry) +{ +} + +void output_ftrace_exception_entry(OutputConfig *config, ExceptionOutput *exception) +{ +} + +void output_ftrace_stats(OutputConfig *config, StatsOutput *stats) +{ +} + +void output_ftrace_rtos_info(OutputConfig *config, void *rtos_data) +{ +} + +void output_ftrace_end_frame(OutputConfig *config) +{ + if (config && config->file) + { + fflush(config->file); + } +} + +void output_ftrace_status_line(OutputConfig *config, const char *format, va_list args) +{ +} \ No newline at end of file diff --git a/Src/rtos/output/output_ftrace.h b/Src/rtos/output/output_ftrace.h new file mode 100644 index 00000000..a099abbb --- /dev/null +++ b/Src/rtos/output/output_ftrace.h @@ -0,0 +1,16 @@ +#ifndef OUTPUT_FTRACE_H +#define OUTPUT_FTRACE_H + +#include + +struct rtosThread; + +void output_ftrace_start_frame(OutputConfig *config, IntervalOutput *interval); +void output_ftrace_profile_entry(OutputConfig *config, ProfileOutput *entry); +void output_ftrace_exception_entry(OutputConfig *config, ExceptionOutput *exception); +void output_ftrace_stats(OutputConfig *config, StatsOutput *stats); +void output_ftrace_rtos_info(OutputConfig *config, void *rtos_data); +void output_ftrace_end_frame(OutputConfig *config); +void output_ftrace_thread_switch(OutputConfig *config, struct rtosThread *prev, struct rtosThread *next, uint64_t timestamp_us); + +#endif \ No newline at end of file diff --git a/Src/rtos/output/output_handler.c b/Src/rtos/output/output_handler.c new file mode 100644 index 00000000..0985203c --- /dev/null +++ b/Src/rtos/output/output_handler.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*start_frame_fn)(OutputConfig*, IntervalOutput*); +typedef void (*profile_entry_fn)(OutputConfig*, ProfileOutput*); +typedef void (*exception_entry_fn)(OutputConfig*, ExceptionOutput*); +typedef void (*stats_fn)(OutputConfig*, StatsOutput*); +typedef void (*rtos_info_fn)(OutputConfig*, void*); +typedef void (*end_frame_fn)(OutputConfig*); +typedef void (*thread_switch_fn)(OutputConfig*, struct rtosThread*, struct rtosThread*, uint64_t); + +static const start_frame_fn start_frame_handlers[] = { + [OUTPUT_CONSOLE] = output_console_start_frame, + [OUTPUT_JSON_FILE] = output_json_start_frame, + [OUTPUT_JSON_UDP] = output_json_start_frame, + [OUTPUT_FTRACE] = output_ftrace_start_frame, + [OUTPUT_DISABLED] = NULL +}; + +static const profile_entry_fn profile_handlers[] = { + [OUTPUT_CONSOLE] = output_console_profile_entry, + [OUTPUT_JSON_FILE] = output_json_profile_entry, + [OUTPUT_JSON_UDP] = output_json_profile_entry, + [OUTPUT_FTRACE] = output_ftrace_profile_entry, + [OUTPUT_DISABLED] = NULL +}; + +static const exception_entry_fn exception_handlers[] = { + [OUTPUT_CONSOLE] = output_console_exception_entry, + [OUTPUT_JSON_FILE] = output_json_exception_entry, + [OUTPUT_JSON_UDP] = output_json_exception_entry, + [OUTPUT_FTRACE] = output_ftrace_exception_entry, + [OUTPUT_DISABLED] = NULL +}; + +static const stats_fn stats_handlers[] = { + [OUTPUT_CONSOLE] = output_console_stats, + [OUTPUT_JSON_FILE] = output_json_stats, + [OUTPUT_JSON_UDP] = output_json_stats, + [OUTPUT_FTRACE] = output_ftrace_stats, + [OUTPUT_DISABLED] = NULL +}; + +static const rtos_info_fn rtos_handlers[] = { + [OUTPUT_CONSOLE] = output_console_rtos_info, + [OUTPUT_JSON_FILE] = output_json_rtos_info, + [OUTPUT_JSON_UDP] = output_json_rtos_info, + [OUTPUT_FTRACE] = output_ftrace_rtos_info, + [OUTPUT_DISABLED] = NULL +}; + +static const end_frame_fn end_frame_handlers[] = { + [OUTPUT_CONSOLE] = output_console_end_frame, + [OUTPUT_JSON_FILE] = output_json_end_frame, + [OUTPUT_JSON_UDP] = output_json_end_frame, + [OUTPUT_FTRACE] = output_ftrace_end_frame, + [OUTPUT_DISABLED] = NULL +}; + +static const thread_switch_fn thread_switch_handlers[] = { + [OUTPUT_CONSOLE] = NULL, + [OUTPUT_JSON_FILE] = NULL, + [OUTPUT_JSON_UDP] = NULL, + [OUTPUT_FTRACE] = output_ftrace_thread_switch, + [OUTPUT_DISABLED] = NULL +}; + +void output_init(OutputConfig *config) +{ + if (!config) + return; + + if (config->mode == OUTPUT_JSON_UDP && config->udp_socket < 0) { + config->udp_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (config->udp_socket >= 0 && config->udp_dest) { + config->udp_dest->sin_family = AF_INET; + config->udp_dest->sin_addr.s_addr = inet_addr("127.0.0.1"); + } + } +} + +void output_cleanup(OutputConfig *config) +{ + if (!config) + return; + + if (config->file && config->file != stdout && config->file != stderr) { + fclose(config->file); + config->file = NULL; + } + + if (config->udp_socket >= 0) { + close(config->udp_socket); + config->udp_socket = -1; + } +} + +void output_start_frame(OutputConfig *config, IntervalOutput *interval) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(start_frame_handlers)/sizeof(start_frame_handlers[0])) + return; + + start_frame_fn handler = start_frame_handlers[config->mode]; + if (handler) + handler(config, interval); +} + +void output_profile_entry(OutputConfig *config, ProfileOutput *entry) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(profile_handlers)/sizeof(profile_handlers[0])) + return; + + profile_entry_fn handler = profile_handlers[config->mode]; + if (handler) + handler(config, entry); +} + +void output_exception_entry(OutputConfig *config, ExceptionOutput *exception) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(exception_handlers)/sizeof(exception_handlers[0])) + return; + + exception_entry_fn handler = exception_handlers[config->mode]; + if (handler) + handler(config, exception); +} + +void output_stats(OutputConfig *config, StatsOutput *stats) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(stats_handlers)/sizeof(stats_handlers[0])) + return; + + stats_fn handler = stats_handlers[config->mode]; + if (handler) + handler(config, stats); +} + +void output_rtos_info(OutputConfig *config, void *rtos_data) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(rtos_handlers)/sizeof(rtos_handlers[0])) + return; + + rtos_info_fn handler = rtos_handlers[config->mode]; + if (handler) + handler(config, rtos_data); +} + +void output_end_frame(OutputConfig *config) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(end_frame_handlers)/sizeof(end_frame_handlers[0])) + return; + + end_frame_fn handler = end_frame_handlers[config->mode]; + if (handler) + handler(config); +} + +void output_clear_screen(OutputConfig *config) +{ + if (!config || config->mode != OUTPUT_CONSOLE) + return; + output_console_clear_screen(config); +} + +void output_status_line(OutputConfig *config, const char *format, ...) +{ + if (!config || config->mode != OUTPUT_CONSOLE) + return; + + va_list args; + va_start(args, format); + output_console_status_line(config, format, args); + va_end(args); +} + +void output_thread_switch(OutputConfig *config, struct rtosThread *prev, struct rtosThread *next, uint64_t timestamp_us) +{ + if (!config || config->mode == OUTPUT_DISABLED || config->mode >= sizeof(thread_switch_handlers)/sizeof(thread_switch_handlers[0])) + { + return; + } + + thread_switch_fn handler = thread_switch_handlers[config->mode]; + if (handler) + { + handler(config, prev, next, timestamp_us); + } +} \ No newline at end of file diff --git a/Src/rtos/output/output_handler.h b/Src/rtos/output/output_handler.h new file mode 100644 index 00000000..2f496945 --- /dev/null +++ b/Src/rtos/output/output_handler.h @@ -0,0 +1,79 @@ +#ifndef OUTPUT_HANDLER_H +#define OUTPUT_HANDLER_H + +#include +#include +#include + +typedef enum { + OUTPUT_CONSOLE, + OUTPUT_JSON_FILE, + OUTPUT_JSON_UDP, + OUTPUT_FTRACE, + OUTPUT_DISABLED +} OutputMode; + +typedef struct { + OutputMode mode; + FILE *file; + int udp_socket; + struct sockaddr_in *udp_dest; + bool mono; + uint32_t cutscreen; +} OutputConfig; + +typedef struct { + uint32_t exception_num; + const char *exception_name; + uint64_t visits; + uint32_t max_depth; + int64_t total_time; + int64_t min_time; + int64_t max_time; + int64_t max_wall_time; + float util_percent; + int64_t ave_time; +} ExceptionOutput; + +typedef struct { + uint32_t overflow; + uint32_t sync_count; + uint32_t error_count; + uint32_t sw_packets; + uint32_t ts_packets; + uint32_t hw_packets; +} StatsOutput; + +typedef struct { + uint64_t timestamp; + uint64_t interval_us; + uint64_t interval_ticks; + uint64_t ticks_per_ms; + uint32_t total_samples; +} IntervalOutput; + +typedef struct { + const char *filename; + const char *function; + uint32_t line; + uint64_t count; + float percentage; +} ProfileOutput; + +void output_init(OutputConfig *config); +void output_cleanup(OutputConfig *config); + +void output_start_frame(OutputConfig *config, IntervalOutput *interval); +void output_profile_entry(OutputConfig *config, ProfileOutput *entry); +void output_exception_entry(OutputConfig *config, ExceptionOutput *exception); +void output_stats(OutputConfig *config, StatsOutput *stats); +void output_rtos_info(OutputConfig *config, void *rtos_data); +void output_end_frame(OutputConfig *config); + +struct rtosThread; +void output_thread_switch(OutputConfig *config, struct rtosThread *prev, struct rtosThread *next, uint64_t timestamp_us); + +void output_clear_screen(OutputConfig *config); +void output_status_line(OutputConfig *config, const char *format, ...); + +#endif \ No newline at end of file diff --git a/Src/rtos/output/output_json.c b/Src/rtos/output/output_json.c new file mode 100644 index 00000000..b1014fe1 --- /dev/null +++ b/Src/rtos/output/output_json.c @@ -0,0 +1,335 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static cJSON *json_root = NULL; +static cJSON *json_profile_array = NULL; +static cJSON *json_exception_array = NULL; + +static void send_udp_json(OutputConfig *config, const char *json_str) +{ + if (config->udp_socket >= 0 && config->udp_dest && json_str) + { + int result = sendto(config->udp_socket, json_str, strlen(json_str), 0, + (struct sockaddr *)config->udp_dest, sizeof(struct sockaddr_in)); + if (result < 0) + { + genericsReport(V_ERROR, "UDP send failed: %s" EOL, strerror(errno)); + } + } +} + +static void output_json_object(OutputConfig *config, cJSON *obj) +{ + if (!obj) + return; + + char *json_str = cJSON_PrintUnformatted(obj); + if (!json_str) + return; + + if (config->mode == OUTPUT_JSON_FILE && config->file) + { + fprintf(config->file, "%s\n", json_str); + fflush(config->file); + } + else if (config->mode == OUTPUT_JSON_UDP) + { + size_t len = strlen(json_str); + char *buffer = malloc(len + 2); + if (buffer) + { + snprintf(buffer, len + 2, "%s\n", json_str); + send_udp_json(config, buffer); + free(buffer); + } + } + + free(json_str); +} + +void output_json_start_frame(OutputConfig *config, IntervalOutput *interval) +{ + if (json_root) + { + cJSON_Delete(json_root); + json_root = NULL; + } + + json_root = cJSON_CreateObject(); + if (!json_root) + return; + + cJSON_AddNumberToObject(json_root, "timestamp", interval->timestamp); + cJSON_AddNumberToObject(json_root, "interval_us", interval->interval_us); + cJSON_AddNumberToObject(json_root, "interval_ticks", interval->interval_ticks); + cJSON_AddNumberToObject(json_root, "ticks_per_ms", interval->ticks_per_ms); + cJSON_AddNumberToObject(json_root, "total_samples", interval->total_samples); + + json_profile_array = cJSON_CreateArray(); + json_exception_array = cJSON_CreateArray(); + + if (json_profile_array) + cJSON_AddItemToObject(json_root, "profile", json_profile_array); + if (json_exception_array) + cJSON_AddItemToObject(json_root, "exceptions", json_exception_array); +} + +void output_json_profile_entry(OutputConfig *config, ProfileOutput *entry) +{ + if (!json_profile_array || !entry) + return; + + cJSON *item = cJSON_CreateObject(); + if (!item) + return; + + cJSON_AddStringToObject(item, "function", entry->function ? entry->function : ""); + if (entry->filename) + cJSON_AddStringToObject(item, "filename", entry->filename); + if (entry->line > 0) + cJSON_AddNumberToObject(item, "line", entry->line); + cJSON_AddNumberToObject(item, "count", entry->count); + cJSON_AddNumberToObject(item, "percentage", entry->percentage); + + cJSON_AddItemToArray(json_profile_array, item); +} + +void output_json_exception_entry(OutputConfig *config, ExceptionOutput *exception) +{ + if (config->mode == OUTPUT_JSON_UDP) + { + cJSON *item = cJSON_CreateObject(); + if (!item) + return; + + cJSON_AddNumberToObject(item, "ex", 1); + cJSON_AddNumberToObject(item, "num", exception->exception_num); + cJSON_AddStringToObject(item, "name", exception->exception_name ? exception->exception_name : ""); + cJSON_AddNumberToObject(item, "count", exception->visits); + cJSON_AddNumberToObject(item, "maxd", exception->max_depth); + cJSON_AddNumberToObject(item, "total", exception->total_time); + cJSON_AddNumberToObject(item, "pct", exception->util_percent); + cJSON_AddNumberToObject(item, "ave", exception->ave_time); + cJSON_AddNumberToObject(item, "min", exception->min_time); + cJSON_AddNumberToObject(item, "max", exception->max_time); + cJSON_AddNumberToObject(item, "maxwall", exception->max_wall_time); + + output_json_object(config, item); + cJSON_Delete(item); + } + else if (json_exception_array) + { + cJSON *item = cJSON_CreateObject(); + if (!item) + return; + + cJSON_AddNumberToObject(item, "num", exception->exception_num); + cJSON_AddStringToObject(item, "name", exception->exception_name ? exception->exception_name : ""); + cJSON_AddNumberToObject(item, "visits", exception->visits); + cJSON_AddNumberToObject(item, "max_depth", exception->max_depth); + cJSON_AddNumberToObject(item, "total_time", exception->total_time); + cJSON_AddNumberToObject(item, "util_percent", exception->util_percent); + cJSON_AddNumberToObject(item, "ave_time", exception->ave_time); + cJSON_AddNumberToObject(item, "min_time", exception->min_time); + cJSON_AddNumberToObject(item, "max_time", exception->max_time); + cJSON_AddNumberToObject(item, "max_wall_time", exception->max_wall_time); + + cJSON_AddItemToArray(json_exception_array, item); + } +} + +void output_json_stats(OutputConfig *config, StatsOutput *stats) +{ + if (!json_root || !stats) + return; + + cJSON *stats_obj = cJSON_CreateObject(); + if (!stats_obj) + return; + + cJSON_AddNumberToObject(stats_obj, "overflow", stats->overflow); + cJSON_AddNumberToObject(stats_obj, "sync_count", stats->sync_count); + cJSON_AddNumberToObject(stats_obj, "error_count", stats->error_count); + cJSON_AddNumberToObject(stats_obj, "sw_packets", stats->sw_packets); + cJSON_AddNumberToObject(stats_obj, "ts_packets", stats->ts_packets); + cJSON_AddNumberToObject(stats_obj, "hw_packets", stats->hw_packets); + + cJSON_AddItemToObject(json_root, "stats", stats_obj); +} + +void output_json_rtos_threads(OutputConfig *config, struct rtosState *rtos, uint64_t window_time_us, bool itm_overflow) +{ + if (!config || !rtos || !rtos->enabled || !rtos->threads) + return; + + struct rtosThread *thread, *tmp; + uint64_t active_accum_us = 0; + uint64_t total_accum_us = 0; + bool has_idle_concept = false; + + cJSON *root = cJSON_CreateObject(); + if (!root) return; + + cJSON *threads_array = cJSON_CreateArray(); + if (!threads_array) + { + cJSON_Delete(root); + return; + } + + HASH_ITER(hh, rtos->threads, thread, tmp) + { + cJSON *thread_obj = cJSON_CreateObject(); + if (!thread_obj) continue; + + uint32_t cpu_pct = 0; + if (window_time_us > 0) + { + uint64_t temp = (uint64_t)thread->accumulated_time_us * 10000ULL; + cpu_pct = (uint32_t)(temp / window_time_us); + if (cpu_pct > 10000) cpu_pct = 10000; + } + + /* Track totals for overall CPU usage calculation */ + total_accum_us += thread->accumulated_time_us; + + /* Check if this is an idle thread using RTOS-specific method */ + bool is_idle = false; + if (rtos->ops && rtos->ops->is_idle_thread) + { + is_idle = rtos->ops->is_idle_thread(thread); + } + + if (!is_idle) + { + active_accum_us += thread->accumulated_time_us; + } + else + { + has_idle_concept = true; + } + + char tcb_str[20]; + snprintf(tcb_str, sizeof(tcb_str), "0x%08X", thread->tcb_addr); + cJSON_AddStringToObject(thread_obj, "tcb", tcb_str); + cJSON_AddStringToObject(thread_obj, "name", thread->name); + cJSON_AddStringToObject(thread_obj, "func", thread->entry_func_name ? thread->entry_func_name : "unknown"); + cJSON_AddNumberToObject(thread_obj, "prio", thread->priority); + cJSON_AddNumberToObject(thread_obj, "time_ms", thread->accumulated_time_us / 1000); + + char cpu_str[16]; + snprintf(cpu_str, sizeof(cpu_str), "%.3f", cpu_pct / 100.0); + cJSON_AddRawToObject(thread_obj, "cpu", cpu_str); + + char max_str[16]; + snprintf(max_str, sizeof(max_str), "%.3f", thread->max_cpu_percent / 100.0); + cJSON_AddRawToObject(thread_obj, "max", max_str); + cJSON_AddNumberToObject(thread_obj, "switches", thread->window_switches); + + cJSON_AddItemToArray(threads_array, thread_obj); + } + + cJSON_AddItemToObject(root, "threads", threads_array); + + if (window_time_us > 0 && has_idle_concept) + { + uint64_t temp = (uint64_t)active_accum_us * 10000ULL; + uint32_t cpu_usage_pct = (uint32_t)(temp / window_time_us); + + cJSON_AddNumberToObject(root, "interval_ms", window_time_us / 1000); + + char cpu_usage_str[16]; + snprintf(cpu_usage_str, sizeof(cpu_usage_str), "%.3f", cpu_usage_pct / 100.0); + cJSON_AddRawToObject(root, "cpu_usage", cpu_usage_str); + + char cpu_max_str[16]; + snprintf(cpu_max_str, sizeof(cpu_max_str), "%.3f", rtos->max_cpu_usage / 100.0); + cJSON_AddRawToObject(root, "cpu_max", cpu_max_str); + if (rtos->cpu_freq > 0) + { + cJSON_AddNumberToObject(root, "cpu_freq", rtos->cpu_freq); + } + cJSON_AddBoolToObject(root, "overflow", itm_overflow); + } + + output_json_object(config, root); + cJSON_Delete(root); +} + +void output_json_exceptions(OutputConfig *config, struct exceptionRecord *exceptions, uint32_t max_exceptions, uint64_t timeStamp, uint64_t lastReportTicks) +{ + if (!config || !exceptions) + return; + + cJSON *root = cJSON_CreateObject(); + if (!root) return; + + cJSON *exceptions_array = cJSON_CreateArray(); + if (!exceptions_array) + { + cJSON_Delete(root); + return; + } + + for (uint32_t e = 0; e < max_exceptions; e++) + { + if (exceptions[e].visits) + { + cJSON *exc_obj = cJSON_CreateObject(); + if (!exc_obj) continue; + + const char* exceptionName = exceptionGetName(e); + float util_percent = (lastReportTicks && timeStamp > lastReportTicks) ? + ((float)exceptions[e].totalTime / (timeStamp - lastReportTicks) * 100.0f) : 0; + + cJSON_AddNumberToObject(exc_obj, "num", e); + cJSON_AddStringToObject(exc_obj, "name", exceptionName); + cJSON_AddNumberToObject(exc_obj, "count", exceptions[e].visits); + cJSON_AddNumberToObject(exc_obj, "maxd", exceptions[e].maxDepth); + cJSON_AddNumberToObject(exc_obj, "total", exceptions[e].totalTime); + cJSON_AddNumberToObject(exc_obj, "pct", util_percent); + cJSON_AddNumberToObject(exc_obj, "ave", exceptions[e].visits ? exceptions[e].totalTime / exceptions[e].visits : 0); + cJSON_AddNumberToObject(exc_obj, "min", exceptions[e].minTime); + cJSON_AddNumberToObject(exc_obj, "max", exceptions[e].maxTime); + cJSON_AddNumberToObject(exc_obj, "maxwall", exceptions[e].maxWallTime); + + cJSON_AddItemToArray(exceptions_array, exc_obj); + } + } + + cJSON_AddItemToObject(root, "exceptions", exceptions_array); + output_json_object(config, root); + cJSON_Delete(root); +} + +void output_json_rtos_info(OutputConfig *config, void *rtos_data) +{ + /* Legacy function - kept for compatibility */ +} + +void output_json_end_frame(OutputConfig *config) +{ + if (!json_root) + return; + + if (config->mode == OUTPUT_JSON_FILE) + { + output_json_object(config, json_root); + } + + cJSON_Delete(json_root); + json_root = NULL; + json_profile_array = NULL; + json_exception_array = NULL; +} \ No newline at end of file diff --git a/Src/rtos/output/output_json.h b/Src/rtos/output/output_json.h new file mode 100644 index 00000000..ad766e49 --- /dev/null +++ b/Src/rtos/output/output_json.h @@ -0,0 +1,19 @@ +#ifndef OUTPUT_JSON_H +#define OUTPUT_JSON_H + +#include + +struct rtosState; +struct rtosThread; +struct exceptionRecord; + +void output_json_start_frame(OutputConfig *config, IntervalOutput *interval); +void output_json_profile_entry(OutputConfig *config, ProfileOutput *entry); +void output_json_exception_entry(OutputConfig *config, ExceptionOutput *exception); +void output_json_stats(OutputConfig *config, StatsOutput *stats); +void output_json_rtos_info(OutputConfig *config, void *rtos_data); +void output_json_rtos_threads(OutputConfig *config, struct rtosState *rtos, uint64_t window_time_us, bool itm_overflow); +void output_json_exceptions(OutputConfig *config, struct exceptionRecord *exceptions, uint32_t max_exceptions, uint64_t timeStamp, uint64_t lastReportTicks); +void output_json_end_frame(OutputConfig *config); + +#endif \ No newline at end of file diff --git a/Src/rtos/rtos_api.c b/Src/rtos/rtos_api.c new file mode 100644 index 00000000..3042d9df --- /dev/null +++ b/Src/rtos/rtos_api.c @@ -0,0 +1,887 @@ +#include +#include +#include +#include +#include "generics.h" +#include "symbols.h" +#include "rtos_support.h" +#include +#include +#include "uthash.h" + + +struct rtx5_private { + uint32_t osRtxInfo; + uint32_t thread_run_curr; + uint32_t pendSV_Handler; + uint32_t osRtxThreadListPut; +}; + +/* Hash for unresolved function addresses */ +struct unresolvedFunc { + uint32_t addr; + UT_hash_handle hh; +}; +static struct unresolvedFunc *unresolvedFuncs = NULL; + +/* Sort functions for threads */ +static int cpu_usage_sort_desc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + if (tb->accumulated_time_us > ta->accumulated_time_us) return 1; + if (tb->accumulated_time_us < ta->accumulated_time_us) return -1; + return 0; +} + +static int max_cpu_sort_desc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + if (tb->max_cpu_percent > ta->max_cpu_percent) return 1; + if (tb->max_cpu_percent < ta->max_cpu_percent) return -1; + return 0; +} + +static int tcb_addr_sort_asc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + if (ta->tcb_addr > tb->tcb_addr) return 1; + if (ta->tcb_addr < tb->tcb_addr) return -1; + return 0; +} + +static int name_sort_asc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + return strcmp(ta->name, tb->name); +} + +static int func_sort_asc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + const char *fa = ta->entry_func_name ? ta->entry_func_name : ""; + const char *fb = tb->entry_func_name ? tb->entry_func_name : ""; + return strcmp(fa, fb); +} + +static int priority_sort_desc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + if (tb->priority > ta->priority) return 1; + if (tb->priority < ta->priority) return -1; + return 0; +} + +static int switches_sort_desc(void *a, void *b) +{ + struct rtosThread *ta = (struct rtosThread *)a; + struct rtosThread *tb = (struct rtosThread *)b; + if (tb->context_switches > ta->context_switches) return 1; + if (tb->context_switches < ta->context_switches) return -1; + return 0; +} + +/* Helper function to find osRtxInfo address from ELF file */ +static uint32_t findOsRtxInfoAddress(const char *elfFile) +{ + if (!elfFile) return 0; + + char cmd[512]; + snprintf(cmd, sizeof(cmd), "arm-none-eabi-objdump -t %s 2>/dev/null | grep 'osRtxInfo$'", elfFile); + + FILE *fp = popen(cmd, "r"); + if (!fp) return 0; + + char line[256]; + uint32_t address = 0; + + if (fgets(line, sizeof(line), fp)) { + /* Parse line like: "20004000 g O RW_KERNEL 000000a4 .hidden osRtxInfo" */ + char *endptr; + address = strtoul(line, &endptr, 16); + if (endptr == line) address = 0; /* Conversion failed */ + } + + pclose(fp); + return address; +} + + +/* Initialize RTOS tracking */ +struct rtosState *rtosDetectAndInit(struct SymbolSet *symbols, const char *requested_type, int options_telnetPort, uint32_t cpu_freq) +{ + if (!requested_type) return NULL; + + struct rtosState *rtos = calloc(1, sizeof(struct rtosState)); + if (!rtos) return NULL; + + rtos->cpu_freq = cpu_freq; + rtos->telnet_port = options_telnetPort; + + if (strcasecmp(requested_type, "rtx5") == 0 || + strcasecmp(requested_type, "rtxv5") == 0) { + rtos->type = RTOS_RTX5; + rtos->name = "RTX5"; + rtos->ops = rtx5GetOps(); + + if (rtos->ops && rtos->ops->init) { + if (rtos->ops->init(rtos, symbols) < 0) { + genericsReport(V_ERROR, "Failed to initialize RTX5" EOL); + free(rtos); + return NULL; + } + } + + /* Verify target match if telnet is configured */ + if (rtos->ops && rtos->ops->verify_target_match && options_telnetPort > 0) { + int verify_result = rtos->ops->verify_target_match(rtos, symbols); + if (verify_result == RTOS_VERIFY_MISMATCH) { + /* Only exit on real mismatch */ + free(rtos); + return NULL; + } else if (verify_result == RTOS_VERIFY_NO_CONNECTION) { + genericsReport(V_INFO, "RTOS verification pending - telnet not ready yet" EOL); + } + } + + /* Initialize timing for first thread detection */ + rtos->last_switch_time = genericsTimestampuS(); + + if (rtos->priv) { + struct rtx5_private *priv = (struct rtx5_private*)rtos->priv; + if (priv->thread_run_curr && options_telnetPort > 0) { + genericsReport(V_INFO, "Configuring DWT for address 0x%08X via telnet" EOL, priv->thread_run_curr); + rtosConfigureDWT(priv->thread_run_curr); + } else if (options_telnetPort <= 0) { + genericsReport(V_WARN, "Telnet not configured, DWT not auto-configured" EOL); + } + } + + rtos->enabled = true; + } else { + genericsReport(V_ERROR, "Unknown RTOS type: %s" EOL, requested_type); + free(rtos); + return NULL; + } + + return rtos; +} + +void rtosFree(struct rtosState *rtos) +{ + if (!rtos) return; + + struct rtosThread *thread, *tmp; + HASH_ITER(hh, rtos->threads, thread, tmp) { + HASH_DEL(rtos->threads, thread); + free(thread); + } + + if (rtos->ops && rtos->ops->cleanup) { + rtos->ops->cleanup(rtos); + } + + free(rtos); +} + +/* Lookup pointer as string in symbols */ +const char *rtosLookupPointerAsString(struct SymbolSet *symbols, uint32_t ptr_value) +{ + if (!ptr_value || !symbols) return NULL; + + struct nameEntry n; + if (SymbolLookup(symbols, ptr_value, &n)) { + const char *name = SymbolFunction(symbols, n.functionindex); + if (name && name[0] != '\0' && name[0] != '.') { + return name; + } + } + + return NULL; +} + +/* Lookup pointer as function in symbols */ +const char *rtosLookupPointerAsFunction(struct SymbolSet *symbols, uint32_t ptr_value) +{ + if (!symbols || !ptr_value || ptr_value == 0xFFFFFFFF) return NULL; + // Intentar con &~1 (limpiar Thumb) + const char *name = rtosLookupPointerAsString(symbols, ptr_value & ~1); + if (name) return name; + // Try with original address + name = rtosLookupPointerAsString(symbols, ptr_value); + if (name) return name; + // Try with address -1 (in case Thumb is at +1) + if (ptr_value > 0) { + name = rtosLookupPointerAsString(symbols, ptr_value - 1); + if (name) return name; + } + // Symbol not found + struct unresolvedFunc *uf; + HASH_FIND_INT(unresolvedFuncs, &ptr_value, uf); + if (!uf) { + uf = malloc(sizeof(struct unresolvedFunc)); + uf->addr = ptr_value; + HASH_ADD_INT(unresolvedFuncs, addr, uf); + genericsReport(V_WARN, "No symbol found for function at 0x%08X" EOL, ptr_value); + } + return "Unknown Function"; +} + +/* Resolve thread info from pointers */ +bool rtosResolveThreadInfo(struct rtosThread *thread, struct SymbolSet *symbols, + uint32_t name_ptr, uint32_t func_ptr) +{ + if (!thread || !symbols) return false; + + bool resolved = false; + + if (name_ptr && !thread->entry_func_name) { + const char *name = rtosLookupPointerAsString(symbols, name_ptr); + if (name) { + strncpy(thread->name, name, sizeof(thread->name) - 1); + thread->name[sizeof(thread->name) - 1] = '\0'; + resolved = true; + } + } + + if (func_ptr && !thread->entry_func_name) { + const char *func = rtosLookupPointerAsFunction(symbols, func_ptr); + if (func) { + thread->entry_func = func_ptr & ~1; + thread->entry_func_name = func; + resolved = true; + } + } + + return resolved; +} + +static const char* rtosGetThreadName(struct rtosState *rtos, uint32_t tcb_addr) +{ + if (!rtos || !tcb_addr) return "NULL"; + struct rtosThread *thread; + HASH_FIND_INT(rtos->threads, &tcb_addr, thread); + return thread ? thread->name : "UNKNOWN"; +} + +/* Handle DWT match with ITM timestamp */ +void rtosHandleDWTMatchWithTimestamp(struct rtosState *rtos, struct SymbolSet *symbols, + uint32_t comp_num, uint32_t address, uint32_t value, + uint64_t itm_timestamp, int options_telnetPort) +{ + if (!rtos || !rtos->enabled) return; + + /* ITM timestamps come from CYCCNT which is 32-bit, handle as 32-bit to detect wraparound */ + uint32_t current_cyccnt = (uint32_t)(itm_timestamp & 0xFFFFFFFF); + + struct rtosThread *thread; + HASH_FIND_INT(rtos->threads, &value, thread); + + if (!thread) { + thread = calloc(1, sizeof(struct rtosThread)); + if (!thread) return; + thread->tcb_addr = value; + HASH_ADD_INT(rtos->threads, tcb_addr, thread); + rtos->thread_count++; + + if (options_telnetPort > 0) { + int read_result = 0; + if (rtos->ops && rtos->ops->read_thread_info) { + read_result = rtos->ops->read_thread_info(rtos, symbols, thread, value); + } else { + strcpy(thread->name, "UNKNOWN"); + thread->priority = 0; + } + + if (read_result < 0) { + genericsReport(V_DEBUG, "Failed to read thread info for TCB=0x%08X - removing from tracking\n", value); + HASH_DEL(rtos->threads, thread); + free(thread); + return; + } else if (read_result > 0) { + genericsReport(V_INFO, "Thread reuse detected for TCB=0x%08X - clearing cache\n", value); + rtosClearMemoryCacheForTCB(value); + } + + genericsReport(V_INFO, "New thread detected: TCB=0x%08X, Name='%s', Func=0x%08X/%s, Prio=%d\n", + thread->tcb_addr, thread->name, thread->entry_func, + thread->entry_func_name ? thread->entry_func_name : "-", thread->priority); + } else { + strcpy(thread->name, "UNNAMED"); + thread->priority = 0; + } + } + + if (rtos->current_thread) { + struct rtosThread *prev_thread; + uint32_t prev_tcb = rtos->current_thread; + HASH_FIND_INT(rtos->threads, &prev_tcb, prev_thread); + if (prev_thread && rtos->last_cyccnt != 0) { + /* Calculate delta handling 32-bit wraparound */ + uint32_t delta_cycles; + if (current_cyccnt >= rtos->last_cyccnt) { + /* Normal case: no wraparound */ + delta_cycles = current_cyccnt - rtos->last_cyccnt; + } else { + /* Wraparound occurred */ + delta_cycles = (0xFFFFFFFF - rtos->last_cyccnt) + current_cyccnt + 1; + } + + if (delta_cycles > 0 && delta_cycles < 0x80000000) { + uint32_t delta_time_us = rtos->cpu_freq > 0 ? delta_cycles / (rtos->cpu_freq / 1000000) : 0; + + if (delta_time_us > 10000) { + genericsReport(V_INFO, "Long timeslice: %u us (%u cycles) for TCB=0x%08X (%s)\n", + delta_time_us, delta_cycles, prev_thread->tcb_addr, prev_thread->name); + } + + prev_thread->accumulated_time_us += delta_time_us; + prev_thread->accumulated_cycles += delta_cycles; + genericsReport(V_DEBUG, "Thread TCB=0x%08X ran for %u us, total=%" PRIu64 " us\n", + prev_thread->tcb_addr, delta_time_us, prev_thread->accumulated_time_us); + } + } + } + + thread->last_scheduled_us = current_cyccnt; /* Store current CYCCNT value */ + + if (rtos->current_thread != value) { + /* Only count as a context switch if it's actually a different thread */ + thread->context_switches++; + thread->window_switches++; + + struct rtosThread *prev_thread = NULL; + if (rtos->current_thread) { + HASH_FIND_INT(rtos->threads, &rtos->current_thread, prev_thread); + } + + genericsReport(V_DEBUG, "Context switch: 0x%08X (%s) → 0x%08X (%s)\n", + rtos->current_thread, + rtos->current_thread ? rtosGetThreadName(rtos, rtos->current_thread) : "NULL", + thread->tcb_addr, thread->name); + + if (rtos->output_config) { + genericsReport(V_DEBUG, "Calling output_thread_switch with output_config=%p\n", rtos->output_config); + output_thread_switch((OutputConfig *)rtos->output_config, prev_thread, thread, itm_timestamp); + } else { + genericsReport(V_DEBUG, "No output_config set for RTOS, skipping thread_switch output\n"); + } + + rtos->current_thread = value; + /* Update last CYCCNT for the new thread */ + rtos->last_cyccnt = current_cyccnt; + } else if (current_cyccnt != rtos->last_cyccnt) { + /* Same thread but new timestamp - update for accurate timing */ + rtos->last_cyccnt = current_cyccnt; + } +} + +/* Handle DWT match - legacy function that uses system time */ +void rtosHandleDWTMatch(struct rtosState *rtos, struct SymbolSet *symbols, + uint32_t comp_num, uint32_t address, uint32_t value, int options_telnetPort) +{ + /* Handle thread switch from DWT watchpoint */ + if (!rtos || !rtos->enabled) return; + + /* Get current timestamp in microseconds */ + uint64_t current_time_us = genericsTimestampuS(); + + /* Value is the thread TCB address that just became active */ + struct rtosThread *thread; + HASH_FIND_INT(rtos->threads, &value, thread); + + if (!thread) { + /* New thread - create and read memory info ONLY ONCE */ + thread = calloc(1, sizeof(struct rtosThread)); + if (!thread) return; + thread->tcb_addr = value; + HASH_ADD_INT(rtos->threads, tcb_addr, thread); + rtos->thread_count++; + + /* ONLY read memory for NEW threads */ + if (options_telnetPort > 0) { + /* Delegate to specific RTOS to read thread details */ + int read_result = 0; + if (rtos->ops && rtos->ops->read_thread_info) { + read_result = rtos->ops->read_thread_info(rtos, symbols, thread, value); + } else { + /* Generic fallback if no specific handler */ + strcpy(thread->name, "UNKNOWN"); + thread->priority = 0; + } + + /* Check read result */ + if (read_result < 0) { + /* Read failed - remove the thread from hash as it's invalid */ + genericsReport(V_WARN, "Failed to read thread info for TCB=0x%08X - removing from tracking\n", value); + HASH_DEL(rtos->threads, thread); + free(thread); + return; + } else if (read_result > 0) { + /* Thread was reused - clear cache for this TCB */ + genericsReport(V_INFO, "Thread reuse detected for TCB=0x%08X - clearing cache\n", value); + rtosClearMemoryCacheForTCB(value); + } + + genericsReport(V_INFO, "New thread detected: TCB=0x%08X, Name='%s', Func=0x%08X/%s, Prio=%d\n", + thread->tcb_addr, thread->name, thread->entry_func, + thread->entry_func_name ? thread->entry_func_name : "-", thread->priority); + } else { + strcpy(thread->name, "UNNAMED"); + thread->priority = 0; + } + } + + if (rtos->current_thread && rtos->last_switch_time > 0) { + struct rtosThread *prev_thread; + uint32_t prev_tcb = rtos->current_thread; + HASH_FIND_INT(rtos->threads, &prev_tcb, prev_thread); + if (prev_thread) { + uint64_t delta_time_us = current_time_us - rtos->last_switch_time; + + if (delta_time_us > 10000) { + genericsReport(V_INFO, "Long timeslice: %" PRIu64 " us for TCB=0x%08X (%s)\n", + delta_time_us, prev_thread->tcb_addr, prev_thread->name); + } + + prev_thread->accumulated_time_us += delta_time_us; + genericsReport(V_DEBUG, "Thread TCB=0x%08X ran for %" PRIu64 " us, total=%" PRIu64 " us\n", + prev_thread->tcb_addr, delta_time_us, prev_thread->accumulated_time_us); + } + } + + thread->last_scheduled_us = current_time_us; + thread->context_switches++; + thread->window_switches++; + + if (rtos->current_thread != value) { + struct rtosThread *prev_thread = NULL; + if (rtos->current_thread) { + HASH_FIND_INT(rtos->threads, &rtos->current_thread, prev_thread); + } + + genericsReport(V_DEBUG, "Context switch: 0x%08X (%s) → 0x%08X (%s)\n", + rtos->current_thread, + rtos->current_thread ? rtosGetThreadName(rtos, rtos->current_thread) : "NULL", + thread->tcb_addr, thread->name); + + if (rtos->output_config) { + output_thread_switch((OutputConfig *)rtos->output_config, prev_thread, thread, current_time_us); + } + } + + rtos->current_thread = value; + rtos->last_switch_time = current_time_us; +} + +/* External sort method selection */ + +/* Helper structure for column widths */ +struct ColumnWidths { + int name; + int address; /* Fixed at 10 for 0xXXXXXXXX */ + int function; + int priority; + int time; + int cpu; + int max; + int switches; +}; + +/* Print horizontal separator line for table */ +static void printTableSeparator(FILE *f, struct ColumnWidths *widths) +{ + fprintf(f, "|"); + for (int i = 0; i < widths->name + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->address + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->function + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->priority + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->time + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->cpu + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->max + 2; i++) fprintf(f, "-"); + fprintf(f, "|"); + for (int i = 0; i < widths->switches + 2; i++) fprintf(f, "-"); + fprintf(f, "|\n"); +} + +/* Get function string for a thread */ +static void getThreadFunctionString(struct rtosThread *thread, char *buf, size_t bufsize) +{ + if (thread->entry_func_name && thread->entry_func) { + snprintf(buf, bufsize, "%s", thread->entry_func_name); + } else if (thread->entry_func && thread->entry_func != 0xFFFFFFFF) { + snprintf(buf, bufsize, "0x%08X", thread->entry_func); + } else { + strcpy(buf, "-"); + } +} + +/* Calculate column widths based on thread data */ +static void calculateColumnWidths(struct rtosState *rtos, struct ColumnWidths *widths) +{ + struct rtosThread *thread, *tmp; + + /* Initialize with header widths */ + widths->name = strlen("Thread Name"); + widths->address = 10; /* Fixed for 0xXXXXXXXX format */ + widths->function = strlen("Function"); + widths->priority = strlen("Priority"); + widths->time = strlen("Time(ms)"); + widths->cpu = 7; /* Fixed width for XXX.XXX format */ + widths->max = 7; /* Fixed width for XXX.XXX format */ + widths->switches = strlen("Switches"); + + /* Calculate actual maximum widths from thread data */ + HASH_ITER(hh, rtos->threads, thread, tmp) { + /* Thread name */ + int len = strlen(thread->name); + if (len > widths->name) widths->name = len; + + /* Function name */ + char func_str[64]; + getThreadFunctionString(thread, func_str, sizeof(func_str)); + len = strlen(func_str); + if (len > widths->function) widths->function = len; + + /* Priority name */ + const char *pri_name = rtx5GetPriorityName(thread->priority); + len = strlen(pri_name); + if (len > widths->priority) widths->priority = len; + + /* Time */ + char time_str[32]; + snprintf(time_str, sizeof(time_str), "%llu", (unsigned long long)(thread->accumulated_time_us / 1000)); + len = strlen(time_str); + if (len > widths->time) widths->time = len; + + /* Switches */ + char switches_str[32]; + snprintf(switches_str, sizeof(switches_str), "%llu", (unsigned long long)thread->window_switches); + len = strlen(switches_str); + if (len > widths->switches) widths->switches = len; + } + + /* Add padding for readability */ + widths->name += 2; + widths->function += 2; + widths->priority += 2; + widths->time += 2; + widths->switches += 2; +} + +/* Print table header */ +static void printTableHeader(FILE *f, struct ColumnWidths *widths) +{ + /* Print top separator first */ + printTableSeparator(f, widths); + + /* Then print column headers */ + fprintf(f, "| %-*s | %-*s | %-*s | %-*s | %*s | %*s | %*s | %*s |\n", + widths->name, "Thread Name", + widths->address, "Address", + widths->function, "Function", + widths->priority, "Priority", + widths->time, "Time(ms)", + widths->cpu, "CPU%", + widths->max, "Max%", + widths->switches, "Switches"); +} + +/* Print a single thread row */ +static void printThreadRow(FILE *f, struct ColumnWidths *widths, struct rtosThread *thread, + struct rtosState *rtos, uint64_t window_time_us) +{ + /* Calculate CPU percentage - use cycles if available, otherwise time */ + uint32_t pct = 0; + if (window_time_us > 0) { + if (thread->accumulated_cycles > 0 && rtos->total_cycles > 0) { + /* Calculate from cycles */ + pct = (thread->accumulated_cycles * 10000) / rtos->total_cycles; + } else if (thread->accumulated_time_us > 0) { + /* Fallback to time-based calculation */ + pct = (thread->accumulated_time_us * 10000) / window_time_us; + } + if (pct > 10000) pct = 10000; /* Cap at 100% */ + } + + /* Update maximum if needed */ + if (pct > thread->max_cpu_percent) { + thread->max_cpu_percent = pct; + } + + /* Get thread info strings */ + char func_str[64]; + getThreadFunctionString(thread, func_str, sizeof(func_str)); + const char *pri_name = rtx5GetPriorityName(thread->priority); + + if (rtos->cpu_freq == 0) { + fprintf(f, "| %-*s | 0x%08X | %-*s | %-*s | %*s | %*.3f | %*.3f | %*" PRIu64 " |\n", + widths->name, thread->name, + thread->tcb_addr, + widths->function, func_str, + widths->priority, pri_name, + widths->time, "NA", + widths->cpu, pct / 100.0, + widths->max, thread->max_cpu_percent / 100.0, + widths->switches, thread->window_switches); + } else { + uint64_t time_ms = (thread->accumulated_cycles * 1000) / rtos->cpu_freq; + fprintf(f, "| %-*s | 0x%08X | %-*s | %-*s | %*" PRIu64 " | %*.3f | %*.3f | %*" PRIu64 " |\n", + widths->name, thread->name, + thread->tcb_addr, + widths->function, func_str, + widths->priority, pri_name, + widths->time, time_ms, + widths->cpu, pct / 100.0, + widths->max, thread->max_cpu_percent / 100.0, + widths->switches, thread->window_switches); + } +} + + +/* Dump thread info */ +void rtosDumpThreadInfo(struct rtosState *rtos, FILE *f, uint64_t window_time_us, bool itm_overflow, const char *sort_order) +{ + if (!rtos || !rtos->threads || !f) return; + + struct rtosThread *thread, *tmp; + struct ColumnWidths widths; + + /* Check if RTOS has idle concept */ + bool has_idle_concept = (rtos->ops && rtos->ops->is_idle_thread); + + /* Calculate dynamic column widths */ + calculateColumnWidths(rtos, &widths); + + /* Print header */ + fprintf(f, "\n=== RTOS Thread Statistics (%s) ===\n", rtos->name); + + /* Print table header */ + printTableHeader(f, &widths); + + /* Print separator line */ + printTableSeparator(f, &widths); + + /* FIRST: Account for the currently running thread's time since last switch */ + /* NOTE: Skip this when using ITM timestamps as they're already accounted for in rtosHandleDWTMatchWithTimestamp */ + if (rtos->current_thread && rtos->last_switch_time > 0 && rtos->last_cyccnt == 0) { + /* Only do final accounting if we're NOT using ITM timestamps (last_cyccnt would be > 0 if using ITM) */ + struct rtosThread *current; + HASH_FIND_INT(rtos->threads, &rtos->current_thread, current); + if (current) { + uint64_t current_time_us = genericsTimestampuS(); + uint64_t delta_time_us = current_time_us - rtos->last_switch_time; + current->accumulated_time_us += delta_time_us; + genericsReport(V_DEBUG, "Final accounting: Thread TCB=0x%08X ran for %" PRIu64 " us in window tail\n", + current->tcb_addr, delta_time_us); + /* Update timestamp so we don't double-count in next window */ + rtos->last_switch_time = current_time_us; + } + } + + uint64_t total_time_us = 0; + + /* Calculate total accumulated time (should equal window_time_us approximately) */ + HASH_ITER(hh, rtos->threads, thread, tmp) { + total_time_us += thread->accumulated_time_us; + } + + /* If no time recorded yet, use window time for calculations */ + if (total_time_us == 0) { + total_time_us = window_time_us; + } + + /* Sort threads based on selected method - update cpu_usage_sort to use accumulated_time_us */ + if (!sort_order || strcmp(sort_order, "cpu") == 0) { + HASH_SORT(rtos->threads, cpu_usage_sort_desc); + } else if (strcmp(sort_order, "maxcpu") == 0) { + HASH_SORT(rtos->threads, max_cpu_sort_desc); + } else if (strcmp(sort_order, "tcb") == 0) { + HASH_SORT(rtos->threads, tcb_addr_sort_asc); + } else if (strcmp(sort_order, "name") == 0) { + HASH_SORT(rtos->threads, name_sort_asc); + } else if (strcmp(sort_order, "func") == 0) { + HASH_SORT(rtos->threads, func_sort_asc); + } else if (strcmp(sort_order, "priority") == 0) { + HASH_SORT(rtos->threads, priority_sort_desc); + } else if (strcmp(sort_order, "switches") == 0) { + HASH_SORT(rtos->threads, switches_sort_desc); + } else { + /* Default to CPU usage */ + HASH_SORT(rtos->threads, cpu_usage_sort_desc); + } + + /* Track idle thread separately */ + struct rtosThread *idle_thread = NULL; + + /* Display each thread (except idle) */ + HASH_ITER(hh, rtos->threads, thread, tmp) { + /* Skip invalid TCBs */ + if (thread->tcb_addr == 0x00000000 || thread->tcb_addr == 0xFFFFFFFF) { + genericsReport(V_DEBUG, "Skipping invalid TCB: 0x%08X\n", thread->tcb_addr); + continue; + } + + /* Skip idle thread if RTOS can identify it - save for later */ + if (has_idle_concept && rtos->ops->is_idle_thread(thread)) { + idle_thread = thread; + continue; + } + + /* Print regular thread row */ + printThreadRow(f, &widths, thread, rtos, window_time_us); + } + + /* Print separator before idle thread if present */ + if (idle_thread) { + printTableSeparator(f, &widths); + printThreadRow(f, &widths, idle_thread, rtos, window_time_us); + } + + /* Calculate CPU percentages */ + uint64_t total_accum_us = 0; + uint64_t active_accum_us = 0; + uint64_t total_cycles = 0; + uint64_t active_cycles = 0; + + HASH_ITER(hh, rtos->threads, thread, tmp) { + total_accum_us += thread->accumulated_time_us; + total_cycles += thread->accumulated_cycles; + + /* If RTOS can identify idle threads, calculate active CPU separately */ + if (has_idle_concept && !rtos->ops->is_idle_thread(thread)) { + active_accum_us += thread->accumulated_time_us; + active_cycles += thread->accumulated_cycles; + } + } + + /* Store total cycles for percentage calculation */ + rtos->total_cycles = total_cycles; + + if (window_time_us > 0) { + uint32_t display_pct; + printTableSeparator(f, &widths); + + if (has_idle_concept) { + /* Show active CPU (non-idle threads only) */ + display_pct = (active_accum_us * 10000) / window_time_us; + + /* Update max CPU usage if needed */ + if (display_pct > rtos->max_cpu_usage) { + rtos->max_cpu_usage = display_pct; + } + + fprintf(f, "Interval: %" PRIu64 " ms, CPU Usage: %.3f%%, Max: %.3f%%, CPU Freq: ", + window_time_us/1000, display_pct / 100.0, rtos->max_cpu_usage / 100.0); + if (rtos->cpu_freq > 0) { + fprintf(f, "%uHz", rtos->cpu_freq); + } else { + fprintf(f, "NA"); + } + } else { + /* Show total for RTOS without idle concept */ + display_pct = (total_accum_us * 10000) / window_time_us; + fprintf(f, "Window: %" PRIu64 " ms, Total CPU: %.3f%%", + window_time_us/1000, display_pct / 100.0); + } + + /* Show warning based on ITM overflow or percentage */ + /* For warnings, always use total (should be ~100%) */ + uint32_t total_pct = (total_accum_us * 10000) / window_time_us; + if (itm_overflow) { + fprintf(f, " [ITM OVERFLOW DETECTED!]"); + } else if (total_pct < 9500) { + fprintf(f, " [WARNING: Low total - possible lost DWT events]"); + } else if (total_pct > 10500) { + fprintf(f, " [WARNING: High total - timing issue?]"); + } + fprintf(f, "\n"); + } +} + +void rtosUpdateThreadCpuMetrics(struct rtosState *rtos, uint64_t window_time_us) +{ + if (!rtos || !rtos->enabled || !rtos->threads || window_time_us == 0) + return; + + struct rtosThread *thread, *tmp; + uint64_t active_accum_us = 0; + uint64_t total_accum_us = 0; + bool has_idle_concept = false; + + /* Calculate CPU percentages and update max values for all threads */ + HASH_ITER(hh, rtos->threads, thread, tmp) + { + /* Calculate CPU percentage with proper scaling to avoid overflow */ + uint32_t cpu_pct = 0; + if (window_time_us > 0) + { + uint64_t temp = (uint64_t)thread->accumulated_time_us * 10000ULL; + cpu_pct = (uint32_t)(temp / window_time_us); + if (cpu_pct > 10000) cpu_pct = 10000; /* Cap at 100% */ + } + + /* Update max CPU percentage */ + if (cpu_pct > thread->max_cpu_percent) + { + thread->max_cpu_percent = cpu_pct; + } + + /* Track totals for overall CPU usage calculation */ + total_accum_us += thread->accumulated_time_us; + + /* Check if this is an idle thread using RTOS-specific method */ + bool is_idle = false; + if (rtos->ops && rtos->ops->is_idle_thread) + { + is_idle = rtos->ops->is_idle_thread(thread); + } + + if (!is_idle) + { + active_accum_us += thread->accumulated_time_us; + } + else + { + has_idle_concept = true; + } + } + + /* Update overall CPU usage max if we have idle thread concept */ + if (has_idle_concept) + { + uint64_t temp = (uint64_t)active_accum_us * 10000ULL; + uint32_t cpu_usage_pct = (uint32_t)(temp / window_time_us); + if (cpu_usage_pct > 10000) cpu_usage_pct = 10000; + + if (cpu_usage_pct > rtos->max_cpu_usage) + { + rtos->max_cpu_usage = cpu_usage_pct; + } + } +} + +void rtosResetThreadCounters(struct rtosState *rtos) +{ + if (!rtos || !rtos->enabled || !rtos->threads) + return; + + struct rtosThread *thread, *tmp; + HASH_ITER(hh, rtos->threads, thread, tmp) + { + thread->accumulated_time_us = 0; + thread->accumulated_cycles = 0; + thread->window_switches = 0; + } +} \ No newline at end of file diff --git a/Src/rtos/rtx5/rtx5.c b/Src/rtos/rtx5/rtx5.c new file mode 100644 index 00000000..6d27e8a6 --- /dev/null +++ b/Src/rtos/rtx5/rtx5.c @@ -0,0 +1,409 @@ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static const char *rtx5_priority_names[] = { + "osPriorityNone", + "osPriorityIdle", + "osPriorityReserved2", + "osPriorityReserved3", + "osPriorityReserved4", + "osPriorityReserved5", + "osPriorityReserved6", + "osPriorityReserved7", + "osPriorityLow", + "osPriorityLow1", + "osPriorityLow2", + "osPriorityLow3", + "osPriorityLow4", + "osPriorityLow5", + "osPriorityLow6", + "osPriorityLow7", + "osPriorityBelowNormal", + "osPriorityBelowNormal1", + "osPriorityBelowNormal2", + "osPriorityBelowNormal3", + "osPriorityBelowNormal4", + "osPriorityBelowNormal5", + "osPriorityBelowNormal6", + "osPriorityBelowNormal7", + "osPriorityNormal", + "osPriorityNormal1", + "osPriorityNormal2", + "osPriorityNormal3", + "osPriorityNormal4", + "osPriorityNormal5", + "osPriorityNormal6", + "osPriorityNormal7", + "osPriorityAboveNormal", + "osPriorityAboveNormal1", + "osPriorityAboveNormal2", + "osPriorityAboveNormal3", + "osPriorityAboveNormal4", + "osPriorityAboveNormal5", + "osPriorityAboveNormal6", + "osPriorityAboveNormal7", + "osPriorityHigh", + "osPriorityHigh1", + "osPriorityHigh2", + "osPriorityHigh3", + "osPriorityHigh4", + "osPriorityHigh5", + "osPriorityHigh6", + "osPriorityHigh7", + "osPriorityRealtime", + "osPriorityRealtime1", + "osPriorityRealtime2", + "osPriorityRealtime3", + "osPriorityRealtime4", + "osPriorityRealtime5", + "osPriorityRealtime6", + "osPriorityRealtime7", + "osPriorityISR" +}; + +static uint32_t simple_hash(const char *str) +{ + uint32_t hash = 5381; + if (!str) + { + return 0; + } + while (*str) + { + hash = ((hash << 5) + hash) + *str++; + } + return hash; +} + +const char *rtx5GetPriorityName(int8_t priority) +{ + if (priority >= 0 && priority <= 56) + { + return rtx5_priority_names[priority]; + } + else if (priority == -1) + { + return "osPriorityError"; + } + else + { + return "osPriorityUnknown"; + } +} + + +struct rtx5_private +{ + uint32_t osRtxInfo; + uint32_t thread_run_curr; + uint32_t pendSV_Handler; + uint32_t osRtxThreadListPut; +}; + + +static int rtx5_read_thread_info(struct rtosState *rtos, + struct SymbolSet *symbols, + struct rtosThread *thread, + uint32_t tcb_addr) +{ + if (!rtos || !thread || !tcb_addr) + { + genericsReport(V_ERROR, "rtx5_read_thread_info: Invalid parameters\n"); + return -1; + } + + if (tcb_addr == 0x00000000 || tcb_addr == 0xFFFFFFFF) + { + genericsReport(V_WARN, "rtx5_read_thread_info: Invalid TCB address 0x%08X\n", tcb_addr); + strcpy(thread->name, "INVALID"); + return -1; + } + + uint32_t id_word = rtosReadMemoryWord(tcb_addr + RTX5_THREAD_ID_OFFSET); + uint8_t thread_id = (uint8_t)(id_word & 0xFF); + if (thread_id != RTX5_ID_THREAD) + { + genericsReport(V_DEBUG, "RTX5: Not a thread at TCB=0x%08X - ID=0x%02X (expected 0xF1)\n", + tcb_addr, thread_id); + return -1; + } + + uint32_t name_ptr = rtosReadMemoryWord(tcb_addr + RTX5_THREAD_NAME_OFFSET); + thread->name_ptr = name_ptr; + + uint32_t old_name_hash = thread->name_hash; + uint32_t old_func_hash = thread->func_hash; + + if (name_ptr && name_ptr != 0xFFFFFFFF) + { + char name_buf[64] = {0}; + char *name_str = rtosReadMemoryString(name_ptr, name_buf, sizeof(name_buf)); + if (name_str && name_buf[0] != 0) + { + strncpy(thread->name, name_buf, sizeof(thread->name) - 1); + thread->name[sizeof(thread->name) - 1] = '\0'; + + if (strstr(thread->name, "_inq") || strstr(thread->name, "_timer")) + { + genericsReport(V_DEBUG, "RTX5: TCB=0x%08X has name='%s' from ptr=0x%08X\n", + tcb_addr, thread->name, name_ptr); + } + } + else + { + strcpy(thread->name, "UNNAMED"); + } + } + else + { + strcpy(thread->name, "UNNAMED"); + } + + uint32_t thread_func = rtosReadMemoryWord(tcb_addr + RTX5_THREAD_THREAD_ADDR_OFFSET); + thread->entry_func = thread_func & ~1; + + if (!thread_func || thread_func == 0xFFFFFFFF) + { + genericsReport(V_WARN, "RTX5: Invalid thread data at TCB=0x%08X - function is NULL/invalid (0x%08X)\n", + tcb_addr, thread_func); + strcpy(thread->name, "INVALID_READ"); + thread->priority = -1; + return -1; + } + + if (symbols) + { + thread->entry_func_name = rtosLookupPointerAsFunction(symbols, thread_func); + } + else + { + thread->entry_func_name = NULL; + } + + uint32_t priority_word = rtosReadMemoryWord(tcb_addr + RTX5_THREAD_PRIORITY_OFFSET); + thread->priority = (int8_t)(priority_word & 0xFF); + + if (thread->priority < -3 || thread->priority > 56) + { + genericsReport(V_WARN, "RTX5: Suspicious priority %d at TCB=0x%08X (raw=0x%08X), name='%s', func=0x%08X\n", + thread->priority, tcb_addr, priority_word, thread->name, thread_func); + } + + thread->name_hash = simple_hash(thread->name); + thread->func_hash = simple_hash(thread->entry_func_name); + + if (old_name_hash != 0 && old_func_hash != 0) + { + if (old_name_hash != thread->name_hash && old_func_hash != thread->func_hash) + { + if (strcmp(thread->name, "UNNAMED") != 0 || thread->entry_func != 0) + { + genericsReport(V_INFO, "Thread REUSED detected: TCB=0x%08X, resetting statistics\n", tcb_addr); + thread->accumulated_time_us = 0; + thread->context_switches = 0; + thread->max_cpu_percent = 0; + return 1; + } + } + } + + genericsReport(V_INFO, "RTX5 Thread: TCB=0x%08X, Name='%s', Func=0x%08X/%s, Priority=%d\n", + tcb_addr, thread->name, thread->entry_func, + thread->entry_func_name ? thread->entry_func_name : "-", + thread->priority); + + return 0; +} + +static bool rtx5_detect(struct SymbolSet *symbols, struct rtosDetection *result) +{ + if (!result) + { + return false; + } + + result->type = RTOS_RTX5; + result->name = "RTX5"; + result->confidence = 90; + result->reason = "RTX5 selected by user"; + return true; +} + +static int rtx5_init(struct rtosState *rtos, struct SymbolSet *symbols) +{ + if (!rtos) + { + return -1; + } + + struct rtx5_private *priv = calloc(1, sizeof(struct rtx5_private)); + if (!priv) + { + return -1; + } + + rtos->priv = priv; + + if (symbols && symbols->elfFile) + { + char cmd[512]; + FILE *fp; + char line[256]; + + snprintf(cmd, sizeof(cmd), "arm-none-eabi-objdump -t %s 2>/dev/null | grep 'osRtxInfo$'", symbols->elfFile); + fp = popen(cmd, "r"); + if (fp && fgets(line, sizeof(line), fp)) + { + priv->osRtxInfo = strtoul(line, NULL, 16); + } + if (fp) + { + pclose(fp); + } + + if (priv->osRtxInfo == 0) + { + genericsReport(V_ERROR, "osRtxInfo symbol not found in ELF!" EOL); + free(priv); + rtos->priv = NULL; + return -1; + } + + priv->thread_run_curr = priv->osRtxInfo + RTX5_INFO_THREAD_RUN_CURR_OFFSET; + } + + return 0; +} + +static void rtx5_cleanup(struct rtosState *rtos) +{ + if (rtos && rtos->priv) + { + free(rtos->priv); + rtos->priv = NULL; + } +} + +static const char* rtx5_get_state_name(uint8_t state) +{ + switch (state) + { + case RTX5_THREAD_INACTIVE: return "Inactive"; + case RTX5_THREAD_READY: return "Ready"; + case RTX5_THREAD_RUNNING: return "Running"; + case RTX5_THREAD_BLOCKED: return "Blocked"; + case RTX5_THREAD_TERMINATED: return "Terminated"; + default: return "Unknown"; + } +} + +static bool rtx5_is_idle_thread(struct rtosThread *thread) +{ + if (!thread) + { + return false; + } + + if (thread->entry_func_name && + strcmp(thread->entry_func_name, "osRtxIdleThread") == 0) + { + return true; + } + + if (thread->priority == -3) + { + return true; + } + + return false; +} + +static int rtx5_verify_target_match(struct rtosState *rtos, struct SymbolSet *symbols) +{ + if (!rtos || !rtos->priv) + { + return RTOS_VERIFY_ERROR; + } + + struct rtx5_private *priv = (struct rtx5_private *)rtos->priv; + + if (rtos->telnet_port <= 0) + { + genericsReport(V_DEBUG, "RTX5: Cannot verify target match - telnet not configured" EOL); + return RTOS_VERIFY_SUCCESS; + } + + if (!telnet_is_connected()) + { + if (telnet_connect(4444) < 0) + { + genericsReport(V_INFO, "RTX5: Telnet not available - will verify when connection is established" EOL); + return RTOS_VERIFY_NO_CONNECTION; + } + } + + uint32_t os_id_ptr = rtosReadMemoryWord(priv->osRtxInfo + RTX5_INFO_OS_ID_OFFSET); + if (!os_id_ptr || os_id_ptr == 0xFFFFFFFF) + { + genericsReport(V_ERROR, "RTX5: Cannot read os_id pointer from osRtxInfo at 0x%08X" EOL, priv->osRtxInfo); + genericsReport(V_ERROR, "Target Connected Mismatch with ELF" EOL); + return RTOS_VERIFY_MISMATCH; + } + + char version_buf[64] = {0}; + char *version_str = rtosReadMemoryString(os_id_ptr, version_buf, sizeof(version_buf)); + + if (!version_str || version_buf[0] == 0) + { + genericsReport(V_WARN, "RTX5: Cannot read version string from target at 0x%08X" EOL, os_id_ptr); + return RTOS_VERIFY_NO_CONNECTION; + } + + if (!strstr(version_buf, "RTX")) + { + genericsReport(V_ERROR, "Target Connected Mismatch with ELF" EOL); + genericsReport(V_ERROR, "Expected RTX version but got: '%s'" EOL, version_buf); + return RTOS_VERIFY_MISMATCH; + } + + genericsReport(V_INFO, "RTX5: Target verified - Version: %s" EOL, version_buf); + + uint32_t kernel_state = rtosReadMemoryWord(priv->osRtxInfo + RTX5_INFO_KERNEL_OFFSET); + if (kernel_state == 0 || kernel_state == 0xFFFFFFFF) + { + genericsReport(V_WARN, "RTX5: Kernel state invalid (0x%08X) - possible target mismatch" EOL, kernel_state); + } + + return RTOS_VERIFY_SUCCESS; +} + +static const struct rtosOps rtx5_ops = +{ + .read_thread_info = rtx5_read_thread_info, + .get_priority_name = rtx5GetPriorityName, + .detect = rtx5_detect, + .init = rtx5_init, + .cleanup = rtx5_cleanup, + .get_state_name = rtx5_get_state_name, + .is_idle_thread = rtx5_is_idle_thread, + .verify_target_match = rtx5_verify_target_match +}; + +void rtosRegisterRTX5(void) +{ +} + +const struct rtosOps *rtx5GetOps(void) +{ + return &rtx5_ops; +} \ No newline at end of file diff --git a/Src/rtos/telnet_client.c b/Src/rtos/telnet_client.c new file mode 100644 index 00000000..4e20e1e3 --- /dev/null +++ b/Src/rtos/telnet_client.c @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int _telnetSocket = -1; + +struct memCache { + uint32_t addr; + uint32_t value; + uint64_t timestamp; + UT_hash_handle hh; +}; +static struct memCache *_memCache = NULL; + +static void _drainSocket(int socket) +{ + char drain[256]; + struct timeval tv = { 0, 10000 }; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(socket, &readfds); + + while (select(socket + 1, &readfds, NULL, NULL, &tv) > 0) + { + if (recv(socket, drain, sizeof(drain), MSG_DONTWAIT) <= 0) + break; + FD_ZERO(&readfds); + FD_SET(socket, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 10000; + } +} + +static int _readLine(int socket, char *buffer, int maxlen, int timeout_ms) +{ + int pos = 0; + fd_set readfds; + struct timeval timeout; + + while (pos < maxlen - 1) + { + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + FD_ZERO(&readfds); + FD_SET(socket, &readfds); + + if (select(socket + 1, &readfds, NULL, NULL, &timeout) <= 0) + break; + + char c; + int len = recv(socket, &c, 1, 0); + if (len <= 0) + break; + + if (c == '\0') + continue; + if ((unsigned char)c == 0xFF) + { + recv(socket, &c, 1, 0); + recv(socket, &c, 1, 0); + continue; + } + + buffer[pos++] = c; + + if (c == '\n') + { + if (pos > 1 && buffer[pos-2] == '\r') + { + buffer[pos-2] = '\n'; + pos--; + } + buffer[pos] = '\0'; + return pos; + } + } + + buffer[pos] = '\0'; + return pos; +} + +int telnet_connect(int port) +{ + if (_telnetSocket >= 0) + return _telnetSocket; + + _telnetSocket = socket(AF_INET, SOCK_STREAM, 0); + if (_telnetSocket < 0) + { + genericsReport(V_DEBUG, "Failed to create socket\n"); + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (connect(_telnetSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + genericsReport(V_DEBUG, "Failed to connect to telnet port %d\n", port); + close(_telnetSocket); + _telnetSocket = -1; + return -1; + } + + char buffer[1024]; + recv(_telnetSocket, buffer, sizeof(buffer), MSG_DONTWAIT); + + unsigned char no_echo[] = { 255, 251, 1 }; + send(_telnetSocket, no_echo, sizeof(no_echo), 0); + + return _telnetSocket; +} + +void telnet_disconnect(void) +{ + if (_telnetSocket >= 0) + { + close(_telnetSocket); + _telnetSocket = -1; + } + + struct memCache *cached, *tmp; + HASH_ITER(hh, _memCache, cached, tmp) + { + HASH_DEL(_memCache, cached); + free(cached); + } + _memCache = NULL; +} + +bool telnet_is_connected(void) +{ + return _telnetSocket >= 0; +} + +uint32_t telnet_read_memory_word(uint32_t address) +{ + struct memCache *cached; + HASH_FIND_INT(_memCache, &address, cached); + if (cached) { + return cached->value; + } + + if (telnet_connect(4444) < 0) + return 0; + + char cmd[64]; + snprintf(cmd, sizeof(cmd), "mdw 0x%08x 1\n", address); + + if (send(_telnetSocket, cmd, strlen(cmd), 0) < 0) { + fprintf(stderr, "Failed to send telnet command\n"); + telnet_disconnect(); + return 0; + } + + char line[256]; + uint32_t value = 0; + int found = 0; + int lines_read = 0; + + while (lines_read < 5) { + int len = _readLine(_telnetSocket, line, sizeof(line), 500); + if (len <= 0) + break; + + lines_read++; + + if (strstr(line, "mdw")) + continue; + + uint32_t addr, val; + if (sscanf(line, "0x%x: %x", &addr, &val) == 2) { + if (addr == address) { + value = val; + found = 1; + break; + } + } + + if (strstr(line, "> ")) + break; + } + + if (found) { + cached = malloc(sizeof(struct memCache)); + if (cached) { + cached->addr = address; + cached->value = value; + cached->timestamp = genericsTimestampuS(); + HASH_ADD_INT(_memCache, addr, cached); + } + } + + return value; +} + +char* telnet_read_memory_string(uint32_t address, char *buffer, size_t maxlen) { + if (!address || !buffer || maxlen < 2) + return NULL; + + if (telnet_connect(4444) < 0) + return NULL; + + char cmd[64]; + int bytes_to_read = 60; + snprintf(cmd, sizeof(cmd), "mdb 0x%08x %d\n", address, bytes_to_read); + + if (send(_telnetSocket, cmd, strlen(cmd), 0) < 0) { + telnet_disconnect(); + return NULL; + } + + char line[256]; + int found = 0; + int lines_read = 0; + size_t out = 0; + + while (lines_read < 5) { + int len = _readLine(_telnetSocket, line, sizeof(line), 500); + if (len <= 0) + break; + + lines_read++; + + if (strstr(line, "mdb")) + continue; + + uint32_t addr; + char *ptr = strstr(line, "0x"); + if (ptr && sscanf(ptr, "0x%x:", &addr) == 1 && addr == address) { + char *colon = strchr(ptr, ':'); + if (colon) { + colon++; + + while (*colon && out < maxlen - 1) { + while (*colon == ' ' || *colon == '\t') + colon++; + + if (!*colon || *colon == '\n' || *colon == '\r') + break; + + unsigned int byte; + if (sscanf(colon, "%02x", &byte) != 1) + break; + + buffer[out++] = (char)byte; + + if (byte == 0) + break; + + colon += 2; + } + found = 1; + break; + } + } + + if (strstr(line, "> ")) + break; + } + + if (found && out > 0) { + buffer[out] = '\0'; + return buffer; + } + + return NULL; +} + +void telnet_clear_cache_for_tcb(uint32_t tcb_addr) { + if (!tcb_addr) return; + + struct memCache *cached, *tmp; + HASH_ITER(hh, _memCache, cached, tmp) + { + if (cached->addr >= tcb_addr && cached->addr < (tcb_addr + 256)) { + HASH_DEL(_memCache, cached); + free(cached); + } + } +} + +void telnet_configure_dwt(uint32_t watch_address) { + if (telnet_connect(4444) < 0) { + genericsReport(V_ERROR, "Cannot connect to OpenOCD telnet\n"); + return; + } + + char cmd[256]; + char response[1024]; + + snprintf(cmd, sizeof(cmd), "rtos_dwt_config 0x%08X\n", watch_address); + send(_telnetSocket, cmd, strlen(cmd), 0); + _readLine(_telnetSocket, response, sizeof(response), 500); + + genericsReport(V_INFO, "Configured DWT comparator 1 to watch 0x%08X\n", watch_address); +} + +void telnet_configure_exception_trace(bool enable) { + /* Always use a fresh connection for exception trace config */ + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + genericsReport(V_ERROR, "Failed to create socket for exception trace\n"); + return; + } + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(4444); + server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + genericsReport(V_ERROR, "Cannot connect to OpenOCD telnet for exception trace\n"); + close(sock); + return; + } + + /* Wait for and clear the OpenOCD banner */ + char response[1024]; + _readLine(sock, response, sizeof(response), 500); + + char cmd[256]; + if (enable) { + snprintf(cmd, sizeof(cmd), "exception_trace_enable\n"); + genericsReport(V_INFO, "TELNET: Sending command: exception_trace_enable\n"); + } else { + snprintf(cmd, sizeof(cmd), "exception_trace_disable\n"); + genericsReport(V_INFO, "TELNET: Sending command: exception_trace_disable\n"); + } + + int sent = send(sock, cmd, strlen(cmd), 0); + if (sent > 0) { + genericsReport(V_INFO, "TELNET: Sent exception trace command (%d bytes)\n", sent); + _readLine(sock, response, sizeof(response), 500); + genericsReport(V_INFO, "TELNET: Response: %s", response); + } + + close(sock); +} \ No newline at end of file diff --git a/Src/rtos/telnet_client.h b/Src/rtos/telnet_client.h new file mode 100644 index 00000000..bec488bb --- /dev/null +++ b/Src/rtos/telnet_client.h @@ -0,0 +1,18 @@ +#ifndef TELNET_CLIENT_H +#define TELNET_CLIENT_H + +#include +#include +#include + +int telnet_connect(int port); +void telnet_disconnect(void); +bool telnet_is_connected(void); + +uint32_t telnet_read_memory_word(uint32_t address); +char* telnet_read_memory_string(uint32_t address, char *buffer, size_t maxlen); +void telnet_clear_cache_for_tcb(uint32_t tcb_addr); +void telnet_configure_dwt(uint32_t watch_address); +void telnet_configure_exception_trace(bool enable); + +#endif \ No newline at end of file diff --git a/openocd/stm32h74x.cfg b/openocd/stm32h74x.cfg new file mode 100644 index 00000000..d10493dd --- /dev/null +++ b/openocd/stm32h74x.cfg @@ -0,0 +1,535 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# +# STMicroelectronics ST-LINK/V1, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 in-circuit +# debugger/programmer +# + +adapter driver st-link +st-link vid_pid 0x0483 0x3744 0x0483 0x3748 0x0483 0x374b 0x0483 0x374d 0x0483 0x374e 0x0483 0x374f 0x0483 0x3752 0x0483 0x3753 0x0483 0x3754 0x0483 0x3755 0x0483 0x3757 + +# script for stm32h7x family + +# +# stm32h7 devices support both JTAG and SWD transports. +# +source [find target/swj-dp.tcl] +source [find mem_helper.tcl] + +set DUAL_BANK 1 + + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME stm32h7x +} + +if { [info exists DUAL_BANK] } { + set $_CHIPNAME.DUAL_BANK $DUAL_BANK + unset DUAL_BANK +} else { + set $_CHIPNAME.DUAL_BANK 0 +} + +if { [info exists DUAL_CORE] } { + set $_CHIPNAME.DUAL_CORE $DUAL_CORE + unset DUAL_CORE +} else { + set $_CHIPNAME.DUAL_CORE 0 +} + +# Issue a warning when hla is used, and fallback to single core configuration +if { [set $_CHIPNAME.DUAL_CORE] && [using_hla] } { + echo "Warning : hla does not support multicore debugging" + set $_CHIPNAME.DUAL_CORE 0 +} + +if { [info exists USE_CTI] } { + set $_CHIPNAME.USE_CTI $USE_CTI + unset USE_CTI +} else { + set $_CHIPNAME.USE_CTI 0 +} + +# Issue a warning when DUAL_CORE=0 and USE_CTI=1, and fallback to USE_CTI=0 +if { ![set $_CHIPNAME.DUAL_CORE] && [set $_CHIPNAME.USE_CTI] } { + echo "Warning : could not use CTI with a single core device, CTI is disabled" + set $_CHIPNAME.USE_CTI 0 +} + +set _ENDIAN little + +# Work-area is a space in RAM used for flash programming +# By default use 64kB +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x10000 +} + +#jtag scan chain +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + if { [using_jtag] } { + set _CPUTAPID 0x6ba00477 + } { + set _CPUTAPID 0x6ba02477 + } +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu + +if {[using_jtag]} { + jtag newtap $_CHIPNAME bs -irlen 5 +} + +if {![using_hla]} { + # STM32H7 provides an APB-AP at access port 2, which allows the access to + # the debug and trace features on the system APB System Debug Bus (APB-D). + target create $_CHIPNAME.ap2 mem_ap -dap $_CHIPNAME.dap -ap-num 2 + swo create $_CHIPNAME.swo -dap $_CHIPNAME.dap -ap-num 2 -baseaddr 0xE00E3000 + tpiu create $_CHIPNAME.tpiu -dap $_CHIPNAME.dap -ap-num 2 -baseaddr 0xE00F5000 +} + +target create $_CHIPNAME.cpu0 cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap -ap-num 0 + +$_CHIPNAME.cpu0 configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +flash bank $_CHIPNAME.bank1.cpu0 stm32h7x 0x08000000 0 0 0 $_CHIPNAME.cpu0 + +if {[set $_CHIPNAME.DUAL_BANK]} { + flash bank $_CHIPNAME.bank2.cpu0 stm32h7x 0x08100000 0 0 0 $_CHIPNAME.cpu0 +} + +if {[set $_CHIPNAME.DUAL_CORE]} { + target create $_CHIPNAME.cpu1 cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap -ap-num 3 + + $_CHIPNAME.cpu1 configure -work-area-phys 0x38000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + + flash bank $_CHIPNAME.bank1.cpu1 stm32h7x 0x08000000 0 0 0 $_CHIPNAME.cpu1 + + if {[set $_CHIPNAME.DUAL_BANK]} { + flash bank $_CHIPNAME.bank2.cpu1 stm32h7x 0x08100000 0 0 0 $_CHIPNAME.cpu1 + } +} + +# Make sure that cpu0 is selected +targets $_CHIPNAME.cpu0 + +if { [info exists QUADSPI] && $QUADSPI } { + set a [llength [flash list]] + set _QSPINAME $_CHIPNAME.qspi + flash bank $_QSPINAME stmqspi 0x90000000 0 0 0 $_CHIPNAME.cpu0 0x52005000 +} else { + if { [info exists OCTOSPI1] && $OCTOSPI1 } { + set a [llength [flash list]] + set _OCTOSPINAME1 $_CHIPNAME.octospi1 + flash bank $_OCTOSPINAME1 stmqspi 0x90000000 0 0 0 $_CHIPNAME.cpu0 0x52005000 + } + if { [info exists OCTOSPI2] && $OCTOSPI2 } { + set b [llength [flash list]] + set _OCTOSPINAME2 $_CHIPNAME.octospi2 + flash bank $_OCTOSPINAME2 stmqspi 0x70000000 0 0 0 $_CHIPNAME.cpu0 0x5200A000 + } +} + +# Clock after reset is HSI at 64 MHz, no need of PLL +adapter speed 4000 + +adapter srst delay 100 +if {[using_jtag]} { + jtag_ntrst_delay 100 +} + +# use hardware reset +# +# The STM32H7 does not support connect_assert_srst mode because the AXI is +# unavailable while SRST is asserted, and that is used to access the DBGMCU +# component at 0x5C001000 in the examine-end event handler. +# +# It is possible to access the DBGMCU component at 0xE00E1000 via AP2 instead +# of the default AP0, and that works with SRST asserted; however, nonzero AP +# usage does not work with HLA, so is not done by default. That change could be +# made in a local configuration file if connect_assert_srst mode is needed for +# a specific application and a non-HLA adapter is in use. +reset_config srst_nogate + +if {![using_hla]} { + # if srst is not fitted use SYSRESETREQ to + # perform a soft reset + $_CHIPNAME.cpu0 cortex_m reset_config sysresetreq + + if {[set $_CHIPNAME.DUAL_CORE]} { + $_CHIPNAME.cpu1 cortex_m reset_config sysresetreq + } + + # Set CSW[27], which according to ARM ADI v5 appendix E1.4 maps to AHB signal + # HPROT[3], which according to AMBA AHB/ASB/APB specification chapter 3.7.3 + # makes the data access cacheable. This allows reading and writing data in the + # CPU cache from the debugger, which is far more useful than going straight to + # RAM when operating on typical variables, and is generally no worse when + # operating on special memory locations. + $_CHIPNAME.dap apcsw 0x08000000 0x08000000 +} + +$_CHIPNAME.cpu0 configure -event examine-end { + # Enable D3 and D1 DBG clocks + # DBGMCU_CR |= D3DBGCKEN | D1DBGCKEN + stm32h7x_dbgmcu_mmw 0x004 0x00600000 0 + + # Enable debug during low power modes (uses more power) + # DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP D1 Domain + stm32h7x_dbgmcu_mmw 0x004 0x00000007 0 + # DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP D2 Domain + stm32h7x_dbgmcu_mmw 0x004 0x00000038 0 + + # Stop watchdog counters during halt + # DBGMCU_APB3FZ1 |= WWDG1 + stm32h7x_dbgmcu_mmw 0x034 0x00000040 0 + # DBGMCU_APB1LFZ1 |= WWDG2 + stm32h7x_dbgmcu_mmw 0x03C 0x00000800 0 + # DBGMCU_APB4FZ1 |= WDGLSD1 | WDGLSD2 + stm32h7x_dbgmcu_mmw 0x054 0x000C0000 0 + + # Enable clock for tracing + # DBGMCU_CR |= TRACECLKEN + stm32h7x_dbgmcu_mmw 0x004 0x00100000 0 + + # RM0399 (id 0x450) M7+M4 with SWO Funnel + # RM0433 (id 0x450) M7 with SWO Funnel + # RM0455 (id 0x480) M7 without SWO Funnel + # RM0468 (id 0x483) M7 without SWO Funnel + # Enable CM7 and CM4 slave ports in SWO trace Funnel + # Works ok also on devices single core and without SWO funnel + # Hack, use stm32h7x_dbgmcu_mmw with big offset to control SWTF + # SWTF_CTRL |= ENS0 | ENS1 + stm32h7x_dbgmcu_mmw 0x3000 0x00000003 0 + + # Configure ITM with SYNC packets enabled + mww 0xE0000E80 0x0001000F ;# TCR: enable ITM with TraceBusID=1, SYNCENA=1, TSENA=1 + mww 0xE0000E00 0x00000001 ;# TER: enable ITM channel 0 + + # Configure and enable SWO (NRZ/UART protocol) + set _CHIPNAME [stm32h7x_get_chipname] +} + +$_CHIPNAME.cpu0 configure -event reset-init { + # Clock after reset is HSI at 64 MHz, no need of PLL + adapter speed 4000 +} + +# get _CHIPNAME from current target +proc stm32h7x_get_chipname {} { + set t [target current] + set sep [string last "." $t] + if {$sep == -1} { + return $t + } + return [string range $t 0 [expr {$sep - 1}]] +} + +if {[set $_CHIPNAME.DUAL_CORE]} { + $_CHIPNAME.cpu1 configure -event examine-end { + set _CHIPNAME [stm32h7x_get_chipname] + global $_CHIPNAME.USE_CTI + + # Stop watchdog counters during halt + # DBGMCU_APB3FZ2 |= WWDG1 + stm32h7x_dbgmcu_mmw 0x038 0x00000040 0 + # DBGMCU_APB1LFZ2 |= WWDG2 + stm32h7x_dbgmcu_mmw 0x040 0x00000800 0 + # DBGMCU_APB4FZ2 |= WDGLSD1 | WDGLSD2 + stm32h7x_dbgmcu_mmw 0x058 0x000C0000 0 + + if {[set $_CHIPNAME.USE_CTI]} { + stm32h7x_cti_start + } + } +} + +# like mrw, but with target selection +proc stm32h7x_mrw {used_target reg} { + return [$used_target read_memory $reg 32 1] +} + +# like mmw, but with target selection +proc stm32h7x_mmw {used_target reg setbits clearbits} { + set old [stm32h7x_mrw $used_target $reg] + set new [expr {($old & ~$clearbits) | $setbits}] + $used_target mww $reg $new +} + +# mmw for dbgmcu component registers, it accepts the register offset from dbgmcu base +# this procedure will use the mem_ap on AP2 whenever possible +proc stm32h7x_dbgmcu_mmw {reg_offset setbits clearbits} { + # use $_CHIPNAME.ap2 if possible, and use the proper dbgmcu base address + if {![using_hla]} { + set _CHIPNAME [stm32h7x_get_chipname] + set used_target $_CHIPNAME.ap2 + set reg_addr [expr {0xE00E1000 + $reg_offset}] + } { + set used_target [target current] + set reg_addr [expr {0x5C001000 + $reg_offset}] + } + + stm32h7x_mmw $used_target $reg_addr $setbits $clearbits +} + +if {[set $_CHIPNAME.USE_CTI]} { + # create CTI instances for both cores + cti create $_CHIPNAME.cti0 -dap $_CHIPNAME.dap -ap-num 0 -baseaddr 0xE0043000 + cti create $_CHIPNAME.cti1 -dap $_CHIPNAME.dap -ap-num 3 -baseaddr 0xE0043000 + + $_CHIPNAME.cpu0 configure -event halted { stm32h7x_cti_prepare_restart_all } + $_CHIPNAME.cpu1 configure -event halted { stm32h7x_cti_prepare_restart_all } + + $_CHIPNAME.cpu0 configure -event debug-halted { stm32h7x_cti_prepare_restart_all } + $_CHIPNAME.cpu1 configure -event debug-halted { stm32h7x_cti_prepare_restart_all } + + proc stm32h7x_cti_start {} { + set _CHIPNAME [stm32h7x_get_chipname] + + # Configure Cores' CTIs to halt each other + # TRIGIN0 (DBGTRIGGER) and TRIGOUT0 (EDBGRQ) at CTM_CHANNEL_0 + $_CHIPNAME.cti0 write INEN0 0x1 + $_CHIPNAME.cti0 write OUTEN0 0x1 + $_CHIPNAME.cti1 write INEN0 0x1 + $_CHIPNAME.cti1 write OUTEN0 0x1 + + # enable CTIs + $_CHIPNAME.cti0 enable on + $_CHIPNAME.cti1 enable on + } + + proc stm32h7x_cti_stop {} { + set _CHIPNAME [stm32h7x_get_chipname] + + $_CHIPNAME.cti0 enable off + $_CHIPNAME.cti1 enable off + } + + proc stm32h7x_cti_prepare_restart_all {} { + stm32h7x_cti_prepare_restart cti0 + stm32h7x_cti_prepare_restart cti1 + } + + proc stm32h7x_cti_prepare_restart {cti} { + set _CHIPNAME [stm32h7x_get_chipname] + + # Acknowlodge EDBGRQ at TRIGOUT0 + $_CHIPNAME.$cti write INACK 0x01 + $_CHIPNAME.$cti write INACK 0x00 + } +} +$_CHIPNAME.cpu0 configure -rtos auto + +set cfg_dir [file dirname [info script]] +source [file join $cfg_dir debug_init.tcl] +# ----------------------------- +# SWV/TPIU/ITM configuration +# ----------------------------- +# Configuration moved to examine-end event to ensure SWO object exists +$_CHIPNAME.swo configure -protocol uart -traceclk 480000000 -pin-freq 2000000 -formatter on -output :46000 +$_CHIPNAME.swo enable +# ============================================================================== +# Helper Functions (for telnet from orbtop) +# ============================================================================== + +# --- Only used constants --- +set CORESIGHT_LAR_KEY 0xC5ACCE55 ;# CoreSight Lock Access Register key + +# ITM Registers +set ITM_TCR 0xE0000E80 ;# ITM Trace Control Register +set ITM_LAR 0xE0000FB0 ;# ITM Lock Access Register +set ITM_TCR_DWTENA 0x00000008 ;# Bit 3: DWT stimulus Enable +set ITM_TCR_TSENA 0x00000002 ;# Bit 1: Timestamp Enable +set ITM_TCR_SYNCENA 0x00000004 ;# Bit 2: Sync packet Enable + +# DWT Registers +set DWT_CTRL 0xE0001000 ;# DWT Control Register +set DWT_CYCCNT 0xE0001004 ;# DWT Cycle Count Register +set DWT_LAR 0xE0001FB0 ;# DWT Lock Access Register +set DWT_COMP1 0xE0001030 ;# DWT Comparator 1 Address +set DWT_MASK1 0xE0001034 ;# DWT Comparator 1 Mask +set DWT_FUNC1 0xE0001038 ;# DWT Comparator 1 Function + +# DWT_CTRL bit definitions +set DWT_CTRL_CYCCNTENA 0x00000001 ;# Bit 0: Cycle counter enable +set DWT_CTRL_POSTPRESET 0x000001E0 ;# Bits 8:5: POSTCNT preset value +set DWT_CTRL_POSTINIT 0x00001E00 ;# Bits 12:9: POSTCNT initial value +set DWT_CTRL_CYCTAP 0x00000200 ;# Bit 9: Cycle counter tap for POSTCNT +set DWT_CTRL_SYNCTAP_MASK 0x00000C00 ;# Bits 11:10: Sync packet tap select +set DWT_CTRL_SYNCTAP_DISABLED 0x00000000 ;# 00: Sync disabled +set DWT_CTRL_SYNCTAP_BIT24 0x00000400 ;# 01: Sync when CYCCNT[24] toggles +set DWT_CTRL_SYNCTAP_BIT26 0x00000800 ;# 10: Sync when CYCCNT[26] toggles +set DWT_CTRL_SYNCTAP_BIT28 0x00000C00 ;# 11: Sync when CYCCNT[28] toggles +set DWT_CTRL_PCSAMPLENA 0x00001000 ;# Bit 12: PC sampling enable +set DWT_CTRL_EXCTRCENA 0x00010000 ;# Bit 16: Exception trace enable +set DWT_CTRL_CPIEVTENA 0x00020000 ;# Bit 17: CPI event enable +set DWT_CTRL_EXCEVTENA 0x00040000 ;# Bit 18: Exception event enable +set DWT_CTRL_SLEEPEVTENA 0x00080000 ;# Bit 19: Sleep event enable +set DWT_CTRL_LSUEVTENA 0x00100000 ;# Bit 20: LSU event enable +set DWT_CTRL_FOLDEVTENA 0x00200000 ;# Bit 21: Fold event enable + +# DWT comparator function settings +set DWT_FUNC_DATA_VALUE 0x0000000D ;# Data value compare on write +set DWT_MASK_DISABLED 0x00000000 ;# No masking - exact address match + +# DEMCR Register +set DEMCR 0xE000EDFC ;# Debug Exception and Monitor Control Register +set DEMCR_TRCENA 0x01000000 ;# Bit 24: Trace enable + +proc rtos_dwt_config {address} { + global DEMCR DEMCR_TRCENA + global ITM_LAR DWT_LAR CORESIGHT_LAR_KEY + global DWT_CTRL DWT_CTRL_CYCCNTENA DWT_CTRL_SYNCTAP_BIT24 + global DWT_COMP1 DWT_MASK1 DWT_FUNC1 DWT_FUNC_DATA_VALUE + global ITM_TCR ITM_TCR_DWTENA ITM_TCR_TSENA ITM_TCR_SYNCENA + global DWT_CYCCNT + global DWT_MASK_DISABLED + + echo "Configuring DWT Comparator 1 for address 0x[format %08X $address]" + + # Enable trace in DEMCR + mmw $DEMCR $DEMCR_TRCENA 0 + + # Unlock ITM and DWT + mww $ITM_LAR $CORESIGHT_LAR_KEY + mww $DWT_LAR $CORESIGHT_LAR_KEY + + # Reset and enable cycle counter for ITM timestamps + mww $DWT_CYCCNT 0 + + # Enable CYCCNT and set SYNCTAP to generate SYNC packets at CYCCNT[24] + mmw $DWT_CTRL [expr {$DWT_CTRL_CYCCNTENA | $DWT_CTRL_SYNCTAP_BIT24}] 0 + + # Ensure DWT events are enabled in ITM_TCR with SYNC packets + mmw $ITM_TCR [expr {$ITM_TCR_DWTENA | $ITM_TCR_TSENA | $ITM_TCR_SYNCENA}] 0 + + # Configure DWT Comparator 1 for data write tracking + mww $DWT_COMP1 $address + mww $DWT_MASK1 $DWT_MASK_DISABLED + mww $DWT_FUNC1 $DWT_FUNC_DATA_VALUE + + echo "DWT Comparator 1 configured for data write at 0x[format %08X $address]" + echo "DWT Cycle counter enabled for timestamps" + echo "SYNC packets enabled at CYCCNT 24Hz @ 480MHz" +} + +# Configurable PC sampling - frequency in Hz +proc pc_sampling_config {freq_hz} { + global DWT_CTRL DWT_CTRL_PCSAMPLENA DWT_CTRL_CYCCNTENA + global DWT_CTRL_SYNCTAP_MASK DWT_CTRL_SYNCTAP_DISABLED + global DWT_CTRL_SYNCTAP_BIT24 DWT_CTRL_SYNCTAP_BIT26 DWT_CTRL_SYNCTAP_BIT28 + + # Calculate SYNCTAP value based on frequency + # SYNCTAP controls when to generate sync packets based on CYCCNT bit toggle + # At 480MHz: bit24=~24Hz, bit26=~6Hz, bit28=~1.5Hz + + if {$freq_hz == 0} { + echo "PC sampling disabled" + mmw $DWT_CTRL 0 $DWT_CTRL_PCSAMPLENA + return + } + + set tap_val 0 + if {$freq_hz > 10} { + set tap_val $DWT_CTRL_SYNCTAP_BIT24 ;# Tap at CYCCNT[24] ~24Hz + echo "PC sampling: 24Hz (tap at CYCCNT bit 24)" + } elseif {$freq_hz > 3} { + set tap_val $DWT_CTRL_SYNCTAP_BIT26 ;# Tap at CYCCNT[26] ~6Hz + echo "PC sampling: 6Hz (tap at CYCCNT bit 26)" + } else { + set tap_val $DWT_CTRL_SYNCTAP_BIT28 ;# Tap at CYCCNT[28] ~1.5Hz + echo "PC sampling: 1.5Hz (tap at CYCCNT bit 28)" + } + + # Clear old SYNCTAP bits and set new ones + mmw $DWT_CTRL 0 $DWT_CTRL_SYNCTAP_MASK + mmw $DWT_CTRL [expr {$DWT_CTRL_PCSAMPLENA | $DWT_CTRL_CYCCNTENA | $tap_val}] 0 + echo "WARNING: PC sampling impacts performance" +} + +# Legacy functions for compatibility +proc pc_sampling_enable {} { + pc_sampling_config 24 ;# Default to highest rate +} + +proc pc_sampling_disable {} { + pc_sampling_config 0 +} + +proc exception_trace_enable {} { + global DEMCR DEMCR_TRCENA + global DWT_CTRL DWT_CTRL_EXCTRCENA + + echo "DWT: enabling exception trace" + + # Asegura que el tracing esté activo + mmw $DEMCR $DEMCR_TRCENA 0 + + # Activa Exception tracing y el cycle counter + mmw $DWT_CTRL [expr {$DWT_CTRL_EXCTRCENA}] 0 +} + +# Disable exception trace +proc exception_trace_disable {} { + global DWT_CTRL DWT_CTRL_EXCTRCENA + + echo "DWT: disabling exception trace" + + mmw $DWT_CTRL 0 $DWT_CTRL_EXCTRCENA +} + +# Control SYNC packet generation with configurable rate +proc sync_config {rate} { + global ITM_TCR ITM_TCR_SYNCENA + global DWT_CTRL DWT_CTRL_SYNCTAP_MASK + global DWT_CTRL_SYNCTAP_DISABLED DWT_CTRL_SYNCTAP_BIT24 + global DWT_CTRL_SYNCTAP_BIT26 DWT_CTRL_SYNCTAP_BIT28 + + if {$rate == 0} { + # Disable SYNC in ITM + mmw $ITM_TCR 0 $ITM_TCR_SYNCENA + # Clear SYNCTAP in DWT + mmw $DWT_CTRL 0 $DWT_CTRL_SYNCTAP_MASK + echo "SYNC packets disabled" + return + } + + # Enable SYNC in ITM + mmw $ITM_TCR $ITM_TCR_SYNCENA 0 + + # Configure SYNCTAP rate in DWT + mmw $DWT_CTRL 0 $DWT_CTRL_SYNCTAP_MASK ;# Clear old value + + if {$rate >= 24} { + mmw $DWT_CTRL $DWT_CTRL_SYNCTAP_BIT24 0 + echo "SYNC packets enabled at 24Hz CYCCNT bit 24" + } elseif {$rate >= 6} { + mmw $DWT_CTRL $DWT_CTRL_SYNCTAP_BIT26 0 + echo "SYNC packets enabled at 6Hz CYCCNT bit 26" + } else { + mmw $DWT_CTRL $DWT_CTRL_SYNCTAP_BIT28 0 + echo "SYNC packets enabled at 1.5Hz CYCCNT bit 28" + } +} + +# Legacy functions for compatibility +proc sync_enable {} { + sync_config 24 ;# Default to 24Hz +} + +proc sync_disable {} { + sync_config 0 +} + +echo "===================================================================================" +echo "DWT config via telnet: rtos_dwt_config to use with Orbtop -T" +echo "pc_sampling_config : Configure PC sampling (0=off, 1=1.5Hz, 6=6Hz, 24=24Hz)" +echo "sync_enable/disable: Control SYNC packet generation" +echo "exception_trace_enable: Enable exception tracing" +echo "===================================================================================" \ No newline at end of file From 053e2b6c9e12878a710f8a9bed3fbf851ff2f2a3 Mon Sep 17 00:00:00 2001 From: fmarotta Date: Tue, 9 Sep 2025 17:04:10 +0200 Subject: [PATCH 2/3] Added orbtop-rtos to meson.build --- meson.build | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/meson.build b/meson.build index 338af476..4e69a61c 100644 --- a/meson.build +++ b/meson.build @@ -139,6 +139,28 @@ executable('orbtop', install: true, ) +executable('orbtop-rtos', + sources: [ + 'Src/rtos/orbtop_rtos.c', + 'Src/rtos/exceptions.c', + 'Src/rtos/options.c', + 'Src/rtos/telnet_client.c', + 'Src/rtos/rtos_api.c', + 'Src/rtos/rtx5/rtx5.c', + 'Src/rtos/output/output_handler.c', + 'Src/rtos/output/output_console.c', + 'Src/rtos/output/output_json.c', + 'Src/rtos/output/output_ftrace.c', + 'Src/symbols.c', + 'Src/external/cJSON.c', + git_version_info_h, + ], + include_directories: [include_directories('Inc/rtos', 'Inc', 'Inc/external', 'Src/rtos', 'Src/rtos/output')], + dependencies: dependencies, + link_with: liborb, + install: true, +) + executable('orbdump', sources: [ 'Src/orbdump.c', From 3ae839dc01a5b9ab607e07f42f6968a86490cd52 Mon Sep 17 00:00:00 2001 From: fmarotta Date: Tue, 9 Sep 2025 17:09:56 +0200 Subject: [PATCH 3/3] Some documentation --- README.md | 83 +++++++++++++++++++++ {openocd => Src/rtos/openocd}/stm32h74x.cfg | 0 2 files changed, 83 insertions(+) rename {openocd => Src/rtos/openocd}/stm32h74x.cfg (100%) diff --git a/README.md b/README.md index cccb5d64..9c915492 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,10 @@ data from the target and make it available to clients whenever it can. * orbtop: A top utility to see what's actually going on with your target. It can also generate input files for dot and gnuplot for perty graphics. +* orbtop-rtos: An enhanced version of orbtop with RTOS-aware thread profiling support. Currently +supports RTOS ( cmsis rtx, etc) and provides per-thread CPU usage statistics, thread state monitoring, and +context switch analysis. Requires connection to OpenOCD for RTOS data extraction. + * orbstat: An analysis/statistics utility which can produce KCacheGrind input files. * orbtrace: The fpga configuration controller for use with ORBtrace hardware. @@ -1059,6 +1063,85 @@ and then; However, that's probably over-complicated now...just use the orbuculum -s option to hook to any source that is pumping out clean SWO data. This information is just left here to show the flexibilities you have got available. +orbtop-rtos: RTOS-aware Performance Profiling +============================================== + +`orbtop-rtos` extends the functionality of `orbtop` by adding RTOS-aware thread profiling capabilities. +It provides detailed per-thread CPU usage statistics and context switch analysis for RTX5-based systems. + +Key features: +* Real-time thread CPU usage monitoring with percentage utilization +* Context switch counting and timing analysis +* Exception/interrupt profiling with entry/exit tracking +* Uses DWT exception trace for accurate interrupt timing +* ITM timestamps for precise time measurements +* Multiple output formats: console, JSON, and ftrace-compatible + +Prerequisites: +* Target must be running RTX5 RTOS +* OpenOCD must be running with telnet enabled (default port 4444) +* DWT exception trace and ITM timestamps must be enabled on target + +Typical usage: + +``` +# In another terminal, run orbtop-rtos with RTOS support +> orbtop-rtos -s localhost:46000 -p ITM -e firmware.elf -T rtxv5 -W 4444 -F 480000000 + +``` + +The `-T rtx5` option enables RTOS thread profiling, and `-E` includes exception/interrupt analysis. +The tool will automatically connect to OpenOCD to extract RTOS thread information and combine it +with the ITM trace data to provide a comprehensive view of system behavior. + +Output formats: + +**JSON output** (`-j ` or `-j udp:`): +Generates structured JSON data with thread statistics, CPU usage, context switches, and timing information. +Can output to a file or send via UDP for real-time monitoring. The JSON includes: +* Per-thread CPU usage percentages and accumulated runtime +* Context switch counts and timing +* Exception/interrupt statistics +* Function-level profiling data +* Thread state and priority information + +**Ftrace output** (`-K `): +Generates Linux ftrace-compatible output that can be analyzed with standard Linux tracing tools like +`trace-cmd` or visualized with tools like Perfetto UI (ui.perfetto.dev). Use `-` for stdout or +`/tmp/trace.pipe` for live tracing. This format captures: +* Thread context switches with precise timestamps +* Function entry/exit events +* CPU usage over time +* Compatible with kernel tracing analysis workflows + +Example with multiple outputs: +``` +# Generate both JSON metrics and ftrace timeline +> ./orbtop-rtos -e firmware.elf -T rtx5 -E -j metrics.json -K trace.ftrace + +# Stream JSON via UDP and ftrace to pipe for live analysis +> ./orbtop-rtos -e firmware.elf -T rtx5 -j udp:5000 -K /tmp/trace.pipe +``` + +Runtime interaction (RTOS mode): +When running in console mode, you can use keyboard shortcuts to change the sort order: +* `t`: Sort by TCB address +* `c`: Sort by current CPU usage +* `m`: Sort by maximum CPU usage +* `n`: Sort by thread name +* `f`: Sort by function name +* `p`: Sort by priority +* `s`: Sort by context switches +* `r`: Reset maximum CPU values + +Command line options specific to orbtop-rtos: +* `-T, --rtos `: RTOS type for thread profiling (currently only 'rtx5' supported) +* `-j, --json-output `: JSON output to file or UDP port (REQUIRED argument when specified) +* `-K, --ftrace `: Generate ftrace output (use `-` for stdout, `/tmp/trace.pipe` for live) +* `-S, --rtos-sort `: Initial sort method: cpu|maxcpu|tcb|name|func|priority|switches +* `-F, --cpu-freq `: CPU frequency for accurate time calculations +* `-W, --telnet-port `: OpenOCD telnet port (default 4444) +* All standard orbtop options are also available Windows: concurrent debug and Orbuculum usage with Orbtrace =========================================================== diff --git a/openocd/stm32h74x.cfg b/Src/rtos/openocd/stm32h74x.cfg similarity index 100% rename from openocd/stm32h74x.cfg rename to Src/rtos/openocd/stm32h74x.cfg