/*************************************************************************** * Copyright (c) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /* This test drives the two trace inserts the enable service writes itself. The last thing the enable does is record two running events, so that a buffer handed to an analysis tool is never empty. Those two inserts are the only ones in the kernel that run inside the service that turns tracing on, which constrains them: the buffer pointer and the event enable bits are both set a few lines above, so neither can be found in any other state on the way in. What is left is the system state the enable runs in, whether the first of the two entries is the one that wraps the buffer, and what a registered buffer-full notification does when it is called from inside the first insert and the second has still to run. The enable is therefore driven with a buffer sized to hold exactly one event, which puts both inserts on the wrap boundary, and with three different notifications: one that only counts, one that filters the class the running event belongs to, and one that turns tracing off again - the shape an application that wants a single bufferful of history would use. The SMP insert reads the system state of the core it is running on rather than a single global, so the forced-state pass writes the entry of the core this test is running on, under the SMP protection primitive. Every thread in this suite is created mapped to core 0 alone, but the core is read rather than assumed. Without event tracing compiled in the enable reports the feature as disabled, which is all there is to check. */ #include #include "tx_api.h" #define TX_SOURCE_CODE /* The system state is per core on SMP, and tx_thread.h remaps the name to a function call returning the running core's entry unless this macro says the file wants the array itself. The forced-state pass below has to write it, so it needs the array. */ #define TX_THREAD_SMP_SOURCE_CODE #include "tx_trace.h" #include "tx_thread.h" #include "tx_initialize.h" static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; /* The trace buffer, declared as ULONGs so that the control header it starts with is aligned. */ static ULONG trace_buffer[256]; #define TRACE_REGISTRY_ENTRIES 4 static ULONG buffer_full_count = 0; /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); #ifdef TX_ENABLE_EVENT_TRACE /* The smallest buffer the enable accepts for the given number of registry entries: a control header, the registry, and room for one event. Sized from the kernel's own structures rather than guessed, so the buffer holds exactly one entry and both of the enable's inserts land on the wrap boundary. */ static ULONG smallest_buffer(ULONG registry_entries) { return((ULONG) ((sizeof(TX_TRACE_HEADER)) + ((sizeof(TX_TRACE_OBJECT_ENTRY)) * registry_entries) + (sizeof(TX_TRACE_BUFFER_ENTRY)))); } /* A notification that only counts. */ static VOID notify_count(void *buffer) { buffer_full_count++; } /* A notification that filters the class the running event belongs to, so the second of the enable's two inserts is reached with its event filtered out. */ static VOID notify_filter(void *buffer) { buffer_full_count++; tx_trace_event_filter(TX_TRACE_INTERNAL_EVENTS); } /* A notification that turns tracing off, which is what an application capturing a single bufferful of history does. The second of the enable's two inserts is then reached with no buffer to write into. */ static VOID notify_disable(void *buffer) { buffer_full_count++; tx_trace_disable(); } /* Enable and immediately disable, with the system state forced to the value the inserts have to see. One SMP protection covers the write, the two services and the restore, so nothing else observes the forced value; the state is non-zero throughout, so the preemption check the enable ends with is a no-op. */ static UINT enable_with_system_state(ULONG system_state) { TX_INTERRUPT_SAVE_AREA UINT status; UINT core; ULONG saved_system_state; TX_DISABLE core = tx_thread_smp_core_get(); saved_system_state = _tx_thread_system_state[core]; _tx_thread_system_state[core] = system_state; status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), TRACE_REGISTRY_ENTRIES); if (status == TX_SUCCESS) { status = tx_trace_disable(); } _tx_thread_system_state[core] = saved_system_state; TX_RESTORE return(status); } #endif /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_trace_enable_trace_application_define(void *first_unused_memory) #endif { UINT status; CHAR *pointer; /* Put first available memory address into a character pointer. */ pointer = (CHAR *) first_unused_memory; /* Put system definition stuff in here, e.g. thread creates and other assorted create information. */ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 1, pointer, TEST_STACK_SIZE_PRINTF, 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Trace Enable Trace Test.................................... ERROR #1\n"); test_control_return(1); } } /* Define the test thread. */ static void thread_0_entry(ULONG thread_input) { UINT status; /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running Trace Enable Trace Test.................................... "); /* A buffer with room for many events: neither insert wraps it. */ status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), TRACE_REGISTRY_ENTRIES); #ifndef TX_ENABLE_EVENT_TRACE if (status != TX_FEATURE_NOT_ENABLED) { printf("ERROR #2\n"); test_control_return(1); } printf("SUCCESS!\n"); test_control_return(0); #else if (status != TX_SUCCESS) { printf("ERROR #3\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #4\n"); test_control_return(1); } /* The state an interrupt presents: the inserts record the interrupted thread instead of a priority. */ if (enable_with_system_state((ULONG) 1) != TX_SUCCESS) { printf("ERROR #5\n"); test_control_return(1); } /* Initialization context: the inserts have no thread to record at all. */ if (enable_with_system_state(TX_INITIALIZE_IN_PROGRESS) != TX_SUCCESS) { printf("ERROR #6\n"); test_control_return(1); } /* A buffer with room for exactly one event, and no notification registered: both inserts wrap it. */ status = tx_trace_enable(trace_buffer, smallest_buffer(TRACE_REGISTRY_ENTRIES), TRACE_REGISTRY_ENTRIES); if (status != TX_SUCCESS) { printf("ERROR #7\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #8\n"); test_control_return(1); } /* The same, with a notification registered. Both of the enable's inserts wrap, so it is called at least twice. */ buffer_full_count = 0; status = tx_trace_buffer_full_notify(notify_count); if (status != TX_SUCCESS) { printf("ERROR #9\n"); test_control_return(1); } status = tx_trace_enable(trace_buffer, smallest_buffer(TRACE_REGISTRY_ENTRIES), TRACE_REGISTRY_ENTRIES); if (status != TX_SUCCESS) { printf("ERROR #10\n"); test_control_return(1); } if (buffer_full_count < 2) { printf("ERROR #11\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #12\n"); test_control_return(1); } /* A notification that filters the running event's own class. It is called from inside the first insert, so the second one is reached with its event filtered out and records nothing. */ buffer_full_count = 0; status = tx_trace_buffer_full_notify(notify_filter); if (status != TX_SUCCESS) { printf("ERROR #13\n"); test_control_return(1); } status = tx_trace_enable(trace_buffer, smallest_buffer(TRACE_REGISTRY_ENTRIES), TRACE_REGISTRY_ENTRIES); if (status != TX_SUCCESS) { printf("ERROR #14\n"); test_control_return(1); } /* The second insert took the filtered arm, so it never wrapped and the notification was called exactly once. */ if (buffer_full_count != 1) { printf("ERROR #15\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #16\n"); test_control_return(1); } status = tx_trace_event_unfilter(TX_TRACE_INTERNAL_EVENTS); if (status != TX_SUCCESS) { printf("ERROR #17\n"); test_control_return(1); } /* A notification that turns tracing off. The second insert is reached with no buffer to write into, and the enable still reports success because it did everything it was asked to. */ buffer_full_count = 0; status = tx_trace_buffer_full_notify(notify_disable); if (status != TX_SUCCESS) { printf("ERROR #18\n"); test_control_return(1); } status = tx_trace_enable(trace_buffer, smallest_buffer(TRACE_REGISTRY_ENTRIES), TRACE_REGISTRY_ENTRIES); if (status != TX_SUCCESS) { printf("ERROR #19\n"); test_control_return(1); } if (buffer_full_count != 1) { printf("ERROR #20\n"); test_control_return(1); } /* Tracing is already off, so there is nothing left for the disable to do. */ status = tx_trace_disable(); if (status != TX_NOT_DONE) { printf("ERROR #21\n"); test_control_return(1); } status = tx_trace_buffer_full_notify(TX_NULL); if (status != TX_SUCCESS) { printf("ERROR #22\n"); test_control_return(1); } printf("SUCCESS!\n"); test_control_return(0); #endif }