/*************************************************************************** * 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 takes the timer thread's loop round to its exit. The timer thread's body is one loop over a condition that under TX_MISRA_ENABLE is a call rather than the literal the other six configurations compile, so that the constant is not written into the controlling expression. The function behind it takes nothing and returns true, so the loop's exit arm never executes and, without the macro, does not exist at all. This test replaces a kernel symbol, which makes it one of the few tests in this suite whose kernel is not the certified kernel. The disclosure an assessor is owed is therefore here rather than in a note: - The symbol is `_tx_misra_always_true`, in common_smp/src/tx_misra.c. - The library's version has no behaviour of its own. Its whole body is `return(TX_TRUE);`: it reads no parameter, no variable and no state, and it exists so that a loop written to run forever does not carry a constant in its controlling expression. The version below returns the same value until the test arms it. - Replacing it leaves everything else intact because nothing else in the kernel depends on what it returns. It has exactly two callers in libthreadx_smp.so, and this test drives both: this loop, and the per-core state initialisation loop in _tx_thread_smp_current_state_set, which the kernel itself reaches only from _tx_initialize_kernel_enter, _tx_initialize_kernel_setup and _tx_thread_smp_high_level_initialize, all of which run before the scheduler starts a thread. The second is therefore called here directly, with the shim armed, and is given back the state its last core already holds so that the one assignment a shortened loop performs writes the value that was there. The shim is armed from a running thread and is disarmed before the test returns. - The library's own version keeps its coverage from every other test in the suite, all of which drive this same loop through it thousands of times. What the coverage costs is the timer thread. The loop is the whole body of the timer thread's entry function, so an exit ends the thread, and from that point the system has no thread to process timer expirations, thread timeouts or time slices. The test therefore does it deliberately and last: it proves the timer is working first, arms the shim, waits for the thread to end, and reports. Nothing after the arming depends on a tick. */ #include #include "tx_api.h" #define TX_SOURCE_CODE #define TX_THREAD_SMP_SOURCE_CODE #include "tx_thread.h" #include "tx_timer.h" #define TEST_PRIORITY 16 /* The timer must expire enough times before the shim is armed for the test to have shown that the thread is doing its work. */ #define EXPIRATIONS_BEFORE_ARMING 3 /* How long to wait, in ticks. This is a bound, so that a shim the kernel never reaches fails the test rather than hanging it. */ #define ARMED_TICK_BUDGET 200 static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; static TX_TIMER timer_0; static volatile ULONG timer_0_counter = 0; #if defined(TX_MISRA_ENABLE) && !defined(TX_TIMER_PROCESS_IN_ISR) && !defined(TX_NO_TIMER) #define TEST_DRIVES_THE_LOOP /* While this is set the shim below reports that the loop should end. */ static volatile UINT shim_armed = TX_FALSE; /* How many times it has done so, which is how many times the exit arm has been taken. */ static volatile ULONG shim_exits = 0; /* The MISRA loop condition, defined here so that it is this one the timer thread's loop calls. The kernel is built shared and every internal reference goes through the procedure linkage table, so a definition in the test executable is the one reached, with no linker option and no preloading. The library's returns true; this one returns false once it is armed. */ UINT _tx_misra_always_true(void) { if (shim_armed == TX_TRUE) { shim_exits++; return(TX_FALSE); } return(TX_TRUE); } #endif /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); /* Count the expirations, so the test can show the timer thread was running before it ends it. */ static void timer_0_entry(ULONG timer_input) { timer_0_counter++; } static void thread_0_entry(ULONG thread_input) { UINT status; ULONG deadline; #ifdef TEST_DRIVES_THE_LOOP UINT core_index; ULONG saved_state[TX_THREAD_SMP_MAX_CORES]; #endif /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running Timer MISRA Loop Test..................................... "); status = tx_timer_activate(&timer_0); if (status != TX_SUCCESS) { printf("ERROR #2\n"); test_control_return(1); } /* The timer thread has to be doing its work before the test ends it, otherwise an exit proves nothing about the loop it exited from. */ deadline = tx_time_get() + ((ULONG) ARMED_TICK_BUDGET); while (timer_0_counter < ((ULONG) EXPIRATIONS_BEFORE_ARMING)) { if (tx_time_get() > deadline) { printf("ERROR #3\n"); test_control_return(1); } } #ifdef TEST_DRIVES_THE_LOOP /* From here on nothing may depend on a tick. */ shim_armed = TX_TRUE; /* The thread ends the next time the tick resumes it and the loop retests its condition. */ deadline = tx_time_get() + ((ULONG) ARMED_TICK_BUDGET); while (_tx_timer_thread.tx_thread_state != TX_COMPLETED) { if (tx_time_get() > deadline) { /* The tick is still running -- the clock is what this waits on -- so a thread that has not ended means the loop reached the library's condition rather than this test's. */ shim_armed = TX_FALSE; printf("ERROR #4\n"); test_control_return(1); } } /* The loop tested its condition once more and took the arm that leaves it. */ if (shim_exits != ((ULONG) 1)) { shim_armed = TX_FALSE; printf("ERROR #5\n"); test_control_return(1); } /* The same construct a second time, in the only other loop the shim controls. _tx_thread_smp_current_state_set walks the cores downwards and leaves through a break on the lowest, so its controlling expression is never false in service. With the shim armed the highest core is written and the loop leaves through the expression instead -- so the value handed to it is the one that core already holds, and the state every core sees is the state it saw before the call. */ for (core_index = ((UINT) 0); core_index < ((UINT) TX_THREAD_SMP_MAX_CORES); core_index++) { saved_state[core_index] = _tx_thread_system_state[core_index]; } _tx_thread_smp_current_state_set(saved_state[TX_THREAD_SMP_MAX_CORES - ((UINT) 1)]); shim_armed = TX_FALSE; /* The second loop tested its condition and took the same arm. */ if (shim_exits != ((ULONG) 2)) { printf("ERROR #6\n"); test_control_return(1); } /* And left every core's state as it found it. */ for (core_index = ((UINT) 0); core_index < ((UINT) TX_THREAD_SMP_MAX_CORES); core_index++) { if (_tx_thread_system_state[core_index] != saved_state[core_index]) { printf("ERROR #7\n"); test_control_return(1); } } #endif status = tx_timer_deactivate(&timer_0); if (status != TX_SUCCESS) { printf("ERROR #8\n"); test_control_return(1); } /* Success! */ printf("SUCCESS!\n"); test_control_return(0); } /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_timer_misra_loop_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(&thread_0, "thread 0", thread_0_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, TEST_PRIORITY, TEST_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Timer MISRA Loop Test..................................... ERROR #1\n"); test_control_return(1); } /* A tick's worth of work every tick, so the timer thread is resumed repeatedly and the loop retests its condition each time. */ status = tx_timer_create(&timer_0, "timer 0", timer_0_entry, 0, 1, 1, TX_NO_ACTIVATE); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Timer MISRA Loop Test..................................... ERROR #1\n"); test_control_return(1); } }