summaryrefslogtreecommitdiff
path: root/test/smp
diff options
context:
space:
mode:
Diffstat (limited to 'test/smp')
-rw-r--r--test/smp/cmake/CMakeLists.txt16
-rwxr-xr-xtest/smp/cmake/coverage.sh146
-rw-r--r--test/smp/cmake/regression/CMakeLists.txt24
-rw-r--r--test/smp/cmake/threadx_smp/CMakeLists.txt1
-rw-r--r--test/smp/cmake/threadx_smp/common_smp/CMakeLists.txt1
-rw-r--r--test/smp/cmake/threadx_smp/ports_smp/linux/gnu/CMakeLists.txt1
-rw-r--r--test/smp/cmake/threadx_smp/ports_smp/win64/vs_2022/CMakeLists.txt1
-rw-r--r--test/smp/regression/execution_profile_test/tx_api.h40
-rw-r--r--test/smp/regression/testcontrol.c379
-rw-r--r--test/smp/regression/threadx_block_memory_basic_test.c4
-rw-r--r--test/smp/regression/threadx_block_memory_error_detection_test.c4
-rw-r--r--test/smp/regression/threadx_block_memory_information_test.c8
-rw-r--r--test/smp/regression/threadx_block_memory_suspension_timeout_test.c2
-rw-r--r--test/smp/regression/threadx_byte_memory_basic_test.c4
-rw-r--r--test/smp/regression/threadx_byte_memory_information_test.c6
-rw-r--r--test/smp/regression/threadx_byte_memory_long_search_test.c337
-rw-r--r--test/smp/regression/threadx_byte_memory_suspension_timeout_test.c2
-rw-r--r--test/smp/regression/threadx_event_flag_basic_test.c4
-rw-r--r--test/smp/regression/threadx_event_flag_information_test.c6
-rw-r--r--test/smp/regression/threadx_mutex_basic_test.c4
-rw-r--r--test/smp/regression/threadx_mutex_information_test.c6
-rw-r--r--test/smp/regression/threadx_object_delete_suspension_test.c481
-rw-r--r--test/smp/regression/threadx_queue_basic_one_word_test.c4
-rw-r--r--test/smp/regression/threadx_queue_information_test.c6
-rw-r--r--test/smp/regression/threadx_semaphore_basic_test.c4
-rw-r--r--test/smp/regression/threadx_semaphore_information_test.c6
-rw-r--r--test/smp/regression/threadx_smp_execution_profile_get_test.c126
-rw-r--r--test/smp/regression/threadx_smp_random_resume_suspend_exclusion_pt_test.c10
-rw-r--r--test/smp/regression/threadx_smp_relinquish_rebalance_test.c250
-rw-r--r--test/smp/regression/threadx_smp_thread_exit_callback_remote_test.c514
-rw-r--r--test/smp/regression/threadx_smp_time_slice_test.c2
-rw-r--r--test/smp/regression/threadx_thread_basic_execution_test.c4
-rw-r--r--test/smp/regression/threadx_thread_delayed_suspension_test.c39
-rw-r--r--test/smp/regression/threadx_thread_exit_callback_transition_test.c1375
-rw-r--r--test/smp/regression/threadx_thread_information_test.c6
-rw-r--r--test/smp/regression/threadx_thread_misaligned_stack_test.c156
-rw-r--r--test/smp/regression/threadx_thread_multiple_sleep_test.c2
-rw-r--r--test/smp/regression/threadx_thread_priority_change.c50
-rw-r--r--test/smp/regression/threadx_thread_stack_checking_test.c15
-rw-r--r--test/smp/regression/threadx_thread_stack_fill_value_test.c190
-rw-r--r--test/smp/regression/threadx_thread_wait_abort_and_isr_test.c89
-rw-r--r--test/smp/regression/threadx_timer_information_test.c6
-rw-r--r--test/smp/regression/threadx_timer_simple_test.c4
-rw-r--r--test/smp/regression/threadx_trace_basic_test.c4
-rw-r--r--test/smp/regression/threadx_trace_entry_update_test.c367
45 files changed, 4657 insertions, 49 deletions
diff --git a/test/smp/cmake/CMakeLists.txt b/test/smp/cmake/CMakeLists.txt
index e2ca8627..3a0d9638 100644
--- a/test/smp/cmake/CMakeLists.txt
+++ b/test/smp/cmake/CMakeLists.txt
@@ -64,7 +64,21 @@ add_subdirectory(regression)
add_subdirectory(samples)
# Coverage
-if(CMAKE_BUILD_TYPE MATCHES ".*_coverage")
+#
+# The gate here used to be the build type alone, and only one of the five
+# configurations -- default_build_coverage -- has a name matching "*_coverage".
+# So four configurations built and ran every test and their coverage was thrown
+# away. That is not redundancy discarded: each configuration selects a different
+# set of TX_ feature macros, so the code the other four compile is *absent from
+# the denominator* rather than reported as uncovered. A figure taken from one of
+# them is the executable-line count of one configuration, not of the kernel.
+#
+# TX_COVERAGE instruments a build regardless of its name. It defaults to OFF and
+# the build-type match is kept, so configuring a single configuration by hand
+# behaves exactly as it did before; the regression run turns it on for all five
+# and merges the results.
+option(TX_COVERAGE "Instrument this build for coverage regardless of build type" OFF)
+if(TX_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage")
if(NOT MSVC)
target_compile_options(threadx_smp PRIVATE -fprofile-arcs -ftest-coverage)
target_link_options(threadx_smp PRIVATE -fprofile-arcs -ftest-coverage)
diff --git a/test/smp/cmake/coverage.sh b/test/smp/cmake/coverage.sh
index d35284f5..88de4f04 100755
--- a/test/smp/cmake/coverage.sh
+++ b/test/smp/cmake/coverage.sh
@@ -14,7 +14,145 @@
set -e
cd $(dirname $0)
-threadx_smp=$(realpath ../../../common_smp/src)
-mkdir -p coverage_report/$1
-gcovr --object-directory=build/$1/threadx_smp/CMakeFiles/threadx_smp.dir/$threadx_smp -r build/$1 -f ../../../common_smp/src --xml-pretty --output coverage_report/$1.xml
-gcovr --object-directory=build/$1/threadx_smp/CMakeFiles/threadx_smp.dir/$threadx_smp -r build/$1 -f ../../../common_smp/src --html --html-details --output coverage_report/$1/index.html
+
+# gcov reads a data format tied to the compiler that produced it, so gcov has to match
+# the gcc that built the objects. Since cmake/linux.cmake began honouring CC, taking
+# whatever gcov happens to be first on PATH is no longer safe: building with gcc-14 and
+# reading with gcov-13 gives "version 'B42*', prefer 'B33*'" from gcov, and gcovr turns
+# that into "GCOV returncode was 3" and exits 64. The tests pass and then the coverage
+# step fails with a Python traceback, which reads like a coverage bug rather than a
+# toolchain mismatch.
+#
+# So derive gcov from CC rather than asking the caller to remember both. GCOV still
+# overrides, for a toolchain that does not follow the gcc/gcov naming.
+: "${CC:=gcc}"
+if [ -z "${GCOV:-}" ]; then
+ cc_base=$(basename "$CC")
+ case "$cc_base" in
+ gcc*) GCOV="gcov${cc_base#gcc}" ;;
+ *) GCOV="gcov" ;;
+ esac
+fi
+
+if ! command -v "$GCOV" >/dev/null 2>&1; then
+ echo "coverage.sh: '$GCOV' not found, derived from CC='$CC'." >&2
+ echo "Install it, or set GCOV to the gcov matching that compiler." >&2
+ exit 1
+fi
+
+# The repository root, needed by every mode below. It has to be absolute: see the
+# note on the per-configuration paths further down.
+repo_root=$(cd ../../.. && pwd)
+filter=$repo_root/common_smp/src
+
+# --merge unions the per-configuration reports into the one number that means
+# something. Each configuration writes an intermediate JSON beside its XML, and
+# this pass adds them all together.
+#
+# Reporting five separate percentages instead would invite a reader to average
+# them, and an average is not a coverage figure -- a line covered only by
+# trace_build is covered, and only the union says so.
+#
+# Do not be alarmed when the merged percentage is lower than the single
+# configuration this used to report. That is the point: the denominator now
+# includes code the old report never counted at all, because it was compiled out
+# of the only instrumented build.
+if [ "$1" = "--merge" ]; then
+ shopt -s nullglob
+ tracefiles=(coverage_report/per_configuration/*.json)
+ shopt -u nullglob
+ if [ ${#tracefiles[@]} -eq 0 ]; then
+ echo "coverage.sh --merge: no JSON in coverage_report/per_configuration/." >&2
+ echo "Run the suites with TX_COVERAGE=ON first." >&2
+ exit 1
+ fi
+
+ add_args=()
+ for t in "${tracefiles[@]}"; do
+ add_args+=(--add-tracefile "$t")
+ done
+
+ mkdir -p coverage_report/merged
+ gcovr -r "$repo_root" "${add_args[@]}" --xml-pretty --output coverage_report/merged.xml
+ gcovr -r "$repo_root" "${add_args[@]}" --html --html-details --output coverage_report/merged/index.html
+
+ if ! grep -q "<class " coverage_report/merged.xml; then
+ echo "coverage.sh --merge: the merged report contains no files." >&2
+ exit 1
+ fi
+
+ # Named, not just counted. coverage_report/ is not cleaned between runs, so a
+ # tracefile left by an earlier run of a different set of configurations would
+ # otherwise be merged in without anything saying so.
+ echo "coverage.sh --merge: SMP, ${#tracefiles[@]} configuration(s):"
+ for t in "${tracefiles[@]}"; do
+ echo " $(basename "$t" .json)"
+ done
+ exit 0
+fi
+
+# gcovr is given three paths below, and each of them has to be absolute, for a
+# different reason.
+#
+# -r is the repository root rather than the build directory, so the report names
+# files the way the repository does -- "common_smp/src/tx_block_allocate.c"
+# instead of "/home/runner/work/threadx/threadx/common_smp/src/tx_block_allocate.c".
+# With -r inside build/, gcovr cannot express the sources relative to it, because
+# they are outside it, and falls back to absolute paths. Those paths then differ
+# on every machine and disagree with the <source> element written beside them in
+# the same file, so anything that maps coverage back to the repository -- PR
+# annotations, Codecov, SonarQube -- cannot follow them.
+#
+# Both -r and -f must be absolute. "-r ../../.. -f common_smp/src" produces a
+# report containing zero files and exits 0, which is the worst failure mode
+# available here: a green run carrying an empty report. Measured, not assumed.
+repo_root=$(cd ../../.. && pwd)
+
+# This is what actually scopes the report to one build configuration, and it is
+# the positional search path -- not --object-directory, which used to be here
+# and was doing nothing at all. That flag tells gcovr how to get from a gcda
+# file back to the compiler's working directory; it does not restrict which gcda
+# files are found. Pointed at an empty directory it still produced the full
+# report, because gcovr searches -r as well.
+#
+# That matters more now than it did before. While -r was build/$1 it happened to
+# constrain the search to this configuration by accident. -r is the repository
+# root now, and every configuration's gcda lies somewhere under it, so without
+# an explicit search path the report would silently merge all five.
+#
+# The odd shape of this path is CMake's, not ours: common_smp/src sits outside
+# this directory's source tree, so CMake mangles each source file's absolute
+# path into the object path, minus the leading slash. Hence the concatenation
+# rather than a join -- $repo_root already begins with one.
+objdir=$PWD/build/$1/threadx_smp/CMakeFiles/threadx_smp.dir$repo_root/common_smp/src
+
+# The Linux port's own sources are deliberately outside -f. gcno files exist for
+# two directories -- common_smp/src and ports_smp/linux/gnu/src -- and only the
+# first is reported. The kernel is what this suite is here to cover; the
+# architecture ports are validated functionally rather than structurally, and
+# linux/gnu is a development host port that nothing ships on. Written down
+# because a filter argument on its own is not a decision the next reader can see.
+
+# Per-configuration output is kept in a subdirectory of its own, and the merged
+# report sits alongside it at the top. That is not tidiness: the Pages deploy
+# uploads coverage_report wholesale and merges the ThreadX and SMP artifacts
+# into one tree, and every configuration directory has the same name in both
+# suites. Left at the top level, default_build_coverage/ from one suite would
+# overwrite the other's on the published site. Nested here, the top level still
+# holds exactly the one suite directory the deploy expects.
+mkdir -p coverage_report/per_configuration/$1
+gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" \
+ --json coverage_report/per_configuration/$1.json \
+ --xml-pretty --output coverage_report/per_configuration/$1.xml
+gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" --html --html-details --output coverage_report/per_configuration/$1/index.html
+
+# An empty report is not an error as far as gcovr is concerned: it warns and
+# exits 0. Worse, it advertises line-rate="1.0" alongside lines-valid="0", so
+# every downstream consumer reads "no data at all" as "100% covered". A coverage
+# threshold cannot catch that, because an empty report passes any threshold. So
+# the assertion belongs here, next to the paths that would cause it.
+if ! grep -q "<class " coverage_report/per_configuration/$1.xml; then
+ echo "coverage.sh: the report for '$1' contains no files." >&2
+ echo "Expected gcda files under $objdir." >&2
+ exit 1
+fi
diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt
index b448ae3e..cdf0c585 100644
--- a/test/smp/cmake/regression/CMakeLists.txt
+++ b/test/smp/cmake/regression/CMakeLists.txt
@@ -25,6 +25,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
${SOURCE_DIR}/threadx_byte_memory_basic_test.c
${SOURCE_DIR}/threadx_byte_memory_information_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_long_search_test.c
${SOURCE_DIR}/threadx_byte_memory_prioritize_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_timeout_test.c
@@ -52,6 +53,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_mutex_proritize_test.c
${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
${SOURCE_DIR}/threadx_mutex_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_object_delete_suspension_test.c
${SOURCE_DIR}/threadx_queue_basic_eight_word_test.c
${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
${SOURCE_DIR}/threadx_queue_basic_one_word_test.c
@@ -84,6 +86,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_smp_random_resume_suspend_exclusion_test.c
${SOURCE_DIR}/threadx_smp_random_resume_suspend_test.c
${SOURCE_DIR}/threadx_smp_rebalance_exclusion_test.c
+ ${SOURCE_DIR}/threadx_smp_relinquish_rebalance_test.c
${SOURCE_DIR}/threadx_smp_relinquish_test.c
${SOURCE_DIR}/threadx_smp_resume_suspend_ascending_order_test.c
${SOURCE_DIR}/threadx_smp_resume_suspend_descending_order_test.c
@@ -95,6 +98,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_thread_create_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
${SOURCE_DIR}/threadx_thread_information_test.c
+ ${SOURCE_DIR}/threadx_thread_misaligned_stack_test.c
${SOURCE_DIR}/threadx_thread_multi_level_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
${SOURCE_DIR}/threadx_thread_multiple_sleep_test.c
@@ -111,7 +115,10 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_thread_sleep_for_100ticks_test.c
${SOURCE_DIR}/threadx_thread_sleep_terminate_test.c
${SOURCE_DIR}/threadx_thread_stack_checking_test.c
+ ${SOURCE_DIR}/threadx_thread_stack_fill_value_test.c
${SOURCE_DIR}/threadx_thread_terminate_delete_test.c
+ ${SOURCE_DIR}/threadx_thread_exit_callback_transition_test.c
+ ${SOURCE_DIR}/threadx_smp_thread_exit_callback_remote_test.c
${SOURCE_DIR}/threadx_thread_time_slice_change_test.c
${SOURCE_DIR}/threadx_thread_wait_abort_and_isr_test.c
${SOURCE_DIR}/threadx_thread_wait_abort_test.c
@@ -124,6 +131,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_timer_multiple_test.c
${SOURCE_DIR}/threadx_timer_simple_test.c
${SOURCE_DIR}/threadx_trace_basic_test.c
+ ${SOURCE_DIR}/threadx_trace_entry_update_test.c
${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c)
add_custom_command(
@@ -162,3 +170,19 @@ foreach(test_case ${regression_test_cases})
target_link_libraries(${test_name} PRIVATE azrtos::threadx_smp)
add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name})
endforeach()
+
+add_executable(threadx_smp_execution_profile_get_test
+ ${SOURCE_DIR}/threadx_smp_execution_profile_get_test.c
+ ${REPO_ROOT}/utility/execution_profile_kit/smp_version/tx_execution_profile.c)
+target_include_directories(threadx_smp_execution_profile_get_test
+ PRIVATE
+ ${SOURCE_DIR}/execution_profile_test
+ ${REPO_ROOT}/utility/execution_profile_kit/smp_version)
+target_compile_definitions(threadx_smp_execution_profile_get_test
+ PRIVATE CTEST BATCH_TEST TEST_STACK_SIZE_PRINTF=4096)
+if(NOT MSVC)
+ target_compile_options(threadx_smp_execution_profile_get_test PRIVATE -m64)
+ target_link_options(threadx_smp_execution_profile_get_test PRIVATE -m64)
+endif()
+add_test(${CMAKE_BUILD_TYPE}::threadx_smp_execution_profile_get_test
+ threadx_smp_execution_profile_get_test)
diff --git a/test/smp/cmake/threadx_smp/CMakeLists.txt b/test/smp/cmake/threadx_smp/CMakeLists.txt
index 568fece4..a2e17636 100644
--- a/test/smp/cmake/threadx_smp/CMakeLists.txt
+++ b/test/smp/cmake/threadx_smp/CMakeLists.txt
@@ -45,6 +45,7 @@ else()
endif()
configure_file(${TX_USER_FILE} ${CUSTOM_INC_DIR}/tx_user.h COPYONLY)
target_include_directories(${PROJECT_NAME}
+ SYSTEM
PUBLIC
${CUSTOM_INC_DIR}
)
diff --git a/test/smp/cmake/threadx_smp/common_smp/CMakeLists.txt b/test/smp/cmake/threadx_smp/common_smp/CMakeLists.txt
index 646b200b..770379af 100644
--- a/test/smp/cmake/threadx_smp/common_smp/CMakeLists.txt
+++ b/test/smp/cmake/threadx_smp/common_smp/CMakeLists.txt
@@ -214,6 +214,7 @@ target_sources(${PROJECT_NAME}
# Add the Common/inc directory to the project include list
target_include_directories(${PROJECT_NAME}
+ SYSTEM
PUBLIC
${CURRENT_DIR}/inc
)
diff --git a/test/smp/cmake/threadx_smp/ports_smp/linux/gnu/CMakeLists.txt b/test/smp/cmake/threadx_smp/ports_smp/linux/gnu/CMakeLists.txt
index 9c51522c..64d97255 100644
--- a/test/smp/cmake/threadx_smp/ports_smp/linux/gnu/CMakeLists.txt
+++ b/test/smp/cmake/threadx_smp/ports_smp/linux/gnu/CMakeLists.txt
@@ -25,6 +25,7 @@ target_sources(${PROJECT_NAME}
)
target_include_directories(${PROJECT_NAME}
+ SYSTEM
PUBLIC
${CURRENT_DIR}/inc
)
diff --git a/test/smp/cmake/threadx_smp/ports_smp/win64/vs_2022/CMakeLists.txt b/test/smp/cmake/threadx_smp/ports_smp/win64/vs_2022/CMakeLists.txt
index 6b219bb2..8635647a 100644
--- a/test/smp/cmake/threadx_smp/ports_smp/win64/vs_2022/CMakeLists.txt
+++ b/test/smp/cmake/threadx_smp/ports_smp/win64/vs_2022/CMakeLists.txt
@@ -24,6 +24,7 @@ target_sources(${PROJECT_NAME}
)
target_include_directories(${PROJECT_NAME}
+ SYSTEM
PUBLIC
${CURRENT_DIR}/inc
)
diff --git a/test/smp/regression/execution_profile_test/tx_api.h b/test/smp/regression/execution_profile_test/tx_api.h
new file mode 100644
index 00000000..aef253c6
--- /dev/null
+++ b/test/smp/regression/execution_profile_test/tx_api.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+ * 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 Codex (GPT-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
+ **************************************************************************/
+
+#ifndef TX_API_H
+#define TX_API_H
+
+#define TX_THREAD_SMP_MAX_CORES 4U
+#define TX_SUCCESS 0U
+#define TX_NOT_DONE 20U
+#define TX_NULL ((void *) 0)
+#define TX_SMP_CORE_ID 0U
+
+typedef unsigned int UINT;
+typedef unsigned long ULONG;
+typedef void VOID;
+
+typedef struct TX_THREAD_STRUCT
+{
+ unsigned long long tx_thread_execution_time_total;
+ unsigned long tx_thread_execution_time_last_start;
+ struct TX_THREAD_STRUCT *tx_thread_created_next;
+} TX_THREAD;
+
+#define TX_INTERRUPT_SAVE_AREA
+#define TX_DISABLE
+#define TX_RESTORE
+
+#endif
diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c
index 1c3b2ccf..c964dc03 100644
--- a/test/smp/regression/testcontrol.c
+++ b/test/smp/regression/testcontrol.c
@@ -10,7 +10,7 @@
/***************************************************************************/
/* This is the test control routine of the ThreadX kernel. All tests are dispatched from this routine. */
-// Some portions generated by Codex (gpt 5.5).
+// Portions of this file were generated with AI assistance.
#define TX_THREAD_SMP_SOURCE_CODE
@@ -25,6 +25,19 @@
#include "tx_block_pool.h"
#include "tx_byte_pool.h"
#include "tx_event_flags.h"
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+#include <io.h>
+#include <process.h>
+#include <stdint.h>
+#else
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <time.h>
+#include <pthread.h>
+#endif
#define TEST_STACK_SIZE 6144
@@ -58,6 +71,7 @@ TX_BLOCK_POOL init_block_pool;
ULONG init_block_pool_area[50];
TX_EVENT_FLAGS_GROUP init_event_flags;
TX_TIMER init_timer;
+TX_TIMER init_timer_change;
TX_THREAD init_test_thread;
TX_THREAD second_test_thread;
#ifndef TX_TIMER_PROCESS_IN_ISR
@@ -233,6 +247,8 @@ void threadx_thread_simple_sleep_non_clear_application_define(void *);
void threadx_thread_sleep_for_100ticks_application_define(void *);
void threadx_thread_multiple_sleep_application_define(void *);
void threadx_thread_terminate_delete_application_define(void *);
+void threadx_thread_exit_callback_transition_application_define(void *);
+void threadx_smp_thread_exit_callback_remote_application_define(void *);
void threadx_thread_preemption_change_application_define(void *);
void threadx_thread_priority_change_application_define(void *);
void threadx_thread_time_slice_change_application_define(void *);
@@ -244,6 +260,7 @@ void threadx_thread_create_preemption_threshold_application_define(void *);
void threadx_thread_information_application_define(void *);
void threadx_thread_reset_application_define(void *);
void threadx_thread_stack_checking_application_define(void *);
+void threadx_thread_stack_fill_value_application_define(void *);
void threadx_time_get_set_application_define(void *);
@@ -377,6 +394,8 @@ TEST_ENTRY test_control_tests[] =
threadx_thread_sleep_for_100ticks_application_define,
threadx_thread_multiple_sleep_application_define,
threadx_thread_terminate_delete_application_define,
+ threadx_thread_exit_callback_transition_application_define,
+ threadx_smp_thread_exit_callback_remote_application_define,
threadx_thread_priority_change_application_define,
@@ -390,6 +409,7 @@ TEST_ENTRY test_control_tests[] =
threadx_thread_information_application_define,
threadx_thread_reset_application_define,
threadx_thread_stack_checking_application_define,
+ threadx_thread_stack_fill_value_application_define,
threadx_time_get_set_application_define,
@@ -681,6 +701,11 @@ TX_THREAD *thread_ptr;
test_timer_create_init = tx_timer_create(&init_timer, "init timer", init_timer_entry, 0x5678,
100, 200, TX_AUTO_ACTIVATE);
+ /* Test timer change from initialization. */
+ test_timer_create_init += tx_timer_create(&init_timer_change, "init timer change", init_timer_entry, 0x5678,
+ 100, 200, TX_NO_ACTIVATE);
+ test_timer_create_init += tx_timer_change(&init_timer_change, 50, 100);
+
/* Test calling tx_thread_relinquish to see if the error checking throws it out. */
tx_thread_relinquish();
#endif
@@ -1335,6 +1360,314 @@ TX_THREAD *thread_ptr;
}
+/* ---------------------------------------------------------------------------
+ Teardown diagnostics.
+
+ When this suite hangs, CI reports nothing but a ctest timeout. The last
+ flush of stdout happens on entry to test_control_return(), so a test that
+ wedges after that point leaves the rest of its output sitting in stdout's
+ block buffer, which is discarded when ctest kills the process. The log
+ stops at the test's own result line and says nothing about where the
+ process stopped.
+
+ Two things close that gap. A stage variable records how far teardown has
+ progressed, and a watchdog thread, armed on entry to test_control_return()
+ and stood down once the next test is about to be dispatched, reports the
+ last stage reached along with the scheduler state, then exits non-zero.
+
+ The watchdog covers teardown only, never the test body. Several tests here
+ wait on a probabilistic interrupt window and have no bounded runtime:
+ threadx_thread_wait_abort_and_isr_test has been observed taking anywhere
+ from 0.34 to 439 seconds while passing. Teardown is a fixed amount of work
+ that takes milliseconds, so bounding it cannot turn a slow pass into a
+ failure.
+
+ The report is written directly to the standard-error descriptor rather
+ than with printf() deliberately. A wedged thread may hold the stdio lock,
+ and a watchdog that blocked on that lock would reproduce the silent timeout
+ it exists to replace.
+
+ TX_TEST_TEARDOWN_TIMEOUT overrides the bound, in seconds; zero disables the
+ watchdog. TX_TEST_TEARDOWN_TRACE echoes every stage as it is reached and
+ puts stdout in line-buffered mode so the surrounding output survives too.
+ --------------------------------------------------------------------------- */
+
+#define TEST_TEARDOWN_TIMEOUT_DEFAULT 60 /* Seconds allowed for teardown. */
+#define TEST_TEARDOWN_POLL_MS 250 /* Watchdog poll interval. */
+#define TEST_TEARDOWN_WALK_LIMIT 100000 /* Cap on the unbounded walk in cleanup. */
+
+static const char * volatile test_teardown_stage_name = "test body";
+static volatile UINT test_teardown_armed = TX_FALSE;
+static UINT test_teardown_timeout = TEST_TEARDOWN_TIMEOUT_DEFAULT;
+static UINT test_teardown_trace = TX_FALSE;
+
+#ifdef _WIN32
+#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_win32_deferred_preempt)
+#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_win32_suspension_type)
+#else
+#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_linux_deferred_preempt)
+#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_linux_suspension_type)
+#endif
+
+
+/* Write diagnostics directly to standard error without taking the stdio lock. */
+static void test_teardown_write(const char *buffer, size_t length)
+{
+
+#ifdef _WIN32
+ (void) _write(2, buffer, (unsigned int) length);
+#else
+ (void) write(2, buffer, length);
+#endif
+}
+
+
+/* Record how far teardown has progressed, and echo it when tracing is on. */
+static void test_teardown_stage(const char *stage)
+{
+
+ test_teardown_stage_name = stage;
+
+ if (test_teardown_trace != TX_FALSE)
+ {
+ test_teardown_write("[teardown] ", 11u);
+ test_teardown_write(stage, strlen(stage));
+ test_teardown_write("\n", 1u);
+ }
+}
+
+
+/* Name a thread for the report, without dereferencing a null pointer. */
+static const char *test_teardown_thread_name(TX_THREAD *thread_ptr)
+{
+
+ if (thread_ptr == TX_NULL)
+ return "(none)";
+
+ if (thread_ptr -> tx_thread_name == TX_NULL)
+ return "(unnamed)";
+
+ return thread_ptr -> tx_thread_name;
+}
+
+
+/* Write out the state that matters for a teardown hang. */
+static void test_teardown_report(const char *reason)
+{
+
+char buffer[512];
+int length;
+UINT core;
+UINT count;
+TX_THREAD *thread_ptr;
+
+
+ length = snprintf(buffer, sizeof(buffer),
+ "\n**** TEARDOWN WATCHDOG: %s ****\n"
+ " last stage reached: %s\n"
+ " _tx_thread_preempt_disable: %u\n"
+ " _tx_thread_created_count: %lu\n",
+ reason, test_teardown_stage_name,
+ (UINT) _tx_thread_preempt_disable,
+ (ULONG) _tx_thread_created_count);
+ if (length > 0)
+ test_teardown_write(buffer, (size_t) length);
+
+ for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++)
+ {
+
+ length = snprintf(buffer, sizeof(buffer),
+ " core %u: system_state=%lu time_slice=%lu current=%s execute=%s\n",
+ core, (ULONG) _tx_thread_system_state[core],
+ (ULONG) _tx_timer_time_slice[core],
+ test_teardown_thread_name(_tx_thread_current_ptr[core]),
+ test_teardown_thread_name(_tx_thread_execute_ptr[core]));
+ if (length > 0)
+ test_teardown_write(buffer, (size_t) length);
+ }
+
+ /* Walk the created list, stopping at the head, and after a fixed number of
+ entries, so a corrupt list cannot trap us in here. */
+ thread_ptr = _tx_thread_created_ptr;
+ for (count = 0; (thread_ptr != TX_NULL) && (count < ((UINT) 64)); count++)
+ {
+
+ length = snprintf(buffer, sizeof(buffer),
+ " thread %-24s state=%u priority=%u threshold=%u inherit=%u "
+ "core_mapped=%u core_control=%lu deferred_preempt=%u suspension=%u\n",
+ test_teardown_thread_name(thread_ptr),
+ thread_ptr -> tx_thread_state,
+ thread_ptr -> tx_thread_priority,
+ thread_ptr -> tx_thread_preempt_threshold,
+ thread_ptr -> tx_thread_inherit_priority,
+ thread_ptr -> tx_thread_smp_core_mapped,
+ (ULONG) thread_ptr -> tx_thread_smp_core_control,
+ TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr),
+ TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr));
+ if (length > 0)
+ test_teardown_write(buffer, (size_t) length);
+
+ thread_ptr = thread_ptr -> tx_thread_created_next;
+ if (thread_ptr == _tx_thread_created_ptr)
+ break;
+ }
+}
+
+
+/* Report and leave. Used by the watchdog and by the bounded walk in cleanup. */
+static void test_teardown_abort(const char *reason)
+{
+
+ test_teardown_report(reason);
+ _exit(99);
+}
+
+
+/* Watch an armed teardown, and only an armed one. */
+#ifdef _WIN32
+static unsigned __stdcall test_teardown_watchdog(void *input)
+{
+
+ULONG waited_ms;
+ULONG limit_ms;
+
+
+ (void) input;
+ limit_ms = ((ULONG) test_teardown_timeout) * ((ULONG) 1000);
+ waited_ms = ((ULONG) 0);
+
+ while (1)
+ {
+
+ Sleep((DWORD) TEST_TEARDOWN_POLL_MS);
+
+ /* Nothing to watch while teardown is stood down. */
+ if (test_teardown_armed == TX_FALSE)
+ {
+ waited_ms = ((ULONG) 0);
+ }
+ else
+ {
+ waited_ms = waited_ms + ((ULONG) TEST_TEARDOWN_POLL_MS);
+ if (waited_ms >= limit_ms)
+ {
+ test_teardown_abort("teardown did not complete");
+ }
+ }
+ }
+
+ return 0u;
+}
+#else
+static void *test_teardown_watchdog(void *input)
+{
+
+sigset_t mask;
+struct timespec interval;
+ULONG waited_ms;
+ULONG limit_ms;
+
+
+ /* The port drives thread suspend and resume with SIGUSR1 and SIGUSR2.
+ Block both here so this thread never takes one. */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ sigaddset(&mask, SIGUSR2);
+ pthread_sigmask(SIG_BLOCK, &mask, TX_NULL);
+
+ limit_ms = ((ULONG) test_teardown_timeout) * ((ULONG) 1000);
+ waited_ms = ((ULONG) 0);
+
+ while (1)
+ {
+
+ interval.tv_sec = 0;
+ interval.tv_nsec = ((long) TEST_TEARDOWN_POLL_MS) * 1000000L;
+ while (nanosleep(&interval, &interval) != 0)
+ {
+ if (errno != EINTR)
+ break;
+ }
+
+ /* Nothing to watch while teardown is stood down. */
+ if (test_teardown_armed == TX_FALSE)
+ {
+ waited_ms = ((ULONG) 0);
+ continue;
+ }
+
+ waited_ms = waited_ms + ((ULONG) TEST_TEARDOWN_POLL_MS);
+ if (waited_ms >= limit_ms)
+ test_teardown_abort("teardown did not complete");
+ }
+
+ return input;
+}
+#endif
+
+
+/* Start the watchdog, stood down. Called before any test runs, so that no
+ thread is created inside the window under investigation. */
+static void test_teardown_watchdog_start(void)
+{
+
+#ifdef _WIN32
+uintptr_t watchdog_id;
+#else
+pthread_t watchdog_id;
+#endif
+char *value;
+
+
+ value = getenv("TX_TEST_TEARDOWN_TIMEOUT");
+ if (value != TX_NULL)
+ test_teardown_timeout = (UINT) atoi(value);
+
+ if (getenv("TX_TEST_TEARDOWN_TRACE") != TX_NULL)
+ {
+
+ /* Tracing wants the stages in the log next to the output around them,
+ which block buffering would otherwise discard on a kill. */
+ test_teardown_trace = TX_TRUE;
+#ifdef _WIN32
+ setvbuf(stdout, TX_NULL, _IONBF, 0u);
+#else
+ setvbuf(stdout, TX_NULL, _IOLBF, 0);
+#endif
+ }
+
+ /* A timeout of zero turns the watchdog off. */
+ if (test_teardown_timeout == ((UINT) 0))
+ return;
+
+#ifdef _WIN32
+ watchdog_id = _beginthreadex(TX_NULL, 0u, test_teardown_watchdog, TX_NULL, 0u, TX_NULL);
+ if (watchdog_id != ((uintptr_t) 0))
+ {
+ (void) CloseHandle((HANDLE) watchdog_id);
+ }
+#else
+ if (pthread_create(&watchdog_id, TX_NULL, test_teardown_watchdog, TX_NULL) == 0)
+ pthread_detach(watchdog_id);
+#endif
+}
+
+
+static void test_teardown_arm(const char *stage)
+{
+
+ test_teardown_stage(stage);
+ test_teardown_armed = TX_TRUE;
+}
+
+
+static void test_teardown_stand_down(void)
+{
+
+ test_teardown_armed = TX_FALSE;
+ test_teardown_stage("test body");
+}
+
/* Define the test control thread. This thread is responsible for dispatching all of the
tests in the ThreadX test suite. */
@@ -1346,6 +1679,9 @@ UINT i;
/* Raise the priority of the control thread to 0. */
tx_thread_priority_change(&test_control_thread, 0, &i);
+
+ /* Bring up the teardown watchdog, stood down for now. */
+ test_teardown_watchdog_start();
#ifdef CTEST
test_control_cleanup();
#endif
@@ -1364,6 +1700,10 @@ UINT i;
while (test_control_tests[i].test_entry != TX_NULL)
{
+ /* Teardown of the previous test is done. The watchdog covers teardown
+ only, so stand it down before dispatching the next test. */
+ test_teardown_stand_down();
+
/* Clear the ISR dispatch. */
test_isr_dispatch = TX_NULL;
@@ -1381,12 +1721,15 @@ UINT i;
tx_thread_suspend(&test_control_thread);
/* Test finished, cleanup in preparation for the next test. */
+ test_teardown_stage("control thread resumed after test");
test_control_cleanup();
}
/* Finished with all tests, print results and return! */
+ test_teardown_stage("all tests dispatched, printing summary");
printf("**** Testing Complete ****\n");
printf("**** Test Summary: Tests Passed: %lu Tests Failed: %lu System Errors: %lu\n", test_control_successful_tests, test_control_failed_tests, test_control_system_errors);
+ test_teardown_stage("calling exit()");
#ifndef EXTERNAL_EXIT
exit(test_control_failed_tests + test_control_system_errors);
#else
@@ -1404,6 +1747,10 @@ UINT old_posture = TX_INT_ENABLE;
fflush(stdout);
+ /* This is the last flush before the test either finishes or wedges, so
+ everything from here on is invisible in the log. Arm the watchdog. */
+ test_teardown_arm("test_control_return entered");
+
/* Save the status in a global. */
test_control_return_status = status;
@@ -1456,7 +1803,9 @@ UINT old_posture = TX_INT_ENABLE;
#endif
/* Resume the control thread to fully exit the test. */
+ test_teardown_stage("test_control_return: resuming control thread");
tx_thread_resume(&test_control_thread);
+ test_teardown_stage("test_control_return: control thread resume returned");
}
@@ -1465,8 +1814,11 @@ void test_control_cleanup(void)
TX_MUTEX *mutex_ptr;
TX_THREAD *thread_ptr;
+UINT walk_guard;
+ test_teardown_stage("cleanup: deleting threads");
+
/* Delete all threads, except for timer thread, and test control thread.
This ensures application-owned objects are no longer referenced before
the object cleanup loops below attempt to delete them. */
@@ -1497,9 +1849,18 @@ TX_THREAD *thread_ptr;
break;
/* Move to the thread not protected. */
+ walk_guard = ((UINT) 0);
while ((thread_ptr == &_tx_timer_thread) || (thread_ptr == &test_control_thread))
{
+ /* This walk has no natural bound. Should the created count and the
+ created list ever disagree, it spins here for good, which is one
+ of the shapes the CI timeout could be taking. Say so and stop
+ instead of hanging. */
+ walk_guard = walk_guard + ((UINT) 1);
+ if (walk_guard > ((UINT) TEST_TEARDOWN_WALK_LIMIT))
+ test_teardown_abort("cleanup thread walk did not terminate");
+
/* Yes, move to the next thread. */
thread_ptr = thread_ptr -> tx_thread_created_next;
}
@@ -1512,6 +1873,8 @@ TX_THREAD *thread_ptr;
tx_thread_delete(thread_ptr);
}
+ test_teardown_stage("cleanup: deleting queues");
+
/* Delete all queues. */
while(_tx_queue_created_ptr)
{
@@ -1520,6 +1883,8 @@ TX_THREAD *thread_ptr;
tx_queue_delete(_tx_queue_created_ptr);
}
+ test_teardown_stage("cleanup: deleting semaphores");
+
/* Delete all semaphores. */
while(_tx_semaphore_created_ptr)
{
@@ -1528,6 +1893,8 @@ TX_THREAD *thread_ptr;
tx_semaphore_delete(_tx_semaphore_created_ptr);
}
+ test_teardown_stage("cleanup: deleting event flag groups");
+
/* Delete all event flag groups. */
while(_tx_event_flags_created_ptr)
{
@@ -1536,6 +1903,8 @@ TX_THREAD *thread_ptr;
tx_event_flags_delete(_tx_event_flags_created_ptr);
}
+ test_teardown_stage("cleanup: deleting byte pools");
+
/* Delete all byte pools. */
while(_tx_byte_pool_created_ptr)
{
@@ -1544,6 +1913,8 @@ TX_THREAD *thread_ptr;
tx_byte_pool_delete(_tx_byte_pool_created_ptr);
}
+ test_teardown_stage("cleanup: deleting block pools");
+
/* Delete all block pools. */
while(_tx_block_pool_created_ptr)
{
@@ -1552,6 +1923,8 @@ TX_THREAD *thread_ptr;
tx_block_pool_delete(_tx_block_pool_created_ptr);
}
+ test_teardown_stage("cleanup: deleting timers");
+
/* Delete all timers. */
while(_tx_timer_created_ptr)
{
@@ -1563,6 +1936,8 @@ TX_THREAD *thread_ptr;
tx_timer_delete(_tx_timer_created_ptr);
}
+ test_teardown_stage("cleanup: deleting mutexes");
+
/* Delete all mutexes (except for system mutex). */
while(_tx_mutex_created_ptr)
{
@@ -1589,6 +1964,8 @@ TX_THREAD *thread_ptr;
tx_mutex_delete(mutex_ptr);
}
+ test_teardown_stage("cleanup: complete");
+
/* At this point, only the test control thread and the system timer thread and/or mutex should still be
in the system. */
}
diff --git a/test/smp/regression/threadx_block_memory_basic_test.c b/test/smp/regression/threadx_block_memory_basic_test.c
index 28edee3f..27990f88 100644
--- a/test/smp/regression/threadx_block_memory_basic_test.c
+++ b/test/smp/regression/threadx_block_memory_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test simple memory block pool creation, deletion, and
allocates and releases. */
@@ -72,7 +74,7 @@ void test_control_return(UINT status);
/* Prototype direct call to block pool core service. */
-UINT _tx_block_pool_create(TX_BLOCK_POOL *pool_ptr, CHAR *name_ptr, ULONG block_size,
+UINT _tx_block_pool_create(TX_BLOCK_POOL *pool_ptr, TX_NAME_CONST CHAR *name_ptr, ULONG block_size,
VOID *pool_start, ULONG pool_size);
diff --git a/test/smp/regression/threadx_block_memory_error_detection_test.c b/test/smp/regression/threadx_block_memory_error_detection_test.c
index 41148120..b0303b76 100644
--- a/test/smp/regression/threadx_block_memory_error_detection_test.c
+++ b/test/smp/regression/threadx_block_memory_error_detection_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test error detection for simple memory block operations. */
#include <stdio.h>
@@ -29,7 +31,7 @@ static CHAR *pointer;
static void thread_0_entry(ULONG thread_input);
-UINT _txe_block_pool_create(TX_BLOCK_POOL *pool_ptr, CHAR *name_ptr, ULONG block_size,
+UINT _txe_block_pool_create(TX_BLOCK_POOL *pool_ptr, TX_NAME_CONST CHAR *name_ptr, ULONG block_size,
VOID *pool_start, ULONG pool_size, UINT pool_control_block_size);
diff --git a/test/smp/regression/threadx_block_memory_information_test.c b/test/smp/regression/threadx_block_memory_information_test.c
index 82d57f40..0c04cb7e 100644
--- a/test/smp/regression/threadx_block_memory_information_test.c
+++ b/test/smp/regression/threadx_block_memory_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test block memory information services. */
#include <stdio.h>
@@ -68,7 +70,7 @@ static void thread_5_entry(ULONG thread_input);
static void thread_6_entry(ULONG thread_input);
/* Direct core function to bypass the error checking shell. */
-UINT _tx_block_pool_info_get(TX_BLOCK_POOL *pool_ptr, CHAR **name, ULONG *available_blocks,
+UINT _tx_block_pool_info_get(TX_BLOCK_POOL *pool_ptr, TX_NAME_CONST CHAR **name, ULONG *available_blocks,
ULONG *total_blocks, TX_THREAD **first_suspended,
ULONG *suspended_count, TX_BLOCK_POOL **next_pool);
@@ -227,7 +229,7 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
VOID *pointer;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG available;
ULONG total_blocks;
TX_THREAD *first_suspended;
@@ -369,7 +371,7 @@ ULONG timeouts;
status += tx_block_pool_info_get(&block_pool_0, &name, &available, &total_blocks, &first_suspended, &suspended_count, &next_pool);
/* Check for an error condition. */
- if ((status) || (available != block_pool_0.tx_block_pool_available) || (total_blocks != block_pool_0.tx_block_pool_total) ||
+ if ((status) || (name != block_pool_0.tx_block_pool_name) || (available != block_pool_0.tx_block_pool_available) || (total_blocks != block_pool_0.tx_block_pool_total) ||
(first_suspended != &thread_4) || (suspended_count != block_pool_0.tx_block_pool_suspended_count) || (next_pool != &block_pool_0))
{
diff --git a/test/smp/regression/threadx_block_memory_suspension_timeout_test.c b/test/smp/regression/threadx_block_memory_suspension_timeout_test.c
index 43477285..1477cff7 100644
--- a/test/smp/regression/threadx_block_memory_suspension_timeout_test.c
+++ b/test/smp/regression/threadx_block_memory_suspension_timeout_test.c
@@ -11,7 +11,7 @@
/* This test is designed to test timeouts on suspension on memory block pools. */
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
#include <stdio.h>
#include "tx_api.h"
diff --git a/test/smp/regression/threadx_byte_memory_basic_test.c b/test/smp/regression/threadx_byte_memory_basic_test.c
index 45a2518d..d7c1232e 100644
--- a/test/smp/regression/threadx_byte_memory_basic_test.c
+++ b/test/smp/regression/threadx_byte_memory_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test simple memory byte pool creation, deletion, and
allocates and releases. */
@@ -42,7 +44,7 @@ extern UINT test_byte_pool_create_init;
extern VOID (*test_isr_dispatch)(void);
-UINT _txe_byte_pool_create(TX_BYTE_POOL *pool_ptr, CHAR *name_ptr, VOID *pool_start,
+UINT _txe_byte_pool_create(TX_BYTE_POOL *pool_ptr, TX_NAME_CONST CHAR *name_ptr, VOID *pool_start,
ULONG pool_size, UINT pool_control_block_size);
diff --git a/test/smp/regression/threadx_byte_memory_information_test.c b/test/smp/regression/threadx_byte_memory_information_test.c
index f1624a65..f65efac3 100644
--- a/test/smp/regression/threadx_byte_memory_information_test.c
+++ b/test/smp/regression/threadx_byte_memory_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test byte memory information. */
#include <stdio.h>
@@ -226,7 +228,7 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
VOID *pointer;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG available;
ULONG fragments;
TX_THREAD *first_suspended;
@@ -369,7 +371,7 @@ ULONG timeouts;
status += tx_byte_pool_info_get(&byte_pool_0, &name, &available, &fragments, &first_suspended, &suspended_count, &next_pool);
/* Check the status. */
- if ((status != TX_SUCCESS) || (available != byte_pool_0.tx_byte_pool_available) || (fragments != byte_pool_0.tx_byte_pool_fragments) ||
+ if ((status != TX_SUCCESS) || (name != byte_pool_0.tx_byte_pool_name) || (available != byte_pool_0.tx_byte_pool_available) || (fragments != byte_pool_0.tx_byte_pool_fragments) ||
(first_suspended != &thread_4) || (suspended_count != byte_pool_0.tx_byte_pool_suspended_count) || (next_pool != &byte_pool_0))
{
diff --git a/test/smp/regression/threadx_byte_memory_long_search_test.c b/test/smp/regression/threadx_byte_memory_long_search_test.c
new file mode 100644
index 00000000..8f076335
--- /dev/null
+++ b/test/smp/regression/threadx_byte_memory_long_search_test.c
@@ -0,0 +1,337 @@
+/***************************************************************************
+ * 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 makes _tx_byte_pool_search walk a long chain of fragments, from
+ more than one core at a time.
+
+ The SMP version of that function carries two things its ThreadX counterpart
+ does not, and neither was covered.
+
+ The first is the block-search limit. Under TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH
+ -- which this port's tx_user.h sets to 20 -- the search holds protection
+ across at most twenty blocks and then deliberately drops and retakes it, so
+ that a long search cannot lock the other cores out for the whole walk.
+ Nothing in the suite ever made one search examine twenty blocks: every byte
+ pool it builds has a handful of fragments at most. That is what this test
+ fixes.
+
+ Having dropped protection, the search then checks whether some other thread
+ claimed the pool while it was let go, and if so spins for
+ TX_BYTE_POOL_DELAY_VALUE iterations before taking ownership back and
+ restarting the walk. That path this test does not reach, and the reason is a
+ property of the port rather than of the test: the Linux SMP port serialises
+ every core on one pthread mutex, and the window here is an unlock
+ immediately followed by a lock on that mutex. A thread spinning for the
+ protection on another core has to win that handoff, and glibc hands an
+ uncontended mutex straight back to the thread that just released it.
+ Measured: 180,003 windows over three cores, and the pool changed hands in
+ none of them. So tx_byte_pool_search.c lines 283 to 301 stay uncovered on
+ this port, and no number of iterations here will change that -- 250 is used
+ below rather than the tens of thousands that were tried, because the twenty-
+ block threshold above is crossed by the very first search and the rest buys
+ nothing.
+
+ The pool is therefore filled with small allocations until it refuses another
+ one, and every second allocation is then released. That leaves upwards of
+ twenty free fragments, each too small to satisfy the request and none of
+ them adjacent to another, so the search can neither stop early nor merge its
+ way out of the walk. Every request is expected to fail with TX_NO_MEMORY;
+ the failure is the point, because a search that fails is a search that
+ walked the whole pool.
+
+ The layout is asserted rather than assumed. If a change to the pool
+ arithmetic ever made the request satisfiable, or left fewer fragments than
+ the twenty-block threshold needs, this test would still pass while quietly
+ covering nothing -- so it checks the fragment count and checks that the
+ request really does fail before the threads start.
+
+ The three worker threads are deliberately not confined to a core, so that
+ the walk really is driven from more than one core at a time. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "threadx_test_port.h"
+
+
+void test_control_return(UINT status);
+
+
+/* Small chunks, every second one given back. The pool is filled until it
+ refuses another chunk rather than a fixed number of times, so that the
+ layout does not depend on this test agreeing with the allocator about the
+ per-block overhead -- get that wrong by one word and the pool is left with a
+ free tail large enough to satisfy the request below, and the search stops at
+ the first fragment instead of walking. */
+
+#define CHUNK_BYTES ((ULONG) 8)
+#define CHUNK_LIMIT ((UINT) 96)
+#define MINIMUM_CHUNKS ((UINT) 40)
+
+/* Larger than any one free fragment and smaller than the pool's theoretical
+ capacity, which is what makes the search walk rather than refuse at the
+ door: _tx_byte_pool_search returns immediately when the request is at least
+ the theoretical total, without examining a single block. */
+
+#define REQUEST_BYTES ((ULONG) 64)
+
+/* TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH is 20 on this port. The walk has to be
+ longer than that for the protection to be dropped at all. */
+
+#define MINIMUM_FRAGMENTS ((ULONG) 24)
+
+/* Each worker searches this many times. One search is enough for the coverage;
+ the repetition is here so that the pool is genuinely walked from several
+ cores at once rather than by one thread at a time, which is the condition
+ the code under test was written for. */
+
+#define SEARCHES_PER_THREAD ((ULONG) 250)
+
+#define WORKERS ((UINT) 3)
+
+
+static TX_THREAD thread_0;
+static TX_THREAD worker[WORKERS];
+
+static TX_BYTE_POOL pool_0;
+
+static UCHAR pool_area[TX_TEST_BYTE_POOL_CAPACITY_BYTES(CHUNK_BYTES, 48)];
+
+static void *chunk[CHUNK_LIMIT];
+static UINT chunk_count;
+
+static volatile ULONG workers_done = 0;
+static volatile ULONG search_count[WORKERS];
+
+static UINT error = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void worker_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_byte_memory_long_search_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+UINT i;
+TX_NAME_CONST CHAR *name;
+ULONG available;
+ULONG fragments;
+void *probe_ptr;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ status = tx_byte_pool_create(&pool_0, "long search pool", pool_area, sizeof(pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #1\n");
+ test_control_return(1);
+ return;
+ }
+
+ /* Fill the pool completely with small chunks, so that no free tail is left
+ behind that could satisfy the request. */
+ chunk_count = ((UINT) 0);
+ while (chunk_count < CHUNK_LIMIT)
+ {
+
+ status = tx_byte_allocate(&pool_0, &chunk[chunk_count], CHUNK_BYTES, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ break;
+ }
+
+ chunk_count++;
+ }
+
+ if ((chunk_count < MINIMUM_CHUNKS) || (chunk_count == CHUNK_LIMIT))
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #2\n");
+ test_control_return(1);
+ return;
+ }
+
+ /* Give every second one back. Alternating rather than consecutive, so that
+ no free fragment has a free neighbour and the search cannot merge two of
+ them into a block big enough to satisfy the request. */
+ for (i = 1; i < chunk_count; i = i + ((UINT) 2))
+ {
+
+ status = tx_byte_release(chunk[i]);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #3\n");
+ test_control_return(1);
+ return;
+ }
+
+ chunk[i] = TX_NULL;
+ }
+
+ /* The walk has to be long enough to cross the twenty-block threshold. */
+ status = tx_byte_pool_info_get(&pool_0, &name, &available, &fragments, TX_NULL, TX_NULL, TX_NULL);
+
+ if ((status != TX_SUCCESS) || (fragments < MINIMUM_FRAGMENTS))
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #4\n");
+ test_control_return(1);
+ return;
+ }
+
+ /* And the request has to fail, or the search stops at the first fragment
+ that fits and the walk never happens. */
+ status = tx_byte_allocate(&pool_0, &probe_ptr, REQUEST_BYTES, TX_NO_WAIT);
+
+ if (status != TX_NO_MEMORY)
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #5\n");
+ test_control_return(1);
+ return;
+ }
+
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #6\n");
+ test_control_return(1);
+ return;
+ }
+
+ /* The workers share one priority so that the scheduler is free to put them
+ on different cores, which is the whole point. */
+ for (i = 0; i < WORKERS; i++)
+ {
+
+ search_count[i] = ((ULONG) 0);
+
+ status = tx_thread_create(&worker[i], "worker", worker_entry, (ULONG) i,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Byte Memory Long Search Test................................ ERROR #7\n");
+ test_control_return(1);
+ }
+ }
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT i;
+TX_NAME_CONST CHAR *name;
+ULONG available;
+ULONG fragments;
+
+
+ /* Inform user. */
+ printf("Running Byte Memory Long Search Test................................ ");
+
+ /* Wait for the workers. They do not sleep, so this thread has to. */
+ while (workers_done != ((ULONG) WORKERS))
+ {
+
+ tx_thread_sleep(1);
+ }
+
+ for (i = 0; i < WORKERS; i++)
+ {
+
+ if (search_count[i] != SEARCHES_PER_THREAD)
+ {
+
+ error++;
+ }
+ }
+
+ /* Every search failed, so nothing was taken and nothing was merged: the
+ pool must look exactly as it did when the workers started. */
+ status = tx_byte_pool_info_get(&pool_0, &name, &available, &fragments, TX_NULL, TX_NULL, TX_NULL);
+
+ if ((status != TX_SUCCESS) || (fragments < MINIMUM_FRAGMENTS))
+ {
+
+ error++;
+ }
+
+ if (error)
+ {
+
+ printf("ERROR #8\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ /* Successful test. */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void worker_entry(ULONG thread_input)
+{
+
+UINT status;
+ULONG i;
+void *memory_ptr;
+
+
+ for (i = 0; i < SEARCHES_PER_THREAD; i++)
+ {
+
+ status = tx_byte_allocate(&pool_0, &memory_ptr, REQUEST_BYTES, TX_NO_WAIT);
+
+ if (status != TX_NO_MEMORY)
+ {
+
+ error++;
+ break;
+ }
+
+ search_count[thread_input]++;
+ }
+
+ workers_done++;
+}
diff --git a/test/smp/regression/threadx_byte_memory_suspension_timeout_test.c b/test/smp/regression/threadx_byte_memory_suspension_timeout_test.c
index c92f2be0..aaf1ba51 100644
--- a/test/smp/regression/threadx_byte_memory_suspension_timeout_test.c
+++ b/test/smp/regression/threadx_byte_memory_suspension_timeout_test.c
@@ -11,7 +11,7 @@
/* This test is designed to test suspension timeout on a memory byte pool. */
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
#include <stdio.h>
#include "tx_api.h"
diff --git a/test/smp/regression/threadx_event_flag_basic_test.c b/test/smp/regression/threadx_event_flag_basic_test.c
index 8ab990f7..fed424a6 100644
--- a/test/smp/regression/threadx_event_flag_basic_test.c
+++ b/test/smp/regression/threadx_event_flag_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test simple event flag group creation, deletion, gets and
sets. */
@@ -57,7 +59,7 @@ static TX_EVENT_FLAGS_GROUP group_3;
static void thread_0_entry(ULONG thread_input);
static void thread_1_entry(ULONG thread_input);
-UINT _txe_event_flags_create(TX_EVENT_FLAGS_GROUP *group_ptr, CHAR *name_ptr, UINT event_control_block_size);
+UINT _txe_event_flags_create(TX_EVENT_FLAGS_GROUP *group_ptr, TX_NAME_CONST CHAR *name_ptr, UINT event_control_block_size);
/* Prototype for test control return. */
diff --git a/test/smp/regression/threadx_event_flag_information_test.c b/test/smp/regression/threadx_event_flag_information_test.c
index 45e547f9..1c7cfd2d 100644
--- a/test/smp/regression/threadx_event_flag_information_test.c
+++ b/test/smp/regression/threadx_event_flag_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the event flag group information gathering services. */
#include <stdio.h>
@@ -134,7 +136,7 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
ULONG actual_events;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG current_flags;
TX_THREAD *first_suspended;
ULONG suspended_count;
@@ -356,7 +358,7 @@ ULONG timeouts;
status += tx_event_flags_info_get(&group_0, &name, &current_flags, &first_suspended, &suspended_count, &next_group);
/* Check the status. */
- if ((status != TX_SUCCESS) || (current_flags != group_0.tx_event_flags_group_current) || (first_suspended != TX_NULL) || (suspended_count != 0) || (next_group != &group_1))
+ if ((status != TX_SUCCESS) || (name != group_0.tx_event_flags_group_name) || (current_flags != group_0.tx_event_flags_group_current) || (first_suspended != TX_NULL) || (suspended_count != 0) || (next_group != &group_1))
{
/* Event flag error. */
diff --git a/test/smp/regression/threadx_mutex_basic_test.c b/test/smp/regression/threadx_mutex_basic_test.c
index bb81eb9a..5a652843 100644
--- a/test/smp/regression/threadx_mutex_basic_test.c
+++ b/test/smp/regression/threadx_mutex_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the mutex create/delete and immediate
return gets and puts. */
@@ -69,7 +71,7 @@ static void thread_2_entry(ULONG thread_input);
static void thread_3_entry(ULONG thread_input);
static void thread_4_entry(ULONG thread_input);
-UINT _txe_mutex_create(TX_MUTEX *mutex_ptr, CHAR *name_ptr, UINT inherit, UINT mutex_control_block_size);
+UINT _txe_mutex_create(TX_MUTEX *mutex_ptr, TX_NAME_CONST CHAR *name_ptr, UINT inherit, UINT mutex_control_block_size);
/* Prototype for test control return. */
diff --git a/test/smp/regression/threadx_mutex_information_test.c b/test/smp/regression/threadx_mutex_information_test.c
index 3829122c..fea78c04 100644
--- a/test/smp/regression/threadx_mutex_information_test.c
+++ b/test/smp/regression/threadx_mutex_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the mutex information services. */
#include <stdio.h>
@@ -134,7 +136,7 @@ static void thread_0_entry(ULONG thread_input)
{
UINT status;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG count;
TX_THREAD *owner;
TX_THREAD *first_suspended;
@@ -300,7 +302,7 @@ ULONG inheritances;
status += tx_mutex_info_get(&mutex_2, &name, &count, &owner, &first_suspended, &suspended_count, &next_mutex);
/* Check status. */
- if ((status != TX_SUCCESS) || (count != mutex_2.tx_mutex_ownership_count) || (owner != mutex_2.tx_mutex_owner) ||
+ if ((status != TX_SUCCESS) || (name != mutex_2.tx_mutex_name) || (count != mutex_2.tx_mutex_ownership_count) || (owner != mutex_2.tx_mutex_owner) ||
(first_suspended != mutex_2.tx_mutex_suspension_list) || (suspended_count != mutex_2.tx_mutex_suspended_count) || (next_mutex != mutex_2.tx_mutex_created_next))
{
diff --git a/test/smp/regression/threadx_object_delete_suspension_test.c b/test/smp/regression/threadx_object_delete_suspension_test.c
new file mode 100644
index 00000000..20633f08
--- /dev/null
+++ b/test/smp/regression/threadx_object_delete_suspension_test.c
@@ -0,0 +1,481 @@
+/***************************************************************************
+ * 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 deletes a block pool, a byte pool, an event flags group and a
+ queue while two threads are suspended on each of them, and checks that every
+ suspended thread is released with TX_DELETED.
+
+ Each of those four delete routines carries the same loop, and in this suite
+ the whole loop body was uncovered -- thirty-six lines across the four files,
+ the largest single gap in the SMP coverage report:
+
+ while (suspended_count != TX_NO_SUSPENSIONS)
+ {
+ suspended_count--;
+ TX_DISABLE
+ thread_ptr -> tx_thread_suspend_cleanup = TX_NULL;
+ thread_ptr -> tx_thread_suspend_status = TX_DELETED;
+ next_thread = thread_ptr -> tx_thread_suspended_next;
+ ...
+ _tx_thread_system_resume(thread_ptr);
+ thread_ptr = next_thread;
+ }
+
+ The reason it was uncovered is worth writing down, because it is not that
+ the suite never deletes these objects -- it deletes all four after every
+ single test. test_control_cleanup() in the ThreadX suite deletes the
+ application's objects first and its threads last, so a test that ends with a
+ thread parked on a queue has that thread walked out of the queue by
+ tx_queue_delete. The SMP suite's cleanup deletes the threads first, on
+ purpose: it was changed so that no application-owned object is still
+ referenced when the object loops run, which is what stopped a class of
+ teardown hang. Correct, and the side effect is that every one of those
+ deletes now runs against an empty suspension list.
+
+ So the coverage has to come from a test that means to delete a busy object
+ rather than from teardown that happens to. threadx_semaphore_delete_test
+ already does exactly this for semaphores, which is why tx_semaphore_delete.c
+ is the one member of the family that was fully covered; this is the same
+ idea for the other four.
+
+ Two waiters per object rather than one, so the loop runs twice and its back
+ edge is taken as well as its body.
+
+ The threads are not confined to a core. On this port they start on separate
+ cores and suspend in whatever order they get scheduled, so the control
+ thread waits on the object's own suspended count through tx_*_info_get
+ rather than on an ordering it cannot guarantee. The wait is bounded: a
+ suspension that never arrives fails the test instead of hanging it. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "threadx_test_port.h"
+
+
+void test_control_return(UINT status);
+
+
+/* The four objects, deleted in this order. */
+
+#define STAGE_BLOCK_POOL ((UINT) 0)
+#define STAGE_BYTE_POOL ((UINT) 1)
+#define STAGE_EVENT_FLAGS ((UINT) 2)
+#define STAGE_QUEUE ((UINT) 3)
+#define STAGE_COUNT ((UINT) 4)
+
+/* Number of threads that suspend on each object. */
+
+#define WAITERS ((ULONG) 2)
+
+/* How many ticks the control thread will wait for a suspension before giving
+ up. Generous, because it is only ever reached when the test is failing --
+ and finite, because the alternative is a run that hangs instead of one that
+ reports. */
+
+#define WAIT_LIMIT_TICKS ((UINT) 200)
+
+
+static TX_THREAD thread_0;
+static TX_THREAD thread_1;
+static TX_THREAD thread_2;
+
+static TX_BLOCK_POOL block_pool_0;
+static TX_BYTE_POOL byte_pool_0;
+static TX_EVENT_FLAGS_GROUP group_0;
+static TX_QUEUE queue_0;
+
+/* Eight blocks of 16 bytes, all of them taken before any waiter runs. */
+
+static UCHAR block_pool_area[TX_TEST_BLOCK_POOL_BYTES(16, 8)];
+static UCHAR byte_pool_area[TX_TEST_BYTE_POOL_BYTES(256)];
+static ULONG queue_area[16];
+
+/* Counts of waiters that saw TX_DELETED, one entry per stage. */
+
+static volatile ULONG deleted_count[STAGE_COUNT];
+
+static UINT error = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void waiter_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_object_delete_suspension_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+void *block_ptr;
+void *bytes_ptr;
+UINT i;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ for (i = 0; i < STAGE_COUNT; i++)
+ {
+
+ deleted_count[i] = ((ULONG) 0);
+ }
+
+ /* Create the block pool and take every block, so that a waiter has nothing
+ to be given and suspends instead. */
+ status = tx_block_pool_create(&block_pool_0, "delete suspension block pool",
+ 16, block_pool_area, sizeof(block_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #1\n");
+ test_control_return(1);
+ }
+
+ while (tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT) == TX_SUCCESS)
+ {
+
+ /* Nothing to do with the block; the point is that the pool is empty. */
+ }
+
+ /* Same idea for the byte pool: take enough of it that the 128-byte requests
+ below cannot be met. */
+ status = tx_byte_pool_create(&byte_pool_0, "delete suspension byte pool",
+ byte_pool_area, sizeof(byte_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #2\n");
+ test_control_return(1);
+ }
+
+ status = tx_byte_allocate(&byte_pool_0, &bytes_ptr, 200, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #3\n");
+ test_control_return(1);
+ }
+
+ /* An event flags group with nothing set. The waiters ask for a flag that is
+ never set by anybody. */
+ status = tx_event_flags_create(&group_0, "delete suspension group");
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #4\n");
+ test_control_return(1);
+ }
+
+ /* An empty queue. The waiters receive from it and nobody ever sends. */
+ status = tx_queue_create(&queue_0, "delete suspension queue", TX_1_ULONG,
+ queue_area, sizeof(queue_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #5\n");
+ test_control_return(1);
+ }
+
+ /* The control thread runs at a higher priority than the waiters, but that
+ decides nothing here -- it waits on the suspended counts. */
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #6\n");
+ test_control_return(1);
+ }
+
+ status = tx_thread_create(&thread_1, "thread 1", waiter_entry, 1,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #7\n");
+ test_control_return(1);
+ }
+
+ status = tx_thread_create(&thread_2, "thread 2", waiter_entry, 2,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #8\n");
+ test_control_return(1);
+ }
+}
+
+
+/* Read the number of threads suspended on the object belonging to a stage.
+ Returns TX_TRUE when the count could be read, so that a failing info_get is
+ reported rather than mistaken for "nobody has suspended yet". */
+
+static UINT suspended_on(UINT stage, ULONG *count)
+{
+
+UINT status;
+
+
+ /* Every destination but the suspended count is TX_NULL: each info_get
+ tests its destinations individually and skips the ones it is not given,
+ so there is nothing to declare here to throw away. */
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ status = tx_block_pool_info_get(&block_pool_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ status = tx_byte_pool_info_get(&byte_pool_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ status = tx_event_flags_info_get(&group_0, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ default:
+
+ status = tx_queue_info_get(&queue_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+ }
+
+ return((status == TX_SUCCESS) ? ((UINT) TX_TRUE) : ((UINT) TX_FALSE));
+}
+
+
+/* Sleep until every waiter has suspended on this stage's object, or until the
+ budget runs out. Returns TX_TRUE if the waiters arrived. */
+
+static UINT wait_for_waiters(UINT stage)
+{
+
+UINT ticks;
+ULONG count;
+
+
+ count = ((ULONG) 0);
+ for (ticks = 0; ticks < WAIT_LIMIT_TICKS; ticks++)
+ {
+
+ if (suspended_on(stage, &count) == ((UINT) TX_FALSE))
+ {
+
+ return((UINT) TX_FALSE);
+ }
+
+ if (count == WAITERS)
+ {
+
+ return((UINT) TX_TRUE);
+ }
+
+ tx_thread_sleep(1);
+ }
+
+ return((UINT) TX_FALSE);
+}
+
+
+/* Sleep until every waiter has reported TX_DELETED for this stage, or until the
+ budget runs out. */
+
+static UINT wait_for_reports(UINT stage)
+{
+
+UINT ticks;
+
+
+ for (ticks = 0; ticks < WAIT_LIMIT_TICKS; ticks++)
+ {
+
+ if (deleted_count[stage] == WAITERS)
+ {
+
+ return((UINT) TX_TRUE);
+ }
+
+ tx_thread_sleep(1);
+ }
+
+ return((UINT) TX_FALSE);
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT stage;
+
+
+ /* Inform user. */
+ printf("Running Object Delete Suspension Test............................... ");
+
+ for (stage = 0; stage < STAGE_COUNT; stage++)
+ {
+
+ /* Both waiters have to be on the list before the delete, or the loop
+ under test walks a shorter list than this test means it to. */
+ if (wait_for_waiters(stage) == ((UINT) TX_FALSE))
+ {
+
+ printf("ERROR #9 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ status = tx_block_pool_delete(&block_pool_0);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ status = tx_byte_pool_delete(&byte_pool_0);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ status = tx_event_flags_delete(&group_0);
+ break;
+
+ default:
+
+ status = tx_queue_delete(&queue_0);
+ break;
+ }
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #10 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+
+ /* Every waiter must come out of the delete with TX_DELETED, which is
+ the status the loop under test writes into each suspended thread. */
+ if (wait_for_reports(stage) == ((UINT) TX_FALSE))
+ {
+
+ printf("ERROR #11 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+ }
+
+ if (error)
+ {
+
+ printf("ERROR #12\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ /* Successful test. */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void waiter_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT stage;
+void *block_ptr;
+void *bytes_ptr;
+ULONG actual_flags;
+ULONG message;
+
+
+ for (stage = 0; stage < STAGE_COUNT; stage++)
+ {
+
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ /* The pool was emptied before any thread ran, so this suspends. */
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ /* More than is left after the allocation made at define time. */
+ status = tx_byte_allocate(&byte_pool_0, &bytes_ptr, 128, TX_WAIT_FOREVER);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ /* Nobody ever sets this flag. */
+ status = tx_event_flags_get(&group_0, ((ULONG) 0x1), TX_AND_CLEAR,
+ &actual_flags, TX_WAIT_FOREVER);
+ break;
+
+ default:
+
+ /* Nobody ever sends to this queue. */
+ status = tx_queue_receive(&queue_0, &message, TX_WAIT_FOREVER);
+ break;
+ }
+
+ if (status == TX_DELETED)
+ {
+
+ deleted_count[stage]++;
+ }
+ else
+ {
+
+ /* Anything else means the object was not deleted out from under
+ this thread, and the control thread will say so. */
+ error++;
+ return;
+ }
+ }
+}
diff --git a/test/smp/regression/threadx_queue_basic_one_word_test.c b/test/smp/regression/threadx_queue_basic_one_word_test.c
index 1abb6986..58142471 100644
--- a/test/smp/regression/threadx_queue_basic_one_word_test.c
+++ b/test/smp/regression/threadx_queue_basic_one_word_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test immediate response queue services including create
and delete. This test is for queue sizes of 1 ULONG. Two queues are used one with
a capacity of 1 message and another with a capacity of 3 messages. */
@@ -51,7 +53,7 @@ static void thread_0_entry(ULONG thread_input);
static void thread_1_entry(ULONG thread_input);
-UINT _txe_queue_create(TX_QUEUE *queue_ptr, CHAR *name_ptr, UINT message_size,
+UINT _txe_queue_create(TX_QUEUE *queue_ptr, TX_NAME_CONST CHAR *name_ptr, UINT message_size,
VOID *queue_start, ULONG queue_size, UINT queue_control_block_size);
diff --git a/test/smp/regression/threadx_queue_information_test.c b/test/smp/regression/threadx_queue_information_test.c
index f884bd0d..8929c465 100644
--- a/test/smp/regression/threadx_queue_information_test.c
+++ b/test/smp/regression/threadx_queue_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the queue information services. */
#include <stdio.h>
@@ -102,7 +104,7 @@ UINT status;
ULONG source_message = 0x12345678UL;
ULONG dest_message;
ULONG expected_message;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG enqueued;
ULONG available_storage;
TX_THREAD *first_suspended;
@@ -409,7 +411,7 @@ ULONG timeouts;
status += tx_queue_info_get(&queue_0, &name, &enqueued, &available_storage, &first_suspended, &suspended_count, &next_queue);
/* Check for errors. */
- if ((status != TX_SUCCESS) || (enqueued != queue_0.tx_queue_enqueued) || (available_storage != queue_0.tx_queue_available_storage) ||
+ if ((status != TX_SUCCESS) || (name != queue_0.tx_queue_name) || (enqueued != queue_0.tx_queue_enqueued) || (available_storage != queue_0.tx_queue_available_storage) ||
(first_suspended != queue_0.tx_queue_suspension_list) || (suspended_count != queue_0.tx_queue_suspended_count) ||
(next_queue != queue_0.tx_queue_created_next))
{
diff --git a/test/smp/regression/threadx_semaphore_basic_test.c b/test/smp/regression/threadx_semaphore_basic_test.c
index 3209b586..a1a3d340 100644
--- a/test/smp/regression/threadx_semaphore_basic_test.c
+++ b/test/smp/regression/threadx_semaphore_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the semaphore create/delete and immediate return gets and puts. */
#include <stdio.h>
@@ -62,7 +64,7 @@ static TX_SEMAPHORE semaphore_3;
static void thread_0_entry(ULONG thread_input);
static void thread_1_entry(ULONG thread_input);
-UINT _txe_semaphore_create(TX_SEMAPHORE *semaphore_ptr, CHAR *name_ptr, ULONG initial_count, UINT semaphore_control_block_size);
+UINT _txe_semaphore_create(TX_SEMAPHORE *semaphore_ptr, TX_NAME_CONST CHAR *name_ptr, ULONG initial_count, UINT semaphore_control_block_size);
/* Prototype for test control return. */
diff --git a/test/smp/regression/threadx_semaphore_information_test.c b/test/smp/regression/threadx_semaphore_information_test.c
index ede276e9..c0bb3c72 100644
--- a/test/smp/regression/threadx_semaphore_information_test.c
+++ b/test/smp/regression/threadx_semaphore_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test the semaphore information services. */
#include <stdio.h>
@@ -100,7 +102,7 @@ static void thread_0_entry(ULONG thread_input)
{
UINT status;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
ULONG current_value;
TX_THREAD *first_suspended;
ULONG suspended_count;
@@ -211,7 +213,7 @@ ULONG timeouts;
status += tx_semaphore_info_get(&semaphore_0, &name, &current_value, &first_suspended, &suspended_count, &next_semaphore);
/* Check status. */
- if ((status != TX_SUCCESS) || (current_value != semaphore_0.tx_semaphore_count) ||
+ if ((status != TX_SUCCESS) || (name != semaphore_0.tx_semaphore_name) || (current_value != semaphore_0.tx_semaphore_count) ||
(first_suspended != semaphore_0.tx_semaphore_suspension_list) || (suspended_count != semaphore_0.tx_semaphore_suspended_count) ||
(next_semaphore != semaphore_0.tx_semaphore_created_next))
{
diff --git a/test/smp/regression/threadx_smp_execution_profile_get_test.c b/test/smp/regression/threadx_smp_execution_profile_get_test.c
new file mode 100644
index 00000000..ccfd3b2a
--- /dev/null
+++ b/test/smp/regression/threadx_smp_execution_profile_get_test.c
@@ -0,0 +1,126 @@
+/***************************************************************************
+ * 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 Codex (GPT-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 verifies that the SMP execution profile aggregate getters copy every core value. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "tx_execution_profile.h"
+
+extern EXECUTION_TIME _tx_execution_thread_time_total[TX_THREAD_SMP_MAX_CORES];
+extern EXECUTION_TIME _tx_execution_isr_time_total[TX_THREAD_SMP_MAX_CORES];
+extern EXECUTION_TIME _tx_execution_idle_time_total[TX_THREAD_SMP_MAX_CORES];
+
+ULONG _tx_thread_system_state[TX_THREAD_SMP_MAX_CORES];
+UINT _tx_thread_preempt_disable;
+TX_THREAD *_tx_thread_current_ptr[TX_THREAD_SMP_MAX_CORES];
+TX_THREAD *_tx_thread_execute_ptr[TX_THREAD_SMP_MAX_CORES];
+TX_THREAD *_tx_thread_created_ptr;
+ULONG _tx_thread_created_count;
+
+
+ULONG _tx_thread_smp_time_get(void)
+{
+
+ return(0);
+}
+
+
+static UINT verify_time_values(EXECUTION_TIME *actual, EXECUTION_TIME *expected)
+{
+
+UINT core;
+
+
+ for (core = 0; core < TX_THREAD_SMP_MAX_CORES; core++)
+ {
+
+ if (actual[core] != expected[core])
+ {
+
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+
+int main(void)
+{
+
+EXECUTION_TIME thread_time[TX_THREAD_SMP_MAX_CORES];
+EXECUTION_TIME isr_time[TX_THREAD_SMP_MAX_CORES];
+EXECUTION_TIME idle_time[TX_THREAD_SMP_MAX_CORES];
+EXECUTION_TIME expected_thread_time[TX_THREAD_SMP_MAX_CORES];
+EXECUTION_TIME expected_isr_time[TX_THREAD_SMP_MAX_CORES];
+EXECUTION_TIME expected_idle_time[TX_THREAD_SMP_MAX_CORES];
+UINT core;
+UINT status;
+
+
+ printf("Running SMP Execution Profile Get Test............................... ");
+
+ for (core = 0; core < TX_THREAD_SMP_MAX_CORES; core++)
+ {
+
+ expected_thread_time[core] = ((EXECUTION_TIME) 1000U) + ((EXECUTION_TIME) core);
+ expected_isr_time[core] = ((EXECUTION_TIME) 2000U) + ((EXECUTION_TIME) core);
+ expected_idle_time[core] = ((EXECUTION_TIME) 3000U) + ((EXECUTION_TIME) core);
+
+ _tx_execution_thread_time_total[core] = expected_thread_time[core];
+ _tx_execution_isr_time_total[core] = expected_isr_time[core];
+ _tx_execution_idle_time_total[core] = expected_idle_time[core];
+
+ thread_time[core] = 0xFFFFFFFFFFFFFFFFULL;
+ isr_time[core] = 0xFFFFFFFFFFFFFFFFULL;
+ idle_time[core] = 0xFFFFFFFFFFFFFFFFULL;
+ }
+
+ status = _tx_execution_thread_total_time_get(thread_time);
+ status += _tx_execution_isr_time_get(isr_time);
+ status += _tx_execution_idle_time_get(idle_time);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #1\n");
+ return(1);
+ }
+
+ if (verify_time_values(thread_time, expected_thread_time) != 0U)
+ {
+
+ printf("ERROR #2\n");
+ return(1);
+ }
+
+ if (verify_time_values(isr_time, expected_isr_time) != 0U)
+ {
+
+ printf("ERROR #3\n");
+ return(1);
+ }
+
+ if (verify_time_values(idle_time, expected_idle_time) != 0U)
+ {
+
+ printf("ERROR #4\n");
+ return(1);
+ }
+
+ printf("SUCCESS!\n");
+ return(0);
+}
diff --git a/test/smp/regression/threadx_smp_random_resume_suspend_exclusion_pt_test.c b/test/smp/regression/threadx_smp_random_resume_suspend_exclusion_pt_test.c
index a9ea93ad..d5fe0436 100644
--- a/test/smp/regression/threadx_smp_random_resume_suspend_exclusion_pt_test.c
+++ b/test/smp/regression/threadx_smp_random_resume_suspend_exclusion_pt_test.c
@@ -10,7 +10,7 @@
/***************************************************************************/
/* Define the ThreadX SMP random resume/suspend/exclude/pt test. */
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
#include <stdio.h>
#include "tx_api.h"
@@ -2312,12 +2312,14 @@ UINT status;
exclusions = (ULONG) (rand()%15);
tx_thread_smp_core_exclude(thread_ptr, exclusions);
- /* See if we should setup a preemption-threshold. */
- if ((thread_ptr -> tx_thread_priority > 40) &&
+ /* See if we should setup a preemption-threshold. The thresholds are
+ derived from TX_MAX_PRIORITIES so that the randomised phase sets one
+ whatever the port's priority count is. */
+ if ((thread_ptr -> tx_thread_priority > (TX_MAX_PRIORITIES/2)) &&
(exclusions & 1))
{
/* Change preemption-threshold to enable it. */
- tx_thread_preemption_change(thread_ptr, thread_ptr -> tx_thread_priority - 20, &original_threshold);
+ tx_thread_preemption_change(thread_ptr, thread_ptr -> tx_thread_priority - (TX_MAX_PRIORITIES/4), &original_threshold);
}
/* Save the thread pointer. */
diff --git a/test/smp/regression/threadx_smp_relinquish_rebalance_test.c b/test/smp/regression/threadx_smp_relinquish_rebalance_test.c
new file mode 100644
index 00000000..68a887f9
--- /dev/null
+++ b/test/smp/regression/threadx_smp_relinquish_rebalance_test.c
@@ -0,0 +1,250 @@
+/***************************************************************************
+ * 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
+ **************************************************************************/
+
+/* Define the ThreadX SMP relinquish rebalance test. This test fills every core with
+ threads of one priority that relinquish in a loop, and places two more threads of the
+ same priority behind them in the ready list: one that no core may run, and one that any
+ core may run. Walking the ready list, a relinquish reaches the unrunnable thread first
+ and cannot schedule it, so the execute list has to be rebalanced for the runnable thread
+ to reach a core. The runnable thread never running is the failure this test detects. */
+
+#include <stdio.h>
+#include "tx_api.h"
+
+
+/* Fill the four cores, so a thread behind these in the ready list only reaches a core
+ through the execute list being rebalanced. */
+
+#define SPIN_THREADS 4
+
+
+/* The spinning threads keep their cores whatever else happens, so they carry the deadline.
+ One second is three orders of magnitude more than the handful of relinquishes a healthy
+ system needs, and the tick advances independently of which threads are scheduled. */
+
+#define DEADLINE_TICKS 100
+
+
+static TX_THREAD thread_control;
+static TX_THREAD thread_spin[SPIN_THREADS];
+static TX_THREAD thread_blocked;
+static TX_THREAD thread_starved;
+
+static ULONG spin_counter[SPIN_THREADS];
+static ULONG starved_counter;
+static ULONG blocked_counter;
+static ULONG reported;
+
+
+/* Define thread prototypes. */
+
+static void thread_control_entry(ULONG thread_input);
+static void thread_spin_entry(ULONG thread_input);
+static void thread_blocked_entry(ULONG thread_input);
+static void thread_starved_entry(ULONG thread_input);
+
+
+/* Prototype for test control return. */
+
+void test_control_return(UINT status);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_smp_relinquish_rebalance_test(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+UINT i;
+
+
+ /* Put first available memory address into a character pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ /* Create the control thread, which only resumes the others and then completes. */
+ status = tx_thread_create(&thread_control, "control thread", thread_control_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 0, 0, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_control, 0xE); /* Core 0 only! */
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #1\n");
+ test_control_return(1);
+ }
+
+ /* Create one spinning thread per core. */
+ for (i = 0; i < SPIN_THREADS; i++)
+ {
+
+ status = tx_thread_create(&thread_spin[i], "spin thread", thread_spin_entry, i,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 20, 20, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #2\n");
+ test_control_return(1);
+ }
+ }
+
+ /* Create the thread no core may run. Excluding every core leaves it ready and
+ permanently unmapped, which is what makes it a stable obstacle in the ready list
+ rather than a thread that eventually gets a turn. */
+ status = tx_thread_create(&thread_blocked, "blocked thread", thread_blocked_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 20, 20, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_blocked, 0xF); /* No core at all! */
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #3\n");
+ test_control_return(1);
+ }
+
+ /* Create the thread that any core may run. It is resumed last so that it sits behind
+ the blocked thread in the ready list at this priority. */
+ status = tx_thread_create(&thread_starved, "starved thread", thread_starved_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 20, 20, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #4\n");
+ test_control_return(1);
+ }
+}
+
+
+/* Resume the others in ready list order and complete, so core 0 is released. */
+
+static void thread_control_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT i;
+
+
+ status = TX_SUCCESS;
+
+ /* Fill the cores first. */
+ for (i = 0; i < SPIN_THREADS; i++)
+ {
+
+ status += tx_thread_resume(&thread_spin[i]);
+ }
+
+ /* Then the obstacle, then the thread behind it. */
+ status += tx_thread_resume(&thread_blocked);
+ status += tx_thread_resume(&thread_starved);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #5\n");
+ test_control_return(1);
+ }
+}
+
+
+/* Hold a core and relinquish. These threads keep running whatever the scheduler does with
+ the rest, so they are the ones that can enforce a deadline on the starved thread. */
+
+static void thread_spin_entry(ULONG thread_input)
+{
+
+ULONG start;
+
+
+ start = tx_time_get();
+
+ while (starved_counter == 0)
+ {
+
+ spin_counter[thread_input]++;
+
+ /* The tick advances whether or not any thread is being scheduled, so this bounds
+ the test even when nothing is. */
+ if ((tx_time_get() - start) > DEADLINE_TICKS)
+ {
+
+ if (reported == 0)
+ {
+
+ reported = 1;
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #6\n");
+ test_control_return(1);
+ }
+
+ return;
+ }
+
+ tx_thread_relinquish();
+ }
+}
+
+
+/* Never runs: every core is excluded for this thread. */
+
+static void thread_blocked_entry(ULONG thread_input)
+{
+
+ blocked_counter++;
+}
+
+
+/* Reaching a core at all is the pass condition. */
+
+static void thread_starved_entry(ULONG thread_input)
+{
+
+ starved_counter++;
+
+ if (reported == 0)
+ {
+
+ reported = 1;
+
+ /* The blocked thread must not have run, or the obstacle was not an obstacle and
+ the test proved nothing. */
+ if (blocked_counter != 0)
+ {
+
+ printf("Running SMP Relinquish Rebalance Test............................... ERROR #7\n");
+ test_control_return(1);
+ }
+
+ printf("Running SMP Relinquish Rebalance Test............................... SUCCESS!\n");
+ test_control_return(0);
+ }
+}
diff --git a/test/smp/regression/threadx_smp_thread_exit_callback_remote_test.c b/test/smp/regression/threadx_smp_thread_exit_callback_remote_test.c
new file mode 100644
index 00000000..488c2822
--- /dev/null
+++ b/test/smp/regression/threadx_smp_thread_exit_callback_remote_test.c
@@ -0,0 +1,514 @@
+/***************************************************************************
+ * 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
+ **************************************************************************/
+
+/* Define the ThreadX SMP thread exit callback remote-core termination test.
+
+ This is the SMP-specific half of the exit-callback transition coverage. The
+ deterministic cases are in threadx_thread_exit_callback_transition_test,
+ which confines every thread it drives to core 0 precisely so that they
+ cannot be made flaky by concurrency; this test supplies the case that needs
+ it.
+
+ The victim is excluded from every core but core 1 and spins there without
+ relinquishing, so it is genuinely executing on another core when the
+ controller -- excluded to core 0 -- terminates it. _tx_thread_terminate
+ publishes TX_TERMINATED, releases the global SMP protection at TX_RESTORE,
+ and calls the victim's exit notification callback in the controller's
+ context on core 0. On an unfixed kernel a callback-side reset of the victim
+ succeeds at that point, and _tx_thread_reset both memsets the victim's stack
+ to the fill pattern and rebuilds its initial frame -- over the stack a live
+ core is running on.
+
+ What is measured:
+
+ - that the victim really was mapped to and executing on another core when
+ termination began, which is the precondition; if it cannot be
+ established within a bounded wait the test says so and drops only the
+ assertions that depend on it, rather than reporting a pass it did not
+ earn or spinning until something changes,
+ - that the callback-side delete and reset are refused, from the wrapper
+ and from the core service alike,
+ - that the victim's stack is untouched, checked with a sentinel written
+ into the unused low end of the stack, which a reset's fill would erase,
+ - that after the service returns the victim is in no core's execute slot
+ and on no ready list.
+
+ Every wait is bounded and every list walk is bounded, so neither a failing
+ kernel nor an unlucky schedule can turn this into a hang. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "tx_thread.h"
+
+
+#define CONTROLLER_PRIORITY 10
+#define VICTIM_PRIORITY 20
+
+/* The core the victim is pinned to, and the exclusion maps that pin it there
+ and the controller to core 0. */
+
+#define VICTIM_CORE 1
+#define EXCLUDE_ALL_BUT_CORE_0 0xE
+#define EXCLUDE_ALL_BUT_CORE_1 0xD
+
+/* Bound on the wait for the victim to be mapped to its core. */
+
+#define REMOTE_WAIT_TICKS 200
+
+/* Sentinel written into the unused low end of the victim's stack. A reset
+ memsets the whole stack to the fill pattern before rebuilding the frame, so
+ the sentinel is gone if a reset ran. Its offset keeps it clear of both the
+ frame a downward-growing stack builds at the top and the stack-checking
+ pattern the kernel inspects at the very bottom. */
+
+#define SENTINEL_OFFSET 64
+#define SENTINEL_WORDS 8
+#define SENTINEL_VALUE 0xA5A5A5A5UL
+
+#define READY_RING_LIMIT 32
+
+#define PROBE_NOT_ATTEMPTED 0xFFFFFFFFUL
+
+#define FAILED_TAG_LIMIT 16
+
+
+static TX_THREAD controller;
+static TX_THREAD victim;
+
+static CHAR *victim_stack;
+
+static volatile ULONG victim_spins = 0;
+static volatile UINT victim_run = TX_FALSE;
+
+static UINT probe_state;
+static UINT probe_suspending;
+static UINT probe_core_mapped;
+static UINT probe_delete_status = PROBE_NOT_ATTEMPTED;
+static UINT probe_core_delete_status = PROBE_NOT_ATTEMPTED;
+static UINT probe_reset_status = PROBE_NOT_ATTEMPTED;
+static UINT probe_core_reset_status = PROBE_NOT_ATTEMPTED;
+static UINT probe_ran = TX_FALSE;
+static UINT remote_confirmed = TX_FALSE;
+static UINT sentinel_intact_after = TX_FALSE;
+
+static unsigned long expectations_checked = 0;
+static unsigned long expectations_failed = 0;
+static UINT first_failure = 0;
+static UINT failed_tags[FAILED_TAG_LIMIT];
+static UINT failed_tag_count = 0;
+
+
+/* Define thread prototypes. */
+
+static void controller_entry(ULONG thread_input);
+static void victim_entry(ULONG thread_input);
+
+
+/* Prototype for test control return. */
+
+void test_control_return(UINT status);
+
+
+static void expect(UINT condition, UINT tag)
+{
+
+ expectations_checked++;
+
+ if (condition != TX_TRUE)
+ {
+
+ expectations_failed++;
+
+ if (first_failure == 0)
+ {
+ first_failure = tag;
+ }
+
+ if (failed_tag_count < FAILED_TAG_LIMIT)
+ {
+ failed_tags[failed_tag_count] = tag;
+ failed_tag_count++;
+ }
+ }
+}
+
+
+static void expect_status(UINT actual, UINT wanted, UINT tag)
+{
+
+ if (actual == wanted)
+ {
+ expect(TX_TRUE, tag);
+ }
+ else
+ {
+ expect(TX_FALSE, tag);
+ }
+}
+
+
+static void write_sentinel(void)
+{
+
+ULONG *words;
+UINT index;
+
+
+ words = (ULONG *) (victim_stack + SENTINEL_OFFSET);
+
+ for (index = 0; index < SENTINEL_WORDS; index++)
+ {
+ words[index] = SENTINEL_VALUE;
+ }
+}
+
+
+static UINT sentinel_intact(void)
+{
+
+ULONG *words;
+UINT index;
+UINT intact;
+
+
+ words = (ULONG *) (victim_stack + SENTINEL_OFFSET);
+ intact = TX_TRUE;
+
+ for (index = 0; index < SENTINEL_WORDS; index++)
+ {
+ if (words[index] != SENTINEL_VALUE)
+ {
+ intact = TX_FALSE;
+ }
+ }
+
+ return(intact);
+}
+
+
+/* Report whether thread_ptr appears in any core's execute slot or on any ready
+ list. Both walks are bounded. */
+
+static UINT fully_detached(TX_THREAD *thread_ptr)
+{
+
+TX_INTERRUPT_SAVE_AREA
+
+TX_THREAD *walk_ptr;
+TX_THREAD *head_ptr;
+UINT priority;
+UINT steps;
+UINT core;
+UINT detached;
+
+
+ detached = TX_TRUE;
+
+ TX_DISABLE
+
+ for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++)
+ {
+ if (_tx_thread_execute_ptr[core] == thread_ptr)
+ {
+ detached = TX_FALSE;
+ }
+ }
+
+ for (priority = 0; priority < ((UINT) TX_MAX_PRIORITIES); priority++)
+ {
+
+ head_ptr = _tx_thread_priority_list[priority];
+ walk_ptr = head_ptr;
+ steps = 0;
+
+ while ((walk_ptr != TX_NULL) && (steps < READY_RING_LIMIT))
+ {
+
+ if (walk_ptr == thread_ptr)
+ {
+ detached = TX_FALSE;
+ }
+
+ walk_ptr = walk_ptr -> tx_thread_ready_next;
+ steps++;
+
+ if (walk_ptr == head_ptr)
+ {
+ walk_ptr = TX_NULL;
+ }
+ }
+ }
+
+ TX_RESTORE
+
+ return(detached);
+}
+
+
+static void entry_exit_notify(TX_THREAD *thread_ptr, UINT type)
+{
+
+ if (thread_ptr != &victim)
+ {
+ return;
+ }
+
+ if (type != TX_THREAD_EXIT)
+ {
+ return;
+ }
+
+ probe_ran = TX_TRUE;
+ probe_state = thread_ptr -> tx_thread_state;
+ probe_suspending = thread_ptr -> tx_thread_suspending;
+ probe_core_mapped = thread_ptr -> tx_thread_smp_core_mapped;
+
+ /* The delete first, then the reset. Each core service is called only if
+ its wrapper already refused, so that a kernel which allows the operation
+ is not asked to perform it twice. */
+
+ probe_delete_status = tx_thread_delete(thread_ptr);
+
+ if (probe_delete_status != TX_SUCCESS)
+ {
+
+ probe_core_delete_status = _tx_thread_delete(thread_ptr);
+
+ if (probe_core_delete_status != TX_SUCCESS)
+ {
+
+ probe_reset_status = tx_thread_reset(thread_ptr);
+
+ if (probe_reset_status != TX_SUCCESS)
+ {
+ probe_core_reset_status = _tx_thread_reset(thread_ptr);
+ }
+ }
+ }
+}
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_smp_thread_exit_callback_remote_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+
+
+ pointer = (CHAR *) first_unused_memory;
+
+ status = tx_thread_create(&controller, "controller", controller_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ CONTROLLER_PRIORITY, CONTROLLER_PRIORITY,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ victim_stack = pointer;
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ status += tx_thread_smp_core_exclude(&controller, EXCLUDE_ALL_BUT_CORE_0);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running SMP Thread Exit Callback Remote Test............... ERROR #1\n");
+ test_control_return(1);
+ }
+}
+
+
+static void controller_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT notify_status;
+UINT index;
+ULONG waited;
+ULONG spins_before;
+
+
+ (void) thread_input;
+
+ printf("Running SMP Thread Exit Callback Remote Test............... ");
+
+ victim_run = TX_TRUE;
+
+ status = tx_thread_create(&victim, "victim", victim_entry, 0,
+ victim_stack, TEST_STACK_SIZE_PRINTF,
+ VICTIM_PRIORITY, VICTIM_PRIORITY,
+ TX_NO_TIME_SLICE, TX_DONT_START);
+
+ status += tx_thread_smp_core_exclude(&victim, EXCLUDE_ALL_BUT_CORE_1);
+
+ notify_status = tx_thread_entry_exit_notify(&victim, entry_exit_notify);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ if (notify_status != TX_SUCCESS)
+ {
+ status = notify_status;
+ }
+#else
+
+ if (notify_status != TX_FEATURE_NOT_ENABLED)
+ {
+ status = notify_status;
+ }
+#endif
+
+ expect_status(status, TX_SUCCESS, 10);
+
+ /* The sentinel goes in after the create, because the create fills the whole
+ stack with the pattern the stack-checking configurations inspect. */
+ write_sentinel();
+ expect(sentinel_intact(), 11);
+
+ if (expectations_failed == 0)
+ {
+ expect_status(tx_thread_resume(&victim), TX_SUCCESS, 12);
+ }
+
+ /* Wait, bounded, for the victim to be both mapped to its core and selected
+ to execute there, and for it to have actually spun. Those three
+ together are the precondition this test exists for. */
+
+ waited = 0;
+
+ while ((waited < REMOTE_WAIT_TICKS) && (remote_confirmed == TX_FALSE))
+ {
+
+ if ((_tx_thread_execute_ptr[VICTIM_CORE] == &victim) &&
+ (victim.tx_thread_smp_core_mapped == ((UINT) VICTIM_CORE)) &&
+ (victim_spins > 0))
+ {
+ remote_confirmed = TX_TRUE;
+ }
+ else
+ {
+ tx_thread_sleep(1);
+ waited++;
+ }
+ }
+
+ /* Confirm it is still spinning, so that the termination below really does
+ start against a running remote thread rather than a stalled one. */
+
+ spins_before = victim_spins;
+
+ if (remote_confirmed == TX_TRUE)
+ {
+ tx_thread_sleep(2);
+ expect(((victim_spins > spins_before) ? TX_TRUE : TX_FALSE), 13);
+ }
+ else
+ {
+
+ /* Say so rather than assert it. The refusal assertions below do not
+ depend on the victim being remote, so they still run; the ones that
+ do are dropped, and this line records that the environment did not
+ provide the precondition. */
+ printf("(remote precondition not established in %u ticks; remote-only checks skipped) ",
+ (UINT) REMOTE_WAIT_TICKS);
+ }
+
+ if (expectations_failed == 0)
+ {
+
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 20);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect(probe_ran, 21);
+ expect_status(probe_state, TX_TERMINATED, 22);
+
+ /* The transition marker is set across the callback, which is what the
+ two refusals below rest on. */
+ expect_status(probe_suspending, TX_TRUE, 23);
+
+ expect_status(probe_delete_status, TX_DELETE_ERROR, 24);
+ expect_status(probe_core_delete_status, TX_DELETE_ERROR, 25);
+ expect_status(probe_reset_status, TX_NOT_DONE, 26);
+ expect_status(probe_core_reset_status, TX_NOT_DONE, 27);
+
+ if (remote_confirmed == TX_TRUE)
+ {
+
+ /* The callback saw the victim still mapped to the remote core. */
+ expect_status(probe_core_mapped, (UINT) VICTIM_CORE, 28);
+ }
+#endif
+
+ /* The reset was refused, so nothing memset the stack a live core was
+ running on. */
+ sentinel_intact_after = sentinel_intact();
+ expect_status(sentinel_intact_after, TX_TRUE, 29);
+
+ /* And the termination completed: the marker is clear, and the victim is
+ in no core's execute slot and on no ready list. */
+ expect_status(victim.tx_thread_state, TX_TERMINATED, 30);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 31);
+ expect(fully_detached(&victim), 32);
+ }
+
+ /* The victim's loop exits on this, for the case where it is still between
+ the terminate and its core acting on the preemption. */
+ victim_run = TX_FALSE;
+
+ if (expectations_failed == 0)
+ {
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 40);
+ }
+
+ if (expectations_failed != 0)
+ {
+
+ printf("ERROR #%u (%lu of %lu expectations failed:",
+ first_failure, expectations_failed, expectations_checked);
+
+ for (index = 0; index < failed_tag_count; index++)
+ {
+ printf(" %u", failed_tags[index]);
+ }
+
+ printf(")\n");
+ fflush(stdout);
+ test_control_return(1);
+ }
+ else
+ {
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void victim_entry(ULONG thread_input)
+{
+
+ (void) thread_input;
+
+ /* Spin without relinquishing, so this thread occupies its core rather than
+ yielding it. */
+ while (victim_run == TX_TRUE)
+ {
+ victim_spins++;
+ }
+}
diff --git a/test/smp/regression/threadx_smp_time_slice_test.c b/test/smp/regression/threadx_smp_time_slice_test.c
index 370f5e37..c5ab0615 100644
--- a/test/smp/regression/threadx_smp_time_slice_test.c
+++ b/test/smp/regression/threadx_smp_time_slice_test.c
@@ -11,7 +11,7 @@
/* Define the ThreadX SMP time-slice test. */
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
#include <stdio.h>
#include "tx_api.h"
diff --git a/test/smp/regression/threadx_thread_basic_execution_test.c b/test/smp/regression/threadx_thread_basic_execution_test.c
index d90d0769..b0acc842 100644
--- a/test/smp/regression/threadx_thread_basic_execution_test.c
+++ b/test/smp/regression/threadx_thread_basic_execution_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to see if one thread can be created and executed.
It thread_0_entry is hit, then the thread was successfully scheduled.
On success, thread_0_counter gets incremented. */
@@ -69,7 +71,7 @@ static unsigned long isr_executed = 0;
static void thread_0_entry(ULONG task_input);
-UINT _txe_thread_create(TX_THREAD *thread_ptr, CHAR *name_ptr,
+UINT _txe_thread_create(TX_THREAD *thread_ptr, TX_NAME_CONST CHAR *name_ptr,
VOID (*entry_function)(ULONG), ULONG entry_input,
VOID *stack_start, ULONG stack_size,
UINT priority, UINT preempt_threshold,
diff --git a/test/smp/regression/threadx_thread_delayed_suspension_test.c b/test/smp/regression/threadx_thread_delayed_suspension_test.c
index fe0a82f8..17a89d03 100644
--- a/test/smp/regression/threadx_thread_delayed_suspension_test.c
+++ b/test/smp/regression/threadx_thread_delayed_suspension_test.c
@@ -12,6 +12,7 @@
/* This test checks out the delayed suspension clear from tx_thread_resume. */
#include <stdio.h>
+#include <time.h>
#include "tx_api.h"
#include "tx_thread.h"
#include "tx_timer.h"
@@ -235,6 +236,12 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
+#ifndef TX_NOT_INTERRUPTABLE
+#define DELAYED_SUSPENSION_SECOND_BUDGET ((ULONG) 120)
+
+time_t start_wall;
+#endif
+
/* Inform user. */
printf("Running Thread Delayed Suspension Clearing Test..................... ");
@@ -277,8 +284,23 @@ UINT status;
/* Resume the test thread. */
tx_thread_resume(&thread_2);
- /* Wait until we see the delayed suspension set flag. */
- while(delayed_suspend_set == 0)
+ /* Wait until we see the delayed suspension set flag.
+
+ The flag is set by the interrupt above, and only when that interrupt lands
+ while thread 2 is part way through suspending. That is a narrow window,
+ and waiting for it without a bound leaves the test no way out when it is
+ not reached. How narrow it is depends on the build: in the ThreadX suite
+ the same loop finishes in between a tenth of a second and three seconds in
+ four configurations, and took 490 seconds in trace_build, where it was the
+ most expensive thing in the suite by a wide margin.
+
+ The budget is in wall clock seconds rather than ticks. A tick arrives only
+ when the port's timer thread runs, so the tick clock falls behind real
+ time under load or instrumentation, which is exactly the configuration
+ that needs bounding here. */
+ start_wall = time(TX_NULL);
+ while ((delayed_suspend_set == 0) &&
+ (((ULONG) (time(TX_NULL) - start_wall)) <= DELAYED_SUSPENSION_SECOND_BUDGET))
{
/* Abort the suspension for thread 2. */
tx_thread_wait_abort(&thread_2);
@@ -291,7 +313,18 @@ UINT status;
tx_thread_relinquish();
/* At this point, check for an error. */
- if (thread_2_counter != thread_2_counter_capture)
+ if (delayed_suspend_set == 0)
+ {
+
+ /* The window was never reached, so thread_2_counter_capture was never
+ taken and the comparison below would be against a value that means
+ nothing. Not reaching the window is a gap in what this run covered
+ rather than a fault in the code under test, so say so and skip the
+ check that depends on it. */
+ printf("(delayed suspension window not reached in %lu seconds) ",
+ (ULONG) (time(TX_NULL) - start_wall));
+ }
+ else if (thread_2_counter != thread_2_counter_capture)
{
/* Delayed suspension error... thread kept running! */
diff --git a/test/smp/regression/threadx_thread_exit_callback_transition_test.c b/test/smp/regression/threadx_thread_exit_callback_transition_test.c
new file mode 100644
index 00000000..c931085c
--- /dev/null
+++ b/test/smp/regression/threadx_thread_exit_callback_transition_test.c
@@ -0,0 +1,1375 @@
+/***************************************************************************
+ * 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 covers the completion and termination transitions that a thread's
+ TX_THREAD_EXIT notification callback runs inside.
+
+ _tx_thread_shell_entry and _tx_thread_terminate both publish the terminal
+ state -- TX_COMPLETED or TX_TERMINATED -- and then call the thread's exit
+ notification callback, before the thread has been detached from the ready
+ list and before either service has finished with the pointer it holds to
+ the control block. That terminal state is the same state _tx_thread_delete
+ and _tx_thread_reset accept as authorization to invalidate or rebuild the
+ control block, so a callback used to be able to delete the object -- and
+ then lawfully recreate it over the same memory -- while the scheduler was
+ still linked to the old incarnation, or to reset a terminated thread and
+ resume it back onto the ready list after termination had reported success.
+
+ The tx_thread_suspending flag marks the transition for exactly its
+ duration, and delete and reset now refuse a target whose flag is still set.
+ The refusal is transient: TX_DELETE_ERROR and TX_NOT_DONE are errors those
+ two services already document, and the same call succeeds once the
+ transition has finished.
+
+ What the test measures, in every scenario:
+
+ 1. what the callback observes -- the published state, the transition
+ flag, and the status of each lifecycle service it can reach,
+ 2. that the scheduler's ready lists, priority map and execute pointer
+ stay consistent with the control blocks they point at, taken as one
+ snapshot under interrupt lockout so that the reading is not itself a
+ race,
+ 3. that the documented behaviour still works afterwards: the same delete
+ and reset succeed once the transition has completed, so nothing has
+ become permanently undeletable.
+
+ The consequences are demonstrated rather than described, and deliberately
+ from the terminator's context rather than the completing thread's:
+
+ - scenario A has the callback recreate the control block if its delete
+ unexpectedly succeeded, at a lower priority, so that the outcome is an
+ assertable disagreement -- the old priority list still heading at a
+ control block whose own priority field names a different list -- rather
+ than a fault;
+ - scenario B has the callback reset and then resume the target, so that a
+ kernel which allows the reset is caught reporting a successful
+ termination for a thread that is runnable again.
+
+ Both run while the victim is a ready thread that is not the current thread.
+ That ordering is not cosmetic. On the Linux, Win32 and Win64 host
+ simulation ports TX_THREAD_DELETE_PORT_COMPLETION cancels and joins the
+ host thread backing the deleted thread, so a callback-side delete of the
+ *completing* thread destroys the host thread the callback is running on and
+ wedges the simulator instead of reaching an assertion. The completion
+ scenarios therefore come last, and the scenario gate below stops the run
+ before them if anything earlier has already failed.
+
+ Every list walk is bounded, so a corrupted ring costs an assertion and not
+ a hang. Expectations are counted rather than short-circuited within a
+ scenario, so a failing kernel reports how much it failed by; progression
+ between scenarios is gated on there being no failures yet, so a kernel that
+ has already corrupted the scheduler is not driven further into it. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "tx_thread.h"
+
+
+/* Priorities. The controller sits above every thread it drives. The victim
+ shares its priority with two peers, so that both the multi-member and the
+ single-member ready-ring unlink paths are reached. RECREATE_PRIORITY is
+ below VICTIM_PRIORITY on purpose: see the note in the callback. */
+
+#define CONTROLLER_PRIORITY 10
+#define VICTIM_PRIORITY 20
+#define RECREATE_PRIORITY 21
+
+/* Upper bound on a ready-ring walk. The suite never has this many threads at
+ one priority, so exceeding it means the ring is broken rather than long. */
+
+#define READY_RING_LIMIT 32
+
+/* Sentinel for a probe slot the callback did not reach. */
+
+#define PROBE_NOT_ATTEMPTED 0xFFFFFFFFUL
+
+
+static unsigned long controller_counter = 0;
+static unsigned long victim_counter = 0;
+static unsigned long peer_a_counter = 0;
+static unsigned long peer_b_counter = 0;
+static unsigned long replacement_counter = 0;
+static unsigned long callback_entry_counter = 0;
+static unsigned long callback_exit_counter = 0;
+
+static TX_THREAD controller;
+static TX_THREAD victim;
+static TX_THREAD peer_a;
+static TX_THREAD peer_b;
+static TX_SEMAPHORE victim_semaphore;
+static TX_MUTEX victim_mutex;
+
+static CHAR *victim_stack;
+static CHAR *replacement_stack;
+
+/* What the victim's entry function should do before it returns. */
+
+static volatile UINT victim_hold = TX_FALSE;
+static volatile UINT victim_self_terminate = TX_FALSE;
+static volatile UINT victim_take_mutex = TX_FALSE;
+static volatile UINT victim_wait_semaphore = TX_FALSE;
+static volatile UINT peers_run = TX_FALSE;
+
+/* What the exit callback should attempt the next time it runs. Independent
+ flags rather than one mode, because a delete that unexpectedly succeeds
+ destroys the object and would mask the reset measurement behind it. */
+
+static volatile UINT probe_delete_enabled = TX_FALSE;
+static volatile UINT probe_reset_enabled = TX_FALSE;
+static volatile UINT probe_misc_enabled = TX_FALSE;
+static volatile UINT probe_reuse_enabled = TX_FALSE;
+
+/* What the callback saw the last time it probed. */
+
+static UINT probe_state;
+static UINT probe_suspending;
+static UINT probe_delete_status;
+static UINT probe_core_delete_status;
+static UINT probe_reset_status;
+static UINT probe_core_reset_status;
+static UINT probe_resume_status;
+static UINT probe_suspend_status;
+static UINT probe_terminate_status;
+static UINT probe_create_status;
+static UINT probe_consistent;
+static UINT probe_ran;
+static UINT delete_succeeded;
+
+/* Ordering witness for the notification pair. Entry and exit counts cannot
+ simply be compared: a victim created with TX_DONT_START never reports an
+ entry, and an incarnation that is reset and terminated again reports a
+ second exit with no second entry. What must hold is that an exit for a
+ thread that ran is preceded by that thread's entry. */
+
+static UINT last_notify_type = PROBE_NOT_ATTEMPTED;
+static UINT exit_saw_entry_first = TX_FALSE;
+
+/* Expectation accounting. */
+
+#define FAILED_TAG_LIMIT 32
+
+static unsigned long expectations_checked = 0;
+static unsigned long expectations_failed = 0;
+static UINT first_failure = 0;
+static UINT failed_tags[FAILED_TAG_LIMIT];
+static UINT failed_tag_count = 0;
+
+
+/* Define task prototypes. */
+
+static void controller_entry(ULONG thread_input);
+static void victim_entry(ULONG thread_input);
+static void peer_entry(ULONG thread_input);
+static void replacement_entry(ULONG thread_input);
+
+
+/* Prototype for test control return. */
+
+void test_control_return(UINT status);
+
+
+/* Record one expectation. tag identifies it in the failure report; the first
+ failing tag is the number the suite prints, so tags are allocated in the
+ order the expectations are written. */
+
+static void expect(UINT condition, UINT tag)
+{
+
+ expectations_checked++;
+
+ if (condition != TX_TRUE)
+ {
+
+ expectations_failed++;
+
+ if (first_failure == 0)
+ {
+ first_failure = tag;
+ }
+
+ /* Keep the tags, so that a failing kernel reports which expectations
+ it failed rather than only the first and a total. */
+ if (failed_tag_count < FAILED_TAG_LIMIT)
+ {
+ failed_tags[failed_tag_count] = tag;
+ failed_tag_count++;
+ }
+ }
+}
+
+
+static void expect_status(UINT actual, UINT wanted, UINT tag)
+{
+
+ if (actual == wanted)
+ {
+ expect(TX_TRUE, tag);
+ }
+ else
+ {
+ expect(TX_FALSE, tag);
+ }
+}
+
+
+static void expect_pointer(void *actual, void *wanted, UINT tag)
+{
+
+ if (actual == wanted)
+ {
+ expect(TX_TRUE, tag);
+ }
+ else
+ {
+ expect(TX_FALSE, tag);
+ }
+}
+
+
+static void expect_count(unsigned long actual, unsigned long wanted, UINT tag)
+{
+
+ if (actual == wanted)
+ {
+ expect(TX_TRUE, tag);
+ }
+ else
+ {
+ expect(TX_FALSE, tag);
+ }
+}
+
+
+/* Walk one ready ring and report whether thread_ptr is a member of it. The
+ walk is bounded, so a ring that does not close is reported as a
+ non-membership rather than spun on for ever. */
+
+static UINT ready_ring_contains(UINT priority, TX_THREAD *thread_ptr)
+{
+
+TX_THREAD *walk_ptr;
+TX_THREAD *head_ptr;
+UINT steps;
+UINT found;
+
+
+ found = TX_FALSE;
+ head_ptr = _tx_thread_priority_list[priority];
+ walk_ptr = head_ptr;
+ steps = 0;
+
+ while ((walk_ptr != TX_NULL) && (steps < READY_RING_LIMIT) && (found == TX_FALSE))
+ {
+
+ if (walk_ptr == thread_ptr)
+ {
+ found = TX_TRUE;
+ }
+
+ walk_ptr = walk_ptr -> tx_thread_ready_next;
+ steps++;
+
+ /* The ring has closed. */
+ if (walk_ptr == head_ptr)
+ {
+ walk_ptr = TX_NULL;
+ }
+ }
+
+ return(found);
+}
+
+
+/* Take one snapshot of the scheduler's ready structures and check that they
+ agree with the control blocks they point at.
+
+ This is the assertion the pre-fix behaviour fails. A callback that deletes
+ the victim and recreates it at another priority leaves the old priority list
+ heading at that control block while the block's own priority field names the
+ new list, and leaves the old priority's map bit set with nothing behind it.
+ Neither disagreement needs a fault to be visible.
+
+ The whole snapshot is taken under interrupt lockout -- which is the global
+ SMP protection on an SMP port -- so that reading the scheduler's state is
+ not itself a race against another core.
+
+ transitioning names the one thread that is allowed to be on a ready list
+ without being ready, which is precisely the thread whose completion or
+ termination transition is in progress: the terminal state is published
+ before the detachment, so inside the callback the victim is legitimately a
+ TX_COMPLETED or TX_TERMINATED member of its ready ring. That single
+ exemption is from the state clause only. Every other clause, and in
+ particular the clause that requires a ready-list member to agree that this
+ is its priority -- the one a callback-side delete and recreate breaks --
+ still applies to it. */
+
+static UINT scheduler_consistent_except(TX_THREAD *transitioning)
+{
+
+TX_INTERRUPT_SAVE_AREA
+
+TX_THREAD *head_ptr;
+TX_THREAD *walk_ptr;
+TX_THREAD *execute_ptr;
+UINT priority;
+UINT steps;
+UINT consistent;
+#ifdef TX_THREAD_SMP_MAX_CORES
+UINT core;
+#endif
+
+
+ consistent = TX_TRUE;
+
+ TX_DISABLE
+
+ for (priority = 0; priority < ((UINT) TX_MAX_PRIORITIES); priority++)
+ {
+
+ head_ptr = _tx_thread_priority_list[priority];
+
+#if TX_MAX_PRIORITIES <= 32
+
+ /* A priority with a ready list must have its map bit set, and a
+ priority with no ready list must not. */
+ if (head_ptr == TX_NULL)
+ {
+ if ((_tx_thread_priority_maps[0] & (((ULONG) 1) << priority)) != ((ULONG) 0))
+ {
+ consistent = TX_FALSE;
+ }
+ }
+ else
+ {
+ if ((_tx_thread_priority_maps[0] & (((ULONG) 1) << priority)) == ((ULONG) 0))
+ {
+ consistent = TX_FALSE;
+ }
+ }
+#endif
+
+ walk_ptr = head_ptr;
+ steps = 0;
+
+ while ((walk_ptr != TX_NULL) && (steps < READY_RING_LIMIT))
+ {
+
+ /* Every thread on a ready list must be a live thread, must be
+ ready, and must agree that this is its priority. */
+ if (walk_ptr -> tx_thread_id != TX_THREAD_ID)
+ {
+ consistent = TX_FALSE;
+ }
+
+ if ((walk_ptr -> tx_thread_state != TX_READY) && (walk_ptr != transitioning))
+ {
+ consistent = TX_FALSE;
+ }
+
+ if (walk_ptr -> tx_thread_priority != priority)
+ {
+ consistent = TX_FALSE;
+ }
+
+ /* The ring must be doubly linked. */
+ if (walk_ptr -> tx_thread_ready_next -> tx_thread_ready_previous != walk_ptr)
+ {
+ consistent = TX_FALSE;
+ }
+
+ walk_ptr = walk_ptr -> tx_thread_ready_next;
+ steps++;
+
+ if (walk_ptr == head_ptr)
+ {
+ walk_ptr = TX_NULL;
+ }
+ }
+
+ /* A ring that did not close inside the bound is broken. */
+ if (walk_ptr != TX_NULL)
+ {
+ consistent = TX_FALSE;
+ }
+ }
+
+ /* Whatever the scheduler has selected to execute must be a member of the
+ ready list that its own priority field names. */
+#ifdef TX_THREAD_SMP_MAX_CORES
+
+ for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++)
+ {
+
+ execute_ptr = _tx_thread_execute_ptr[core];
+
+ if (execute_ptr != TX_NULL)
+ {
+ if (ready_ring_contains(execute_ptr -> tx_thread_priority, execute_ptr) == TX_FALSE)
+ {
+ consistent = TX_FALSE;
+ }
+ }
+ }
+#else
+
+ execute_ptr = _tx_thread_execute_ptr;
+
+ if (execute_ptr != TX_NULL)
+ {
+ if (ready_ring_contains(execute_ptr -> tx_thread_priority, execute_ptr) == TX_FALSE)
+ {
+ consistent = TX_FALSE;
+ }
+ }
+#endif
+
+ TX_RESTORE
+
+ return(consistent);
+}
+
+
+static UINT scheduler_consistent(void)
+{
+ return(scheduler_consistent_except(TX_NULL));
+}
+
+
+/* Report whether thread_ptr is absent from every ready list and from every
+ execute slot, which is what a completed or terminated thread must be once
+ its transition has finished. */
+
+static UINT fully_detached(TX_THREAD *thread_ptr)
+{
+
+TX_INTERRUPT_SAVE_AREA
+
+UINT priority;
+UINT detached;
+#ifdef TX_THREAD_SMP_MAX_CORES
+UINT core;
+#endif
+
+
+ detached = TX_TRUE;
+
+ TX_DISABLE
+
+ for (priority = 0; priority < ((UINT) TX_MAX_PRIORITIES); priority++)
+ {
+ if (ready_ring_contains(priority, thread_ptr) == TX_TRUE)
+ {
+ detached = TX_FALSE;
+ }
+ }
+
+#ifdef TX_THREAD_SMP_MAX_CORES
+
+ for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++)
+ {
+ if (_tx_thread_execute_ptr[core] == thread_ptr)
+ {
+ detached = TX_FALSE;
+ }
+ }
+#else
+
+ if (_tx_thread_execute_ptr == thread_ptr)
+ {
+ detached = TX_FALSE;
+ }
+#endif
+
+ TX_RESTORE
+
+ return(detached);
+}
+
+
+static void arm_probe(UINT probe_delete, UINT probe_reset, UINT probe_misc, UINT probe_reuse)
+{
+
+ probe_state = PROBE_NOT_ATTEMPTED;
+ probe_suspending = PROBE_NOT_ATTEMPTED;
+ probe_delete_status = PROBE_NOT_ATTEMPTED;
+ probe_core_delete_status = PROBE_NOT_ATTEMPTED;
+ probe_reset_status = PROBE_NOT_ATTEMPTED;
+ probe_core_reset_status = PROBE_NOT_ATTEMPTED;
+ probe_resume_status = PROBE_NOT_ATTEMPTED;
+ probe_suspend_status = PROBE_NOT_ATTEMPTED;
+ probe_terminate_status = PROBE_NOT_ATTEMPTED;
+ probe_create_status = PROBE_NOT_ATTEMPTED;
+ probe_consistent = PROBE_NOT_ATTEMPTED;
+ probe_ran = TX_FALSE;
+ delete_succeeded = TX_FALSE;
+
+ probe_delete_enabled = probe_delete;
+ probe_reset_enabled = probe_reset;
+ probe_misc_enabled = probe_misc;
+ probe_reuse_enabled = probe_reuse;
+}
+
+
+/* The exit notification callback. Everything it learns is recorded for the
+ controller to check; it prints nothing and it never suspends, so it obeys
+ the one restriction the manual places on a notification callback. */
+
+static void entry_exit_notify(TX_THREAD *thread_ptr, UINT type)
+{
+
+ /* Only the victim's notifications are of interest. Its control block is
+ reused across scenarios, so the pointer is the identity. */
+ if (thread_ptr != &victim)
+ {
+ return;
+ }
+
+ if (type == TX_THREAD_ENTRY)
+ {
+ callback_entry_counter++;
+ last_notify_type = TX_THREAD_ENTRY;
+ return;
+ }
+
+ if (type != TX_THREAD_EXIT)
+ {
+ return;
+ }
+
+ callback_exit_counter++;
+
+ if (last_notify_type == TX_THREAD_ENTRY)
+ {
+ exit_saw_entry_first = TX_TRUE;
+ }
+
+ last_notify_type = TX_THREAD_EXIT;
+
+ if ((probe_delete_enabled == TX_FALSE) && (probe_reset_enabled == TX_FALSE) &&
+ (probe_misc_enabled == TX_FALSE))
+ {
+ return;
+ }
+
+ probe_ran = TX_TRUE;
+
+ /* What the kernel has published about the thread at this point. */
+ probe_state = thread_ptr -> tx_thread_state;
+ probe_suspending = thread_ptr -> tx_thread_suspending;
+
+ /* None of the services below has a suspension option, which is the only
+ thing the manual forbids a callback from using, so every one of them is
+ reachable from here.
+
+ Where a service is expected to refuse, the core service is called after
+ the _txe_ wrapper as well. The wrapper is what an ordinary build
+ reaches; the core service is what a TX_DISABLE_ERROR_CHECKING build
+ reaches directly, and the refusal has to live there so that disabling
+ error checking cannot disable it. The core call is made only when the
+ wrapper already refused, so that a kernel which allows the operation is
+ not asked to perform it twice. */
+
+ if (probe_delete_enabled == TX_TRUE)
+ {
+
+ probe_delete_status = tx_thread_delete(thread_ptr);
+
+ if (probe_delete_status == TX_SUCCESS)
+ {
+ delete_succeeded = TX_TRUE;
+ }
+ else
+ {
+
+ probe_core_delete_status = _tx_thread_delete(thread_ptr);
+
+ if (probe_core_delete_status == TX_SUCCESS)
+ {
+ delete_succeeded = TX_TRUE;
+ }
+ }
+
+ /* Recreate the control block only if the delete unexpectedly
+ succeeded. On a fixed kernel this never runs. On an unfixed one it
+ is the lawful next step after a successful delete, and it is what
+ turns the window into an observable inconsistency: tx_thread_create
+ zeroes the whole control block, and the auto-start then links it
+ onto RECREATE_PRIORITY's ready list while the old incarnation is
+ still the head of VICTIM_PRIORITY's.
+
+ RECREATE_PRIORITY is the lower of the two so that the stale head is
+ the one the scheduler selects from, and so that the ring at the new
+ priority has a single member. Recreating at the same priority would
+ instead have the auto-start write through the control block's
+ freshly zeroed ready_previous pointer, which is a fault rather than
+ an assertion. */
+ if ((delete_succeeded == TX_TRUE) && (probe_reuse_enabled == TX_TRUE))
+ {
+ probe_create_status = tx_thread_create(thread_ptr, "replacement",
+ replacement_entry, 0,
+ replacement_stack,
+ TEST_STACK_SIZE_PRINTF,
+ RECREATE_PRIORITY, RECREATE_PRIORITY,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ }
+ }
+
+ if ((probe_reset_enabled == TX_TRUE) && (delete_succeeded == TX_FALSE))
+ {
+
+ probe_reset_status = tx_thread_reset(thread_ptr);
+
+ if (probe_reset_status != TX_SUCCESS)
+ {
+ probe_core_reset_status = _tx_thread_reset(thread_ptr);
+ }
+
+ /* Follow the reset with the resume that the reset would have unlocked.
+ On a fixed kernel the state is still terminal and the resume is
+ refused. On an unfixed one the reset has moved the state to
+ TX_SUSPENDED, the resume finds a suspending thread whose state is no
+ longer terminal, clears the flag and puts the thread back on the
+ ready list -- and the outer service then skips the removal and
+ reports success for a termination that did not happen. */
+ probe_resume_status = tx_thread_resume(thread_ptr);
+ }
+
+ if ((probe_misc_enabled == TX_TRUE) && (delete_succeeded == TX_FALSE))
+ {
+ probe_suspend_status = tx_thread_suspend(thread_ptr);
+ probe_terminate_status = tx_thread_terminate(thread_ptr);
+ }
+
+ probe_consistent = scheduler_consistent_except(thread_ptr);
+}
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_thread_exit_callback_transition_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;
+
+ status = tx_thread_create(&controller, "controller", controller_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ CONTROLLER_PRIORITY, CONTROLLER_PRIORITY,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ status += tx_thread_create(&peer_a, "peer a", peer_entry, 1,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ VICTIM_PRIORITY, VICTIM_PRIORITY,
+ TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ status += tx_thread_create(&peer_b, "peer b", peer_entry, 2,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ VICTIM_PRIORITY, VICTIM_PRIORITY,
+ TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ /* The victim's control block is created and destroyed repeatedly, so its
+ two stacks are set aside here rather than per scenario. */
+ victim_stack = pointer;
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ replacement_stack = pointer;
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ status += tx_semaphore_create(&victim_semaphore, "victim semaphore", 0);
+ status += tx_mutex_create(&victim_mutex, "victim mutex", TX_INHERIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Thread Exit Callback Transition Test............... ERROR #1\n");
+ test_control_return(1);
+ }
+
+#ifdef TX_THREAD_SMP_MAX_CORES
+
+ /* Confine every thread this test drives to core 0. The measurements below
+ are of global scheduler structures at chosen moments, and letting the
+ peers and the victim run on other cores would make them a race against
+ the test rather than a property of it. A victim executing on a
+ different core when termination begins is deliberately covered by a
+ separate SMP test, so that the deterministic cases here cannot be made
+ flaky by it. */
+ status = tx_thread_smp_core_exclude(&controller, 0xE);
+ status += tx_thread_smp_core_exclude(&peer_a, 0xE);
+ status += tx_thread_smp_core_exclude(&peer_b, 0xE);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Thread Exit Callback Transition Test............... ERROR #2\n");
+ test_control_return(1);
+ }
+#endif
+}
+
+
+/* Create the victim over its own control block, with the exit callback
+ registered. The notify status is folded into the result according to what
+ the configuration supports. */
+
+static UINT create_victim(UINT priority, UINT auto_start)
+{
+
+UINT status;
+UINT notify_status;
+
+
+ status = tx_thread_create(&victim, "victim", victim_entry, 0,
+ victim_stack, TEST_STACK_SIZE_PRINTF,
+ priority, priority, TX_NO_TIME_SLICE, auto_start);
+
+ notify_status = tx_thread_entry_exit_notify(&victim, entry_exit_notify);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ if (notify_status != TX_SUCCESS)
+ {
+ status = notify_status;
+ }
+#else
+
+ /* Notification is compiled out, so the registration is expected to be
+ refused and the callback never runs. */
+ if (notify_status != TX_FEATURE_NOT_ENABLED)
+ {
+ status = notify_status;
+ }
+#endif
+
+#ifdef TX_THREAD_SMP_MAX_CORES
+
+ if (status == TX_SUCCESS)
+ {
+ status = tx_thread_smp_core_exclude(&victim, 0xE);
+ }
+#endif
+
+ return(status);
+}
+
+
+/* The gate between scenarios. A kernel that has already failed an expectation
+ has, in these scenarios, already left the scheduler inconsistent, and
+ driving it further would report nothing more while risking a wedge instead
+ of a verdict. */
+
+static UINT ok_so_far(void)
+{
+
+UINT result;
+
+
+ result = TX_FALSE;
+
+ if (expectations_failed == 0)
+ {
+ result = TX_TRUE;
+ }
+
+ return(result);
+}
+
+
+static void controller_entry(ULONG thread_input)
+{
+
+ULONG expected_exits;
+ULONG expected_entries;
+UINT index;
+
+
+ (void) thread_input;
+
+ controller_counter++;
+ expected_exits = 0;
+ expected_entries = 0;
+
+ /* Inform user. */
+ printf("Running Thread Exit Callback Transition Test............... ");
+
+ /* The scheduler must be self-consistent before anything is measured, so
+ that a later failure is attributable to this test. */
+ expect(scheduler_consistent(), 10);
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario A: terminate a ready target that is not the current thread, */
+ /* with two peers ready at the same priority. The callback attempts */
+ /* the delete, and recreates the control block if it succeeded. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ peers_run = TX_TRUE;
+ victim_hold = TX_TRUE;
+
+ expect_status(tx_thread_resume(&peer_a), TX_SUCCESS, 11);
+ expect_status(tx_thread_resume(&peer_b), TX_SUCCESS, 12);
+
+ arm_probe(TX_TRUE, TX_FALSE, TX_FALSE, TX_TRUE);
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 13);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ /* Let the victim and both peers become ready and start spinning, so
+ that the victim's unlink is from a multi-member ring. */
+ tx_thread_sleep(3);
+
+ expect_status(victim.tx_thread_state, TX_READY, 14);
+ expect(ready_ring_contains(VICTIM_PRIORITY, &victim), 15);
+ expect(ready_ring_contains(VICTIM_PRIORITY, &peer_a), 16);
+ expect(ready_ring_contains(VICTIM_PRIORITY, &peer_b), 17);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 18);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect(probe_ran, 19);
+
+ /* The callback runs after the terminal state has been published, and
+ inside the transition, which is what the flag records. */
+ expect_status(probe_state, TX_TERMINATED, 20);
+ expect_status(probe_suspending, TX_TRUE, 21);
+
+ /* Both the wrapper and the core service must refuse. */
+ expect_status(probe_delete_status, TX_DELETE_ERROR, 22);
+ expect_status(probe_core_delete_status, TX_DELETE_ERROR, 23);
+
+ /* So the recreate never happened and nothing is inconsistent. */
+ expect_status(delete_succeeded, TX_FALSE, 24);
+ expect_status(probe_create_status, PROBE_NOT_ATTEMPTED, 25);
+ expect_status(probe_consistent, TX_TRUE, 26);
+ expect_count(replacement_counter, 0, 27);
+#endif
+
+ /* After the service returns the termination is complete: the state is
+ terminal, the marker is clear, the target is on no ready list and in
+ no execute slot, and the peers are undisturbed. */
+ expect_status(victim.tx_thread_state, TX_TERMINATED, 28);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 29);
+ expect(fully_detached(&victim), 30);
+ expect(ready_ring_contains(VICTIM_PRIORITY, &peer_a), 31);
+ expect(ready_ring_contains(VICTIM_PRIORITY, &peer_b), 32);
+ expect(scheduler_consistent(), 33);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ /* The documented behaviour is available again. */
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 34);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario B: the same termination, but the callback attempts a reset */
+ /* and then the resume that a successful reset would have unlocked. */
+ /* */
+ /* This is the leg where the transition check is the only thing */
+ /* standing between the callback and a reset of a thread that is still */
+ /* on the ready list. Without it the reset succeeds, the resume */
+ /* clears the suspending flag and restores TX_READY, the outer service */
+ /* then finds the flag clear and skips the removal, and */
+ /* tx_thread_terminate returns TX_SUCCESS for a runnable thread. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_TRUE, TX_FALSE, TX_FALSE);
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 40);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ tx_thread_sleep(3);
+
+ expect_status(victim.tx_thread_state, TX_READY, 41);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 42);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect(probe_ran, 43);
+ expect_status(probe_state, TX_TERMINATED, 44);
+ expect_status(probe_suspending, TX_TRUE, 45);
+
+ /* The discriminating leg. The target is not the current thread and
+ its published state is terminal, so both of the conditions the
+ service tested before this fix are satisfied. */
+ expect_status(probe_reset_status, TX_NOT_DONE, 46);
+ expect_status(probe_core_reset_status, TX_NOT_DONE, 47);
+
+ /* With the reset refused, the state is still terminal, so the resume
+ cannot reach the branch that would void the termination. */
+ expect_status(probe_resume_status, TX_RESUME_ERROR, 48);
+ expect_status(probe_consistent, TX_TRUE, 49);
+#endif
+
+ /* And the termination stands. A kernel that let the callback reset
+ and resume the target arrives here with the victim ready to run. */
+ expect_status(victim.tx_thread_state, TX_TERMINATED, 50);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 51);
+ expect(fully_detached(&victim), 52);
+ expect(scheduler_consistent(), 53);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ expect_status(tx_thread_reset(&victim), TX_SUCCESS, 54);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 55);
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 56);
+ }
+
+ /* Retire the peers, so that the ring drops to a single member and then to
+ none, and check consistency across both unlinks. */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ peers_run = TX_FALSE;
+ tx_thread_sleep(3);
+
+ expect_status(peer_a.tx_thread_state, TX_COMPLETED, 57);
+ expect_status(peer_b.tx_thread_state, TX_COMPLETED, 58);
+ expect(scheduler_consistent(), 59);
+ expect_status(tx_thread_delete(&peer_a), TX_SUCCESS, 60);
+ expect_status(tx_thread_delete(&peer_b), TX_SUCCESS, 61);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario C: terminate a target suspended on an object, so that the */
+ /* non-ready branch of _tx_thread_terminate runs -- suspension cleanup, */
+ /* the terminated extension, the callback, and mutex release, in that */
+ /* order. That branch used to clear the marker before the callback */
+ /* rather than after the last dereference of the target. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_TRUE, TX_TRUE, TX_FALSE, TX_FALSE);
+ victim_hold = TX_FALSE;
+ victim_take_mutex = TX_TRUE;
+ victim_wait_semaphore = TX_TRUE;
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 70);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ /* Let the victim take the mutex and suspend on the semaphore. */
+ tx_thread_sleep(3);
+
+ expect_status(victim.tx_thread_state, TX_SEMAPHORE_SUSP, 71);
+ expect_pointer(victim_mutex.tx_mutex_owner, &victim, 72);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 73);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect(probe_ran, 74);
+ expect_status(probe_state, TX_TERMINATED, 75);
+
+ /* The discriminating leg for this branch. The marker used to be
+ cleared before this point, which left the callback free to delete or
+ reset a control block that the service still had mutex-release
+ processing to do against. */
+ expect_status(probe_suspending, TX_TRUE, 76);
+ expect_status(probe_delete_status, TX_DELETE_ERROR, 77);
+ expect_status(probe_core_delete_status, TX_DELETE_ERROR, 78);
+ expect_status(probe_reset_status, TX_NOT_DONE, 79);
+ expect_status(probe_core_reset_status, TX_NOT_DONE, 80);
+ expect_status(probe_resume_status, TX_RESUME_ERROR, 81);
+ expect_status(delete_succeeded, TX_FALSE, 82);
+ expect_status(probe_consistent, TX_TRUE, 83);
+#endif
+
+ /* The marker is cleared at the safe point, after the mutex release,
+ and the mutex the terminated thread owned has been given up. The
+ value of the flag during the release itself is not observable from
+ application code; what is observable is that there is exactly one
+ clear site and that it sits downstream of the release. */
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 84);
+ expect_pointer(victim_mutex.tx_mutex_owner, TX_NULL, 85);
+ expect_count((unsigned long) victim_semaphore.tx_semaphore_suspended_count, 0, 86);
+ expect(fully_detached(&victim), 87);
+ expect(scheduler_consistent(), 88);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 89);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario D: natural completion, victim alone at its priority. */
+ /* */
+ /* The callback here runs on the completing thread itself, so a */
+ /* callback-side delete that succeeds destroys the host thread on the */
+ /* simulation ports. This scenario is therefore placed after the ones */
+ /* that reach an assertion, and the gate above stops the run before it */
+ /* if any of them has already failed. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_TRUE, TX_TRUE, TX_TRUE, TX_FALSE);
+ victim_take_mutex = TX_FALSE;
+ victim_wait_semaphore = TX_FALSE;
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 100);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ /* Let the victim run to completion, so that its callback runs inside
+ the completion transition. */
+ tx_thread_sleep(3);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect(probe_ran, 101);
+ expect_status(probe_state, TX_COMPLETED, 102);
+ expect_status(probe_suspending, TX_TRUE, 103);
+ expect_status(probe_delete_status, TX_DELETE_ERROR, 104);
+ expect_status(probe_core_delete_status, TX_DELETE_ERROR, 105);
+
+ /* Reset of the running thread is refused for two independent reasons
+ here -- it is the current thread, and its transition is in progress
+ -- so this leg is a guard rather than a discriminator. Scenario B
+ is where the transition check is the only thing refusing a reset. */
+ expect_status(probe_reset_status, TX_NOT_DONE, 106);
+ expect_status(probe_core_reset_status, TX_NOT_DONE, 107);
+
+ /* A completed thread is neither resumable nor suspendable, and
+ terminating one is a documented no-op. */
+ expect_status(probe_resume_status, TX_RESUME_ERROR, 108);
+ expect_status(probe_suspend_status, TX_SUSPEND_ERROR, 109);
+ expect_status(probe_terminate_status, TX_SUCCESS, 110);
+ expect_status(delete_succeeded, TX_FALSE, 111);
+ expect_status(probe_consistent, TX_TRUE, 112);
+#endif
+
+ /* The transition has finished, so the calls the callback was refused
+ must succeed. The refusal is transient and nothing has become
+ permanently undeletable. */
+ expect_status(victim.tx_thread_state, TX_COMPLETED, 113);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 114);
+ expect(fully_detached(&victim), 115);
+ expect(scheduler_consistent(), 116);
+ expect(((victim_counter > 0) ? TX_TRUE : TX_FALSE), 117);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ /* Reset, so that the documented reset of a completed thread is covered
+ on a target whose transition has finished, and so that the
+ reset/terminate/delete order is exercised on this branch too. */
+ expect_status(tx_thread_reset(&victim), TX_SUCCESS, 118);
+ expect_status(victim.tx_thread_state, TX_SUSPENDED, 119);
+ expect_status(tx_thread_delete(&victim), TX_DELETE_ERROR, 120);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 121);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 122);
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 123);
+ }
+
+ /* A safe post-completion recreate at a different priority, which is the
+ lifecycle the callback was refused, performed at the point it is
+ legitimate. It exercises the priority map and the list heads for a
+ recreate that moves priority as well as for one that does not. */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ expect_status(create_victim(RECREATE_PRIORITY, TX_AUTO_START), TX_SUCCESS, 124);
+
+ tx_thread_sleep(3);
+
+ expect_status(victim.tx_thread_state, TX_COMPLETED, 125);
+ expect(scheduler_consistent(), 126);
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 127);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario E: self termination. The callback fires once, in the */
+ /* victim's own context, the lifecycle mutation is refused there too, */
+ /* and another thread can complete the lifecycle afterwards. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ victim_self_terminate = TX_TRUE;
+ expected_exits = callback_exit_counter;
+
+ arm_probe(TX_TRUE, TX_TRUE, TX_FALSE, TX_FALSE);
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 130);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ tx_thread_sleep(3);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ /* Exactly one exit notification, even though the thread terminated
+ itself from inside its own entry function. */
+ expect_count(callback_exit_counter, expected_exits + 1, 131);
+ expect_status(probe_state, TX_TERMINATED, 132);
+ expect_status(probe_suspending, TX_TRUE, 133);
+ expect_status(probe_delete_status, TX_DELETE_ERROR, 134);
+ expect_status(probe_core_delete_status, TX_DELETE_ERROR, 135);
+ expect_status(probe_reset_status, TX_NOT_DONE, 136);
+ expect_status(delete_succeeded, TX_FALSE, 137);
+ expect_status(probe_consistent, TX_TRUE, 138);
+#else
+
+ expect_count(callback_exit_counter, expected_exits, 139);
+#endif
+
+ expect_status(victim.tx_thread_state, TX_TERMINATED, 140);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 141);
+ expect(fully_detached(&victim), 142);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ expect_status(tx_thread_reset(&victim), TX_SUCCESS, 143);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 144);
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 145);
+ expect(scheduler_consistent(), 146);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario F: a benign callback is unaffected. The notification */
+ /* ordering and counts are what they were. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ victim_self_terminate = TX_FALSE;
+ expected_exits = callback_exit_counter;
+ expected_entries = callback_entry_counter;
+ exit_saw_entry_first = TX_FALSE;
+ last_notify_type = PROBE_NOT_ATTEMPTED;
+
+ arm_probe(TX_FALSE, TX_FALSE, TX_FALSE, TX_FALSE);
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_AUTO_START), TX_SUCCESS, 150);
+ }
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ tx_thread_sleep(3);
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ /* This incarnation ran and completed, so it reported exactly one entry
+ and one exit, and the entry came first. The transition marker has
+ changed neither how often notifications are delivered nor in what
+ order. */
+ expect_count(callback_exit_counter, expected_exits + 1, 151);
+ expect_count(callback_entry_counter, expected_entries + 1, 152);
+ expect_status(exit_saw_entry_first, TX_TRUE, 153);
+#else
+
+ expect_count(callback_exit_counter, expected_exits, 154);
+ expect_count(callback_entry_counter, expected_entries, 155);
+#endif
+
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 156);
+ expect(scheduler_consistent(), 157);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* Scenario G: state and boundary cases. A thread that never entered a */
+ /* terminal state, and one that is already fully detached, behave as */
+ /* documented -- the new refusal applies only while the marker is set. */
+ /* ------------------------------------------------------------------- */
+
+ if (ok_so_far() == TX_TRUE)
+ {
+
+ expect_status(create_victim(VICTIM_PRIORITY, TX_DONT_START), TX_SUCCESS, 170);
+
+ /* Suspended, never run: neither state is terminal, so both services
+ are refused for the reason they always were, and the marker is not
+ set on a thread that is not transitioning. */
+ expect_status(tx_thread_delete(&victim), TX_DELETE_ERROR, 171);
+ expect_status(tx_thread_reset(&victim), TX_NOT_DONE, 172);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 173);
+
+ /* Terminated from a suspended state: deletable immediately, and
+ repeatedly resettable and re-terminable in between. */
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 174);
+ expect_status(victim.tx_thread_suspending, TX_FALSE, 175);
+ expect_status(tx_thread_reset(&victim), TX_SUCCESS, 176);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 177);
+ expect_status(tx_thread_terminate(&victim), TX_SUCCESS, 178);
+ expect_status(tx_thread_delete(&victim), TX_SUCCESS, 179);
+ expect(scheduler_consistent(), 180);
+ }
+
+ if (expectations_failed != 0)
+ {
+
+ printf("ERROR #%u (%lu of %lu expectations failed:",
+ first_failure, expectations_failed, expectations_checked);
+
+ for (index = 0; index < failed_tag_count; index++)
+ {
+ printf(" %u", failed_tags[index]);
+ }
+
+ if (expectations_failed > ((unsigned long) failed_tag_count))
+ {
+ printf(" ...");
+ }
+
+ printf(")\n");
+ fflush(stdout);
+ test_control_return(1);
+ }
+ else
+ {
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void victim_entry(ULONG thread_input)
+{
+
+ (void) thread_input;
+
+ victim_counter++;
+
+ if (victim_take_mutex == TX_TRUE)
+ {
+ tx_mutex_get(&victim_mutex, TX_NO_WAIT);
+ }
+
+ if (victim_wait_semaphore == TX_TRUE)
+ {
+
+ /* Suspend on an object, so that a termination takes the non-ready
+ branch and runs the suspension cleanup routine. */
+ tx_semaphore_get(&victim_semaphore, TX_WAIT_FOREVER);
+ }
+
+ if (victim_self_terminate == TX_TRUE)
+ {
+
+ /* This does not return: the termination detaches this thread and the
+ preemption check at the end of the service switches away from it. */
+ tx_thread_terminate(tx_thread_identify());
+ }
+
+ while (victim_hold == TX_TRUE)
+ {
+
+ /* Stay ready, so that a terminate issued by another thread finds this
+ thread on the ready list. */
+ tx_thread_relinquish();
+ }
+
+ /* Returning enters the completion transition. */
+}
+
+
+static void peer_entry(ULONG thread_input)
+{
+
+ if (thread_input == 1)
+ {
+ peer_a_counter++;
+ }
+ else
+ {
+ peer_b_counter++;
+ }
+
+ while (peers_run == TX_TRUE)
+ {
+
+ /* Stay ready at the victim's priority, so that the victim's ready ring
+ has more than one member. */
+ tx_thread_relinquish();
+ }
+}
+
+
+static void replacement_entry(ULONG thread_input)
+{
+
+ (void) thread_input;
+
+ replacement_counter++;
+
+ /* Only reached on a kernel that let the callback recreate the control
+ block. Suspend rather than return, so that the run does not depend on a
+ second completion transition over a control block already known to be
+ inconsistent. */
+ tx_thread_suspend(tx_thread_identify());
+}
diff --git a/test/smp/regression/threadx_thread_information_test.c b/test/smp/regression/threadx_thread_information_test.c
index b5d06e71..fef3473f 100644
--- a/test/smp/regression/threadx_thread_information_test.c
+++ b/test/smp/regression/threadx_thread_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is for the thread information services. */
#include <stdio.h>
@@ -63,7 +65,7 @@ static void thread_0_entry(ULONG thread_input)
{
UINT status;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
UINT state;
ULONG run_count;
UINT priority;
@@ -96,7 +98,7 @@ ULONG idle_returns;
status += tx_thread_info_get(&thread_0, &name, &state, &run_count, &priority, &preemption_threshold, &time_slice, &next_thread, &suspended_thread);
/* Check for error status. */
- if ((status != TX_SUCCESS) || (state != TX_READY) || (run_count != thread_0.tx_thread_run_count) || (priority != 16) || (preemption_threshold != 16) ||
+ if ((status != TX_SUCCESS) || (name != thread_0.tx_thread_name) || (state != TX_READY) || (run_count != thread_0.tx_thread_run_count) || (priority != 16) || (preemption_threshold != 16) ||
(time_slice != 0) || (next_thread != thread_0.tx_thread_created_next) || (suspended_thread != thread_0.tx_thread_suspended_next))
{
diff --git a/test/smp/regression/threadx_thread_misaligned_stack_test.c b/test/smp/regression/threadx_thread_misaligned_stack_test.c
new file mode 100644
index 00000000..fa72aab5
--- /dev/null
+++ b/test/smp/regression/threadx_thread_misaligned_stack_test.c
@@ -0,0 +1,156 @@
+/***************************************************************************
+ * 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 creates a thread whose stack does not begin on a ULONG boundary.
+ It is the SMP counterpart of the ThreadX suite's test of the same name.
+
+ tx_thread_create rounds the starting address up to the next ULONG and then
+ takes a ULONG off the size, so that the rounding cannot push the end of the
+ stack past the memory the caller supplied:
+
+ if (new_stack_start != updated_stack_start)
+ {
+ stack_size = stack_size - (sizeof(ULONG));
+ }
+
+ Every other test in the suite hands tx_thread_create an aligned stack, so
+ that subtraction was never executed -- the only uncovered line in
+ tx_thread_create.c across all five build configurations. A misaligned stack
+ is legitimate: the API takes a VOID * and the alignment fix-up exists
+ precisely to accept one.
+
+ The line is compiled only under TX_ENABLE_STACK_CHECKING, so it is absent
+ from three of the five configurations' reports rather than uncovered in
+ them.
+
+ The thread is then run to completion rather than merely created, because the
+ point of the adjustment is that the resulting stack is still usable and still
+ inside the caller's buffer. */
+
+#include <stdio.h>
+#include "tx_api.h"
+
+
+void test_control_return(UINT status);
+
+
+static TX_THREAD thread_0;
+static TX_THREAD thread_1;
+
+/* Thread 1's stack is carved out of this by hand so that its start can be put
+ deliberately off a ULONG boundary. The extra ULONG is what the misaligned
+ start is offset into, so that the stack still ends inside the array after
+ tx_thread_create has rounded the start up. */
+
+static UCHAR misaligned_area[TEST_STACK_SIZE_PRINTF + sizeof(ULONG)];
+
+static ULONG thread_1_counter = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void thread_1_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_thread_misaligned_stack_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+CHAR *misaligned_stack;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Thread Misaligned Stack Test................................ ERROR #1\n");
+ test_control_return(1);
+ }
+
+ /* Push the start one byte past a ULONG boundary. The array itself is
+ aligned, so adding one is enough to guarantee the fix-up runs whatever
+ the alignment of the array turned out to be. */
+ misaligned_stack = ((CHAR *) misaligned_area) + 1;
+
+ status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
+ misaligned_stack, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Thread Misaligned Stack Test................................ ERROR #2\n");
+ test_control_return(1);
+ }
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+ /* On this port the two threads start on separate cores, so this one does
+ not have to wait for a relinquish to run. It sleeps well past the single
+ tick thread 1 sleeps for, so the checks below are made against a thread
+ that has actually finished either way. */
+ tx_thread_sleep(5);
+
+ /* Both increments must have happened: one before thread 1 slept and one
+ after it woke, which is the half that needed the stack to survive a
+ context switch. */
+ if (thread_1_counter != ((ULONG) 2))
+ {
+
+ printf("Running Thread Misaligned Stack Test................................ ERROR #3\n");
+ test_control_return(1);
+ }
+
+ /* The thread that ran on the misaligned stack must have run to completion,
+ which is what says the adjusted stack was usable. */
+ if (thread_1.tx_thread_state != TX_COMPLETED)
+ {
+
+ printf("Running Thread Misaligned Stack Test................................ ERROR #4\n");
+ test_control_return(1);
+ }
+
+ printf("Running Thread Misaligned Stack Test................................ SUCCESS!\n");
+ test_control_return(0);
+}
+
+
+static void thread_1_entry(ULONG thread_input)
+{
+
+ /* Use the stack either side of a context switch rather than returning
+ straight away. */
+ thread_1_counter++;
+
+ tx_thread_sleep(1);
+
+ thread_1_counter++;
+}
diff --git a/test/smp/regression/threadx_thread_multiple_sleep_test.c b/test/smp/regression/threadx_thread_multiple_sleep_test.c
index 4dfe5bdc..778495a7 100644
--- a/test/smp/regression/threadx_thread_multiple_sleep_test.c
+++ b/test/smp/regression/threadx_thread_multiple_sleep_test.c
@@ -11,7 +11,7 @@
/* This test is designed to test multiple threads sleeping for 33 ticks. */
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
#include <stdio.h>
#include "tx_api.h"
diff --git a/test/smp/regression/threadx_thread_priority_change.c b/test/smp/regression/threadx_thread_priority_change.c
index e68eadf5..d22f3d72 100644
--- a/test/smp/regression/threadx_thread_priority_change.c
+++ b/test/smp/regression/threadx_thread_priority_change.c
@@ -465,11 +465,28 @@ TX_THREAD *temp_thread;
/* Restore interrupts. */
TX_RESTORE
- /* Hit branch in _tx_thread_smp_simple_priority_change such that the
- new priority is less than the inheritance priority. */
- thread_0.tx_thread_inherit_priority = 0;
+ /* Hit the branch in _tx_thread_smp_simple_priority_change where the new
+ priority is below the inheritance priority. That needs an inheritance
+ priority above the new one, so 0 does not reach it: with 0 the other
+ branch runs, and that branch records the inheritance priority as the
+ thread's priority while still linking the thread at the new priority.
+ Thread 0 came out of here claiming priority 0 while living in the
+ priority 16 list, which is what has been hanging this test in CI.
+ Priority 0 ties with the control thread, so resuming the control thread
+ raised no preemption, and once thread 0 completed, core 0 was left owned
+ by a completed thread with the control thread ready and unscheduled.
+
+ This routine is internal and expects protection to be in force, so hold
+ it here rather than calling in the open. */
+ TX_DISABLE
+ thread_0.tx_thread_inherit_priority = 20;
_tx_thread_smp_simple_priority_change(&thread_0, 16);
+ /* Put the inheritance priority back to the value that means this thread is
+ not inheriting one, so nothing downstream reasons about a phantom. */
+ thread_0.tx_thread_inherit_priority = TX_MAX_PRIORITIES;
+ TX_RESTORE
+
/* Successful test. */
printf("SUCCESS!\n");
test_control_return(0);
@@ -513,6 +530,15 @@ static void thread_3_entry(ULONG thread_input)
UINT old_priority;
UINT loop;
+/* The interrupt below only fires on a narrow window: thread 3 at priority 6,
+ ready, and not yet at the head of its priority list. Waiting for it without a
+ bound made this test hang indefinitely when the window was never hit, which
+ showed up in CI as a timeout carrying no information about what went wrong.
+ The TX_NOT_INTERRUPTABLE path already stopped after a fixed amount of work;
+ this gives the interruptable path the same protection. */
+UINT attempts = 0;
+#define MAX_PRIORITY_CHANGE_ATTEMPTS 100000
+
/* Resume threads 4. */
tx_thread_resume(&thread_4);
@@ -537,7 +563,23 @@ UINT loop;
if (thread_4_counter)
thread_1_counter++;
- } while (test_isr_dispatch);
+ attempts++;
+
+ } while (test_isr_dispatch && (attempts < MAX_PRIORITY_CHANGE_ATTEMPTS));
+
+ /* Stop the interrupt regardless, so a window that was never hit does not
+ leave the handler installed for the rest of the test. */
+ test_isr_dispatch = TX_NULL;
+
+ if (attempts >= MAX_PRIORITY_CHANGE_ATTEMPTS)
+ {
+
+ /* The window was not reached. That is a gap in what this test covered on
+ this run rather than a fault in the code under test, so say so instead
+ of failing, and leave the counters alone so the checks that follow
+ still mean what they did before. */
+ printf("(priority change window not reached in %u attempts) ", attempts);
+ }
}
diff --git a/test/smp/regression/threadx_thread_stack_checking_test.c b/test/smp/regression/threadx_thread_stack_checking_test.c
index 9db10d0a..2ef9a2df 100644
--- a/test/smp/regression/threadx_thread_stack_checking_test.c
+++ b/test/smp/regression/threadx_thread_stack_checking_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/* This test is for the thread stack checking services. */
#include <stdio.h>
@@ -200,6 +202,19 @@ TX_THREAD fake_thread;
fake_thread.tx_thread_stack_highest_ptr = TX_NULL;
_tx_thread_stack_analyze(&fake_thread);
+ /* Call the stack analyze with a highest stack pointer below the start of the stack, which is
+ what a stack overflow or a corrupted control block looks like. This used to hang. */
+ fake_thread.tx_thread_id = ((ULONG) 0x54485244);
+ fake_thread.tx_thread_stack_start = (void *) 0x2000;
+ fake_thread.tx_thread_stack_highest_ptr = (void *) 0x1000;
+ _tx_thread_stack_analyze(&fake_thread);
+
+ /* Call the stack analyze with a highest stack pointer equal to the start of the stack. */
+ fake_thread.tx_thread_id = ((ULONG) 0x54485244);
+ fake_thread.tx_thread_stack_start = (void *) 0x2000;
+ fake_thread.tx_thread_stack_highest_ptr = (void *) 0x2000;
+ _tx_thread_stack_analyze(&fake_thread);
+
/* Clear the pattern in thread 2's stack. */
TX_MEMSET(thread_2_stack_start, (CHAR) 0x11, TEST_STACK_SIZE_PRINTF);
diff --git a/test/smp/regression/threadx_thread_stack_fill_value_test.c b/test/smp/regression/threadx_thread_stack_fill_value_test.c
new file mode 100644
index 00000000..fa6eda22
--- /dev/null
+++ b/test/smp/regression/threadx_thread_stack_fill_value_test.c
@@ -0,0 +1,190 @@
+/***************************************************************************/
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
+// Portions of this file were generated with AI assistance.
+
+/* This test checks that the stack fill value recorded in a thread control block is the
+ value that was actually written into that thread's stack. The two used to disagree
+ when TX_ENABLE_RANDOM_NUMBER_STACK_FILLING was enabled, because the control block was
+ cleared after the random value had been chosen and the stack had been filled with it,
+ which left the stack checking and stack analyze routines looking for a pattern that is
+ not the one present in the stack. */
+
+#include <stdio.h>
+#include "tx_api.h"
+
+
+/* Number of probe threads created. A random fill value can legitimately come out as zero,
+ which hides the defect for that one thread, so several threads are created and at least
+ one of them is required to have produced a non-zero pattern. */
+
+#define TEST_PROBE_THREADS 16
+
+
+static unsigned long thread_0_counter = 0;
+static TX_THREAD thread_0;
+
+static TX_THREAD probe_thread;
+static ULONG probe_stack[TX_MINIMUM_STACK / sizeof(ULONG)];
+
+
+/* Define task prototypes. */
+
+static void thread_0_entry(ULONG task_input);
+
+
+/* Prototype for test control return. */
+
+void test_control_return(UINT status);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_thread_stack_fill_value_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *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, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Check for status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Thread Stack Fill Value Test................................ ERROR #1\n");
+ test_control_return(1);
+ }
+}
+
+
+/* Define the test threads. */
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT i;
+ULONG stack_pattern;
+UINT non_zero_seen = TX_FALSE;
+
+
+ /* Increment thread 0 counter. */
+ thread_0_counter++;
+
+ /* Inform user of success getting to this test. */
+ printf("Running Thread Stack Fill Value Test................................ ");
+
+ for (i = 0; i < TEST_PROBE_THREADS; i++)
+ {
+
+ /* Create a thread that is never started, so that its stack still holds nothing but
+ the fill pattern and the initial stack frame built at the top of the stack. */
+ status = tx_thread_create(&probe_thread, "probe thread", thread_0_entry, 0,
+ probe_stack, sizeof(probe_stack),
+ 17, 17, TX_NO_TIME_SLICE, TX_DONT_START);
+
+ /* Check for status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #2\n");
+ test_control_return(1);
+ }
+
+ /* Pick up the pattern the create service wrote into the unused part of the stack. */
+ stack_pattern = *(((ULONG *) probe_thread.tx_thread_stack_start) + 4);
+
+#ifndef TX_DISABLE_STACK_FILLING
+#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING)
+
+ /* The control block must hold the value that was written into the stack. */
+ if (probe_thread.tx_thread_stack_fill_value != stack_pattern)
+ {
+
+ printf("ERROR #3\n");
+ test_control_return(1);
+ }
+
+ /* The random value is duplicated into all four bytes of the fill value. */
+ if ((stack_pattern & 0xFFUL) !=
+ ((stack_pattern >> 24) & 0xFFUL))
+ {
+
+ printf("ERROR #4\n");
+ test_control_return(1);
+ }
+
+ if (stack_pattern != ((ULONG) 0))
+ {
+
+ non_zero_seen = TX_TRUE;
+ }
+#else
+
+ /* Without random filling, the pattern is the fixed one. */
+ if (stack_pattern != ((ULONG) 0xEFEFEFEFUL))
+ {
+
+ printf("ERROR #5\n");
+ test_control_return(1);
+ }
+
+ non_zero_seen = TX_TRUE;
+#endif
+#else
+
+ /* Stack filling is disabled, so there is nothing to compare. */
+ non_zero_seen = TX_TRUE;
+#endif
+
+ /* Terminate and delete the probe thread so that the next pass starts from a clean
+ slate. A thread created with TX_DONT_START is suspended rather than completed,
+ so it must be terminated before it can be deleted. */
+ status = tx_thread_terminate(&probe_thread);
+
+ if (status == TX_SUCCESS)
+ {
+
+ status = tx_thread_delete(&probe_thread);
+ }
+
+ /* Check for status. */
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #6\n");
+ test_control_return(1);
+ }
+ }
+
+ /* A run in which every fill value came out as zero would prove nothing. */
+ if (non_zero_seen != TX_TRUE)
+ {
+
+ printf("ERROR #7\n");
+ test_control_return(1);
+ }
+
+ /* Success! */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+}
diff --git a/test/smp/regression/threadx_thread_wait_abort_and_isr_test.c b/test/smp/regression/threadx_thread_wait_abort_and_isr_test.c
index 75faf050..c66abff2 100644
--- a/test/smp/regression/threadx_thread_wait_abort_and_isr_test.c
+++ b/test/smp/regression/threadx_thread_wait_abort_and_isr_test.c
@@ -12,6 +12,7 @@
/* This test is designed to test for simultaneous thread suspension lifting AND thread wait abort calls. */
#include <stdio.h>
+#include <time.h>
#include "tx_api.h"
#include "tx_timer.h"
@@ -178,6 +179,54 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
+/* The window this test waits for is probabilistic, and the handler above says
+ as much: it can settle into a resonance in which the condition is never met.
+ Waiting for it without a bound means such a run never ends, and this test has
+ been the most expensive thing in the suite: run one at a time in CI it took
+ between 148 and 726 seconds per configuration, 88 percent of the whole
+ ThreadX suite, against 273 seconds for the other ninety five tests together.
+
+ The budget is in wall clock seconds rather than ticks on purpose. The
+ simulated tick clock is not a proxy for elapsed time here: a tick arrives only
+ when the port's timer thread gets to run, so under load, or under coverage
+ instrumentation, ticks fall behind and never catch up. A budget of 20000
+ ticks, nominally 200 seconds, failed to stop a run that took 726 seconds,
+ because fewer than 20000 ticks had passed. time() does not drift that way.
+
+ How many windows to ask for is set by what a run can actually reach. Four CI
+ runs of the same tree measured this loop in two modes: one where a window
+ arrives in milliseconds and every window asked for costs under a second in
+ total, and one where a single window costs around forty seconds. Seven of
+ twenty configuration-runs landed in the slow mode and ran out of budget,
+ reaching 0, 3, 3, 3, 4, 7 and 7 of the ten then asked for, every one of them
+ still reporting a pass. Asking for three keeps the count reachable in both
+ modes. The later hits repeat what the first ones establish, so the coverage
+ given up is small, and what is recorded is honest. This copy reaches its
+ twenty windows in under half a second in all five of its configurations, so
+ it keeps that count; the non-SMP copy, where the slow mode was measured, asks
+ for three.
+
+ Reaching the window no times at all is different in kind, which is what the
+ ceiling below is for. The check after the loop compares semaphore bookkeeping
+ that a window has to have touched to mean anything, so a pass with a count of
+ zero claims coverage the run did not have. One of those twenty runs did
+ exactly that, and said so only in an artifact nobody reads. A run that has not
+ reached the window once keeps trying up to the ceiling, and fails if it still
+ has not.
+
+ The budget is 180 seconds rather than 120 because of what the slow mode costs
+ per window. The seven truncated runs reached their windows at between 17 and
+ 40 seconds each, so three of them can need 120 seconds, which is exactly what
+ the old budget allowed and would have truncated at two. 180 leaves margin at
+ the worst rate measured, and bounds five configurations at 15 minutes against
+ a 60 minute step timeout. Locally, where a window costs 2 to 5 seconds, three
+ of them take 5 to 14 seconds and the budget is never approached. */
+#define WAIT_ABORT_WINDOWS_WANTED ((ULONG) 20)
+#define WAIT_ABORT_SECOND_BUDGET ((ULONG) 180)
+#define WAIT_ABORT_ZERO_WINDOW_CEILING ((ULONG) 300)
+
+time_t start_wall;
+
/* Setup ISR for this test. */
test_isr_dispatch = isr_entry;
@@ -186,7 +235,8 @@ UINT status;
printf("Running Thread Wait Abort and ISR Resume Test....................... ");
/* Loop to exploit the probability window inside tx_thread_wait_abort. */
- while (condition_count < 20)
+ start_wall = time(TX_NULL);
+ while (condition_count < WAIT_ABORT_WINDOWS_WANTED)
{
/* Suspend on the semaphore that is going to be set via the ISR. */
@@ -227,11 +277,48 @@ UINT status;
#endif
}
+
+ /* Out of budget? A run that has not reached the window even once has
+ verified nothing yet, so it gets the higher ceiling before giving up. */
+#ifdef TX_NOT_INTERRUPTABLE
+ if (((ULONG) (time(TX_NULL) - start_wall)) > WAIT_ABORT_SECOND_BUDGET)
+ break;
+#else
+ if (condition_count == 0)
+ {
+ if (((ULONG) (time(TX_NULL) - start_wall)) > WAIT_ABORT_ZERO_WINDOW_CEILING)
+ break;
+ }
+ else if (((ULONG) (time(TX_NULL) - start_wall)) > WAIT_ABORT_SECOND_BUDGET)
+ break;
+#endif
}
/* Clear ISR dispatch. */
test_isr_dispatch = TX_NULL;
+ /* Say what this run reached, on every run and not only a short one. A count
+ printed only on shortfall cannot be told apart from a count nobody
+ recorded, and this line is what the CI artifacts carry. Falling short is a
+ gap in what was exercised rather than a fault in the code under test, so
+ the check below still runs and still means what it did. */
+ printf("(reached %lu of %lu windows in %lu seconds) ",
+ (ULONG) condition_count, WAIT_ABORT_WINDOWS_WANTED,
+ (ULONG) (time(TX_NULL) - start_wall));
+
+#ifndef TX_NOT_INTERRUPTABLE
+
+ /* Reached it no times? Then the check below compares bookkeeping no window
+ ever touched, and a pass would report coverage this run did not have. */
+ if (condition_count == 0)
+ {
+
+ /* Test error! */
+ printf("ERROR #8\n");
+ test_control_return(4);
+ }
+#endif
+
#ifdef TX_NOT_INTERRUPTABLE
/* At this point, check to see if we got all the semaphores! */
if ((thread_0_counter != (semaphore_put_counter - semaphore_0.tx_semaphore_count)) ||
diff --git a/test/smp/regression/threadx_timer_information_test.c b/test/smp/regression/threadx_timer_information_test.c
index ae3bb414..cbb88239 100644
--- a/test/smp/regression/threadx_timer_information_test.c
+++ b/test/smp/regression/threadx_timer_information_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test timer information services. */
#include <stdio.h>
@@ -104,7 +106,7 @@ static void thread_0_entry(ULONG thread_input)
UINT status;
UINT interrupt_status;
-CHAR *name;
+TX_NAME_CONST CHAR *name;
UINT active;
ULONG remaining_ticks;
ULONG reschedule_ticks;
@@ -231,7 +233,7 @@ TX_TIMER_INTERNAL **list_head;
status += tx_timer_info_get(&timer_0, &name, &active, &remaining_ticks, &reschedule_ticks, &next_timer);
/* Check for successful completion. */
- if ((status != TX_SUCCESS) || (active != TX_TRUE) || (remaining_ticks != 100) || (reschedule_ticks != 200) || (next_timer != &timer_1))
+ if ((status != TX_SUCCESS) || (name != timer_0.tx_timer_name) || (active != TX_TRUE) || (remaining_ticks != 100) || (reschedule_ticks != 200) || (next_timer != &timer_1))
{
/* Application timer error. */
diff --git a/test/smp/regression/threadx_timer_simple_test.c b/test/smp/regression/threadx_timer_simple_test.c
index e2272bd2..5abd1d3c 100644
--- a/test/smp/regression/threadx_timer_simple_test.c
+++ b/test/smp/regression/threadx_timer_simple_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test a simple application timer services, including create,
activate, deactivate, change, and delete. */
@@ -63,7 +65,7 @@ static void thread_1_entry(ULONG thread_input);
static void timer_0_expiration(ULONG timer_input);
static void timer_1_expiration(ULONG timer_input);
-UINT _txe_timer_create(TX_TIMER *timer_ptr, CHAR *name_ptr,
+UINT _txe_timer_create(TX_TIMER *timer_ptr, TX_NAME_CONST CHAR *name_ptr,
VOID (*expiration_function)(ULONG), ULONG expiration_input,
ULONG initial_ticks, ULONG reschedule_ticks, UINT auto_activate, UINT timer_control_block_size);
diff --git a/test/smp/regression/threadx_trace_basic_test.c b/test/smp/regression/threadx_trace_basic_test.c
index 51fa6256..10b927bb 100644
--- a/test/smp/regression/threadx_trace_basic_test.c
+++ b/test/smp/regression/threadx_trace_basic_test.c
@@ -9,6 +9,8 @@
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
+/* Portions of this file were generated with AI assistance. */
+
/* This test is designed to test trace functionality in ThreadX. */
#include <stdio.h>
@@ -30,7 +32,7 @@
extern VOID (*test_isr_dispatch)(void);
extern UINT _tx_trace_interrupt_control(UINT new_posture);
-extern VOID _tx_trace_object_register(UCHAR object_type, VOID *object_ptr, CHAR *object_name, ULONG parameter_1, ULONG parameter_2);
+extern VOID _tx_trace_object_register(UCHAR object_type, VOID *object_ptr, TX_NAME_CONST CHAR *object_name, ULONG parameter_1, ULONG parameter_2);
extern VOID _tx_trace_object_unregister(VOID *object_ptr);
static unsigned long thread_0_counter = 0;
diff --git a/test/smp/regression/threadx_trace_entry_update_test.c b/test/smp/regression/threadx_trace_entry_update_test.c
new file mode 100644
index 00000000..17c36600
--- /dev/null
+++ b/test/smp/regression/threadx_trace_entry_update_test.c
@@ -0,0 +1,367 @@
+/***************************************************************************
+ * 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 is the SMP counterpart of the ThreadX suite's trace entry update test.
+ It drives the trace entry update paths -- the blocks guarded by
+ TX_ENABLE_EVENT_TRACE that go back and patch a trace entry after the call
+ they belong to has finished its work.
+
+ Those blocks all have the same shape:
+
+ if (entry_ptr != TX_NULL)
+ {
+ if (time_stamp == entry_ptr -> tx_trace_buffer_entry_time_stamp)
+ {
+ entry_ptr -> tx_trace_buffer_entry_information_field_N = ...;
+ }
+ }
+
+ entry_ptr comes from _tx_trace_buffer_current_ptr, which stays TX_NULL until
+ tx_trace_enable() is called at run time. Compiling with TX_ENABLE_EVENT_TRACE
+ is therefore not enough to reach them, and the trace_build configuration on
+ its own never does: the only other test that enables tracing --
+ threadx_trace_basic_test -- exercises the enable API itself and never calls
+ tx_block_allocate or tx_byte_allocate at all. So the update blocks in
+ tx_block_allocate.c, tx_byte_allocate.c and tx_thread_system_suspend.c were
+ absent from the coverage report's covered set while appearing in its
+ denominator.
+
+ Both allocators are driven twice, because they carry two of these blocks
+ each and only one is on the path that succeeds immediately: the second sits
+ after a suspension, where the allocation completes in the releasing thread's
+ context. Hence the second thread, which exists only to release what
+ thread 0 is waiting on.
+
+ The one thing this version has to do that the ThreadX version does not is
+ pin both threads to a single core. On four cores the two threads run at the
+ same time, and thread 1 would give the block back before thread 0 had
+ suspended waiting for it -- the suspension the second update block sits
+ behind would never happen. Excluding both from cores 1 to 3 restores the
+ strict alternation the ThreadX suite gets for free.
+
+ The test is built in all five configurations, and four of them do not define
+ TX_ENABLE_EVENT_TRACE. There tx_trace_enable reports TX_FEATURE_NOT_ENABLED,
+ the update blocks do not exist, and the allocation sequence below is still a
+ valid exercise of both allocators' suspension paths -- so the test is
+ meaningful in every configuration rather than skipped in four. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "threadx_test_port.h"
+#define TX_SOURCE_CODE
+#include "tx_trace.h"
+
+
+void test_control_return(UINT status);
+
+
+static TX_THREAD thread_0;
+static TX_THREAD thread_1;
+
+static TX_BLOCK_POOL block_pool_0;
+static TX_BYTE_POOL byte_pool_0;
+static TX_SEMAPHORE semaphore_0;
+
+/* Every thread this test creates is confined to core 0, so that the two of
+ them take turns the way they would on a uniprocessor. Bits 1, 2 and 3 set
+ means cores 1, 2 and 3 are excluded. */
+
+#define TRACE_ENTRY_UPDATE_CORE_0_ONLY ((ULONG) 0xE)
+
+/* The trace buffer is sized generously on purpose. The inner test of each
+ update block compares the saved time stamp against the one in the entry, and
+ a buffer small enough to wrap between the call and its completion would
+ overwrite the entry and take the false branch every time -- the block would
+ be reached and its body still never run. */
+
+static UCHAR trace_buffer[16384];
+
+/* Four blocks of 20 bytes. */
+
+static UCHAR block_pool_area[TX_TEST_BLOCK_POOL_BYTES(20, 4)];
+static UCHAR byte_pool_area[TX_TEST_BYTE_POOL_BYTES(512)];
+
+/* Allocated by thread 0 and released by thread 1, which is what lets thread 0's
+ suspended allocations complete. */
+
+static void *held_block;
+static void *held_bytes;
+
+static UINT error = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void thread_1_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_trace_entry_update_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ /* Create the pools before tracing is enabled, so that the events this test
+ cares about are the allocations rather than the creates. */
+ status = tx_block_pool_create(&block_pool_0, "trace entry update block pool",
+ 20, block_pool_area, sizeof(block_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #1\n");
+ test_control_return(1);
+ }
+
+ status = tx_byte_pool_create(&byte_pool_0, "trace entry update byte pool",
+ byte_pool_area, sizeof(byte_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #2\n");
+ test_control_return(1);
+ }
+
+ /* Turn tracing on. This is the whole point of the test: without it
+ _tx_trace_buffer_current_ptr stays TX_NULL and every update block below
+ takes its guarding false branch. */
+ _tx_trace_initialize();
+ status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), 16);
+
+#ifdef TX_ENABLE_EVENT_TRACE
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #3\n");
+ test_control_return(1);
+ }
+#else
+
+ /* Four of the five build configurations reach this branch. The rest of the
+ test still runs; it just is not exercising any update block. */
+ if (status != TX_FEATURE_NOT_ENABLED)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #4\n");
+ test_control_return(1);
+ }
+#endif
+
+ /* Register an object with no name at all, now that tracing is on. The name
+ copy in _tx_trace_object_register guards against a TX_NULL name and
+ breaks out of the copy loop, and nothing else in the suite takes that
+ branch with tracing enabled -- threadx_trace_basic_test makes the
+ equivalent call only under #ifndef TX_ENABLE_EVENT_TRACE, against the
+ no-op stub.
+
+ A semaphore rather than a thread, deliberately: for a thread object the
+ register function dereferences the pointer it is handed to read the
+ thread's priority, so that type needs a real TX_THREAD behind it. */
+ status = tx_semaphore_create(&semaphore_0, TX_NULL, 1);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #5\n");
+ test_control_return(1);
+ }
+
+ /* Thread 0 drives the test and must run first, so it takes the higher
+ priority -- the lower number. Both threads are created stopped so that
+ the core exclusion is in place before either of them is schedulable. */
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_0, TRACE_ENTRY_UPDATE_CORE_0_ONLY);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #6\n");
+ test_control_return(1);
+ }
+
+ /* Thread 1 releases what thread 0 waits on. It runs only while thread 0 is
+ suspended, which is exactly when it is needed -- and only because it
+ shares thread 0's single core. */
+ status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_1, TRACE_ENTRY_UPDATE_CORE_0_ONLY);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #7\n");
+ test_control_return(1);
+ }
+
+ /* Start them now that both exclusions are set. Priority decides the order,
+ not this one. */
+ status = tx_thread_resume(&thread_1);
+ status += tx_thread_resume(&thread_0);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #8\n");
+ test_control_return(1);
+ }
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+void *block_ptr;
+void *byte_ptr;
+UINT i;
+
+
+ /* Inform user. */
+ printf("Running Trace Entry Update Test..................................... ");
+
+ /* Empty the block pool. The first of these takes the immediate-success path
+ through tx_block_allocate, which carries the first update block. */
+ held_block = TX_NULL;
+ for (i = 0; i < 4; i++)
+ {
+
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ break;
+ }
+
+ /* Keep the first one for thread 1 to give back. */
+ if (held_block == TX_NULL)
+ {
+
+ held_block = block_ptr;
+ }
+ }
+
+ /* The pool is empty now, so this suspends. It completes in thread 1's
+ context when the block comes back, which is the second update block in
+ tx_block_allocate -- and the suspend it goes through carries the update
+ block in tx_thread_system_suspend that names the next thread to run on
+ this core. */
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #9\n");
+ test_control_return(1);
+ }
+
+ /* Now the same shape for the byte pool. Take most of it first, on the path
+ that succeeds immediately. */
+ status = tx_byte_allocate(&byte_pool_0, &held_bytes, 300, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #10\n");
+ test_control_return(1);
+ }
+
+ /* Not enough left, so this one suspends and is satisfied by thread 1's
+ release. */
+ status = tx_byte_allocate(&byte_pool_0, &byte_ptr, 300, TX_WAIT_FOREVER);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #11\n");
+ test_control_return(1);
+ }
+
+ /* Sleep, so that thread 1 gets to run out its entry function and suspend
+ itself. That leaves nothing ready to execute anywhere, which is a second
+ update block in tx_thread_system_suspend -- the one on the branch taken
+ when the priority map is empty. The two suspensions above never reach it,
+ because thread 1 was always ready to take over. */
+ status = tx_thread_sleep(5);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #12\n");
+ test_control_return(1);
+ }
+
+ if (error)
+ {
+
+ printf("ERROR #13\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ /* Successful test. */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void thread_1_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* Reached only when thread 0 has suspended on the empty block pool. */
+ status = tx_block_release(held_block);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ }
+
+ /* And again when thread 0 has suspended on the byte pool. Thread 0 preempts
+ on the release above, runs until it suspends a second time, and control
+ comes back here. */
+ status = tx_byte_release(held_bytes);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ }
+
+ /* Thread 0 reports the result; this thread has nothing left to do. */
+ tx_thread_suspend(&thread_1);
+}